Compare commits
10 Commits
1f4aa569e3
...
b24e0818f0
| Author | SHA1 | Date |
|---|---|---|
|
|
b24e0818f0 | |
|
|
bed5f88da7 | |
|
|
fd3ffc2b79 | |
|
|
9e89932600 | |
|
|
799e358d0f | |
|
|
d042bf46c8 | |
|
|
457bafee31 | |
|
|
a50178a736 | |
|
|
4c4f90971d | |
|
|
8efd8b083d |
|
|
@ -0,0 +1,109 @@
|
|||
package com.ether.pms.mdm.controller;
|
||||
|
||||
import com.ether.pms.common.ApiResponse;
|
||||
import com.ether.pms.mdm.entity.EnergyConsumption;
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import com.ether.pms.mdm.service.EnergyConsumptionService;
|
||||
import com.ether.pms.mdm.service.EnergyMeterService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/ops")
|
||||
@RequiredArgsConstructor
|
||||
public class EnergyController {
|
||||
|
||||
private final EnergyMeterService energyMeterService;
|
||||
private final EnergyConsumptionService energyConsumptionService;
|
||||
|
||||
// ==================== 计量点管理 ====================
|
||||
|
||||
@PostMapping("/energy-meters")
|
||||
public ApiResponse<EnergyMeter> createMeter(@Valid @RequestBody EnergyMeter meter) {
|
||||
return ApiResponse.success(energyMeterService.createMeter(meter));
|
||||
}
|
||||
|
||||
@GetMapping("/energy-meters")
|
||||
public ApiResponse<List<EnergyMeter>> getMeters(
|
||||
@RequestParam UUID projectId,
|
||||
@RequestParam(required = false) EnergyMeter.EnergyType energyType) {
|
||||
List<EnergyMeter> meters;
|
||||
if (energyType != null) {
|
||||
meters = energyMeterService.getMetersByType(projectId, energyType);
|
||||
} else {
|
||||
meters = energyMeterService.getMetersByProject(projectId);
|
||||
}
|
||||
return ApiResponse.success(meters);
|
||||
}
|
||||
|
||||
@GetMapping("/energy-meters/{id}")
|
||||
public ApiResponse<EnergyMeter> getMeter(@PathVariable UUID id) {
|
||||
return ApiResponse.success(energyMeterService.getMeterById(id));
|
||||
}
|
||||
|
||||
@PutMapping("/energy-meters/{id}")
|
||||
public ApiResponse<EnergyMeter> updateMeter(@PathVariable UUID id, @Valid @RequestBody EnergyMeter meter) {
|
||||
return ApiResponse.success(energyMeterService.updateMeter(id, meter));
|
||||
}
|
||||
|
||||
@DeleteMapping("/energy-meters/{id}")
|
||||
public ApiResponse<Void> deleteMeter(@PathVariable UUID id) {
|
||||
energyMeterService.deleteMeter(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
// ==================== 能耗记录 ====================
|
||||
|
||||
@PostMapping("/energy-consumption")
|
||||
public ApiResponse<EnergyConsumption> recordConsumption(@RequestBody RecordConsumptionRequest request) {
|
||||
EnergyConsumption consumption = energyConsumptionService.recordConsumption(
|
||||
request.getMeterId(), request.getCurrentReading(), request.getRecordedBy());
|
||||
return ApiResponse.success(consumption);
|
||||
}
|
||||
|
||||
@GetMapping("/energy-consumption/{meterId}")
|
||||
public ApiResponse<List<EnergyConsumption>> getConsumption(
|
||||
@PathVariable UUID meterId,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
|
||||
List<EnergyConsumption> consumptions;
|
||||
if (startDate != null && endDate != null) {
|
||||
consumptions = energyConsumptionService.getConsumptionByMeterAndDateRange(meterId, startDate, endDate);
|
||||
} else {
|
||||
consumptions = energyConsumptionService.getConsumptionByMeter(meterId);
|
||||
}
|
||||
return ApiResponse.success(consumptions);
|
||||
}
|
||||
|
||||
// ==================== 能耗统计 ====================
|
||||
|
||||
@GetMapping("/energy-statistics/by-type")
|
||||
public ApiResponse<Map<EnergyMeter.EnergyType, BigDecimal>> getConsumptionByType(
|
||||
@RequestParam UUID projectId,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate month) {
|
||||
return ApiResponse.success(energyConsumptionService.getConsumptionByType(projectId, month));
|
||||
}
|
||||
|
||||
@GetMapping("/energy-statistics/unit-consumption")
|
||||
public ApiResponse<BigDecimal> getUnitConsumption(
|
||||
@RequestParam UUID projectId,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate month) {
|
||||
return ApiResponse.success(energyConsumptionService.getUnitConsumption(projectId, month));
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RecordConsumptionRequest {
|
||||
private UUID meterId;
|
||||
private BigDecimal currentReading;
|
||||
private UUID recordedBy;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.ether.pms.mdm.controller;
|
||||
|
||||
import com.ether.pms.common.ApiResponse;
|
||||
import com.ether.pms.mdm.entity.EquipmentFailureHistory;
|
||||
import com.ether.pms.mdm.entity.EquipmentHealthScore;
|
||||
import com.ether.pms.mdm.service.EquipmentHealthService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/ops")
|
||||
@RequiredArgsConstructor
|
||||
public class EquipmentHealthController {
|
||||
|
||||
private final EquipmentHealthService equipmentHealthService;
|
||||
|
||||
/**
|
||||
* 获取设备健康度
|
||||
*/
|
||||
@GetMapping("/equipment-health/{equipmentId}")
|
||||
public ApiResponse<EquipmentHealthScore> getEquipmentHealth(@PathVariable UUID equipmentId) {
|
||||
return ApiResponse.success(equipmentHealthService.getLatestHealthScore(equipmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备健康度历史
|
||||
*/
|
||||
@GetMapping("/equipment-health/{equipmentId}/history")
|
||||
public ApiResponse<List<EquipmentHealthScore>> getHealthHistory(@PathVariable UUID equipmentId) {
|
||||
return ApiResponse.success(equipmentHealthService.getHealthHistory(equipmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算设备健康度
|
||||
*/
|
||||
@PostMapping("/equipment-health/calculate")
|
||||
public ApiResponse<EquipmentHealthScore> calculateHealthScore(@RequestBody CalculateHealthRequest request) {
|
||||
return ApiResponse.success(equipmentHealthService.calculateHealthScore(request.getEquipmentId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录故障
|
||||
*/
|
||||
@PostMapping("/equipment-failure-history")
|
||||
public ApiResponse<EquipmentFailureHistory> recordFailure(@Valid @RequestBody EquipmentFailureHistory failure) {
|
||||
return ApiResponse.success(equipmentHealthService.recordFailure(failure));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备故障历史
|
||||
*/
|
||||
@GetMapping("/equipment-failure-history/{equipmentId}")
|
||||
public ApiResponse<List<EquipmentFailureHistory>> getFailureHistory(@PathVariable UUID equipmentId) {
|
||||
return ApiResponse.success(equipmentHealthService.getFailureHistory(equipmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备MTBF(平均故障间隔时间)
|
||||
*/
|
||||
@GetMapping("/equipment-mtbf/{equipmentId}")
|
||||
public ApiResponse<MTBFResponse> getEquipmentMTBF(
|
||||
@PathVariable UUID equipmentId,
|
||||
@RequestParam(required = false, defaultValue = "30") Integer days) {
|
||||
BigDecimal mtbf = equipmentHealthService.calculateMTBF(equipmentId, days);
|
||||
MTBFResponse response = new MTBFResponse();
|
||||
response.setEquipmentId(equipmentId);
|
||||
response.setDays(days);
|
||||
response.setMtbfHours(mtbf);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备MTTR(平均修复时间)
|
||||
*/
|
||||
@GetMapping("/equipment-mttr/{equipmentId}")
|
||||
public ApiResponse<MTTRResponse> getEquipmentMTTR(
|
||||
@PathVariable UUID equipmentId,
|
||||
@RequestParam(required = false, defaultValue = "30") Integer days) {
|
||||
BigDecimal mttr = equipmentHealthService.calculateMTTR(equipmentId, days);
|
||||
MTTRResponse response = new MTTRResponse();
|
||||
response.setEquipmentId(equipmentId);
|
||||
response.setDays(days);
|
||||
response.setMttrHours(mttr);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CalculateHealthRequest {
|
||||
private UUID equipmentId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MTBFResponse {
|
||||
private UUID equipmentId;
|
||||
private Integer days;
|
||||
private BigDecimal mtbfHours;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MTTRResponse {
|
||||
private UUID equipmentId;
|
||||
private Integer days;
|
||||
private BigDecimal mttrHours;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.ether.pms.mdm.controller;
|
||||
|
||||
import com.ether.pms.common.ApiResponse;
|
||||
import com.ether.pms.mdm.entity.InspectionTemplate;
|
||||
import com.ether.pms.mdm.service.InspectionTemplateService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/ops/inspection-templates")
|
||||
@RequiredArgsConstructor
|
||||
public class InspectionTemplateController {
|
||||
|
||||
private final InspectionTemplateService inspectionTemplateService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<InspectionTemplate>> getTemplates(@RequestParam UUID projectId) {
|
||||
List<InspectionTemplate> templates = inspectionTemplateService.getTemplatesByProject(projectId);
|
||||
return ApiResponse.success(templates);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<InspectionTemplate> createTemplate(@Valid @RequestBody InspectionTemplate template) {
|
||||
InspectionTemplate created = inspectionTemplateService.createTemplate(template);
|
||||
return ApiResponse.success(created);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<InspectionTemplate> getTemplate(@PathVariable UUID id) {
|
||||
return ApiResponse.success(inspectionTemplateService.getTemplateById(id));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<InspectionTemplate> updateTemplate(@PathVariable UUID id,
|
||||
@Valid @RequestBody InspectionTemplate template) {
|
||||
return ApiResponse.success(inspectionTemplateService.updateTemplate(id, template));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/copy")
|
||||
public ApiResponse<InspectionTemplate> copyTemplate(@PathVariable UUID id,
|
||||
@RequestParam String newName) {
|
||||
return ApiResponse.success(inspectionTemplateService.copyTemplate(id, newName));
|
||||
}
|
||||
|
||||
@GetMapping("/by-type/{equipmentType}")
|
||||
public ApiResponse<List<InspectionTemplate>> getTemplatesByType(@PathVariable String equipmentType) {
|
||||
return ApiResponse.success(inspectionTemplateService.getTemplatesByType(equipmentType));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.ether.pms.mdm.controller;
|
||||
|
||||
import com.ether.pms.common.ApiResponse;
|
||||
import com.ether.pms.mdm.entity.SparePart;
|
||||
import com.ether.pms.mdm.entity.SparePartCategory;
|
||||
import com.ether.pms.mdm.entity.SparePartRecord;
|
||||
import com.ether.pms.mdm.service.SparePartService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/ops/spare-parts")
|
||||
@RequiredArgsConstructor
|
||||
public class SparePartController {
|
||||
|
||||
private final SparePartService sparePartService;
|
||||
|
||||
// ==================== 分类管理 ====================
|
||||
|
||||
@GetMapping("/categories")
|
||||
public ApiResponse<List<SparePartCategory>> getCategories() {
|
||||
return ApiResponse.success(sparePartService.getCategories());
|
||||
}
|
||||
|
||||
@PostMapping("/categories")
|
||||
public ApiResponse<SparePartCategory> createCategory(@Valid @RequestBody SparePartCategory category) {
|
||||
return ApiResponse.success(sparePartService.createCategory(category));
|
||||
}
|
||||
|
||||
// ==================== 备件管理 ====================
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<SparePart>> getSpareParts(
|
||||
@RequestParam UUID projectId,
|
||||
@RequestParam(required = false) UUID categoryId) {
|
||||
List<SparePart> spareParts;
|
||||
if (categoryId != null) {
|
||||
spareParts = sparePartService.getSparePartsByCategory(categoryId);
|
||||
} else {
|
||||
spareParts = sparePartService.getSparePartsByProject(projectId);
|
||||
}
|
||||
return ApiResponse.success(spareParts);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<SparePart> getSparePart(@PathVariable UUID id) {
|
||||
return ApiResponse.success(sparePartService.getSparePartById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<SparePart> createSparePart(@Valid @RequestBody SparePart sparePart) {
|
||||
return ApiResponse.success(sparePartService.createSparePart(sparePart));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<SparePart> updateSparePart(@PathVariable UUID id, @Valid @RequestBody SparePart sparePart) {
|
||||
return ApiResponse.success(sparePartService.updateSparePart(id, sparePart));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteSparePart(@PathVariable UUID id) {
|
||||
sparePartService.deleteSparePart(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/low-stock")
|
||||
public ApiResponse<List<SparePart>> getLowStockParts(@RequestParam UUID projectId) {
|
||||
return ApiResponse.success(sparePartService.getLowStockParts(projectId));
|
||||
}
|
||||
|
||||
// ==================== 库存操作 ====================
|
||||
|
||||
@PostMapping("/in-stock")
|
||||
public ApiResponse<SparePartRecord> inStock(@RequestBody StockRequest request) {
|
||||
SparePartRecord record = sparePartService.inStock(
|
||||
request.getSparePartId(), request.getQuantity(), request.getRecordedBy(), request.getRemarks());
|
||||
return ApiResponse.success(record);
|
||||
}
|
||||
|
||||
@PostMapping("/out-stock")
|
||||
public ApiResponse<SparePartRecord> outStock(@RequestBody OutStockRequest request) {
|
||||
SparePartRecord record = sparePartService.outStock(
|
||||
request.getSparePartId(), request.getQuantity(),
|
||||
request.getRelatedOrderId(), request.getRecordedBy(), request.getRemarks());
|
||||
return ApiResponse.success(record);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/records")
|
||||
public ApiResponse<List<SparePartRecord>> getSparePartRecords(@PathVariable UUID id) {
|
||||
return ApiResponse.success(sparePartService.getSparePartRecords(id));
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class StockRequest {
|
||||
private UUID sparePartId;
|
||||
private Integer quantity;
|
||||
private UUID recordedBy;
|
||||
private String remarks;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OutStockRequest {
|
||||
private UUID sparePartId;
|
||||
private Integer quantity;
|
||||
private UUID relatedOrderId;
|
||||
private UUID recordedBy;
|
||||
private String remarks;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.ether.pms.mdm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ops_equipment_failure_history", indexes = {
|
||||
@Index(name = "idx_failure_equipment", columnList = "equipment_id"),
|
||||
@Index(name = "idx_failure_time", columnList = "failure_time"),
|
||||
@Index(name = "idx_failure_project", columnList = "project_id")
|
||||
})
|
||||
@Data
|
||||
public class EquipmentFailureHistory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "project_id", nullable = false)
|
||||
private UUID projectId;
|
||||
|
||||
@Column(name = "equipment_id", nullable = false)
|
||||
private UUID equipmentId;
|
||||
|
||||
@Column(name = "equipment_code", length = 50)
|
||||
private String equipmentCode;
|
||||
|
||||
@Column(name = "equipment_name", length = 100)
|
||||
private String equipmentName;
|
||||
|
||||
@Column(name = "failure_time", nullable = false)
|
||||
private LocalDateTime failureTime;
|
||||
|
||||
@Column(name = "failure_type", length = 50)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private FailureType failureType;
|
||||
|
||||
@Column(name = "failure_level", length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private FailureLevel failureLevel;
|
||||
|
||||
@Column(name = "failure_reason", columnDefinition = "TEXT")
|
||||
private String failureReason;
|
||||
|
||||
@Column(name = "failure_description", columnDefinition = "TEXT")
|
||||
private String failureDescription;
|
||||
|
||||
@Column(name = "repair_start_time")
|
||||
private LocalDateTime repairStartTime;
|
||||
|
||||
@Column(name = "repair_end_time")
|
||||
private LocalDateTime repairEndTime;
|
||||
|
||||
@Column(name = "repair_duration_hours", precision = 10, scale = 2)
|
||||
private Double repairDurationHours;
|
||||
|
||||
@Column(name = "repair_person", length = 100)
|
||||
private String repairPerson;
|
||||
|
||||
@Column(name = "repair_result", length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private RepairResult repairResult;
|
||||
|
||||
@Column(name = "downtime_hours", precision = 10, scale = 2)
|
||||
private Double downtimeHours;
|
||||
|
||||
@Column(name = "maintenance_cost", precision = 12, scale = 2)
|
||||
private java.math.BigDecimal maintenanceCost;
|
||||
|
||||
@Column(name = "is_scheduled", nullable = false)
|
||||
private Boolean isScheduled = false;
|
||||
|
||||
@Column(name = "related_task_id")
|
||||
private UUID relatedTaskId;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column(name = "created_by")
|
||||
private UUID createdBy;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
if (isScheduled == null) {
|
||||
isScheduled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public enum FailureType {
|
||||
SUDDEN("突发故障"),
|
||||
SCHEDULED("计划停机"),
|
||||
WARNING("预警"),
|
||||
OTHER("其他");
|
||||
|
||||
private final String desc;
|
||||
|
||||
FailureType(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FailureLevel {
|
||||
LOW("轻微"),
|
||||
MEDIUM("一般"),
|
||||
HIGH("严重"),
|
||||
CRITICAL("危急");
|
||||
|
||||
private final String desc;
|
||||
|
||||
FailureLevel(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
public enum RepairResult {
|
||||
FIXED("已修复"),
|
||||
PARTIAL_FIXED("部分修复"),
|
||||
REPLACED("更换新设备"),
|
||||
PENDING("待处理");
|
||||
|
||||
private final String desc;
|
||||
|
||||
RepairResult(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
package com.ether.pms.mdm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ops_equipment_health_score", indexes = {
|
||||
@Index(name = "idx_health_equipment", columnList = "equipment_id"),
|
||||
@Index(name = "idx_health_calc_time", columnList = "calculated_at"),
|
||||
@Index(name = "idx_health_project", columnList = "project_id")
|
||||
})
|
||||
@Data
|
||||
public class EquipmentHealthScore {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "project_id", nullable = false)
|
||||
private UUID projectId;
|
||||
|
||||
@Column(name = "equipment_id", nullable = false)
|
||||
private UUID equipmentId;
|
||||
|
||||
@Column(name = "equipment_code", length = 50)
|
||||
private String equipmentCode;
|
||||
|
||||
@Column(name = "equipment_name", length = 100)
|
||||
private String equipmentName;
|
||||
|
||||
@Column(name = "health_score", nullable = false, precision = 5, scale = 2)
|
||||
private BigDecimal healthScore;
|
||||
|
||||
@Column(name = "failure_deduction", precision = 5, scale = 2)
|
||||
private BigDecimal failureDeduction;
|
||||
|
||||
@Column(name = "maintenance_deduction", precision = 5, scale = 2)
|
||||
private BigDecimal maintenanceDeduction;
|
||||
|
||||
@Column(name = "age_deduction", precision = 5, scale = 2)
|
||||
private BigDecimal ageDeduction;
|
||||
|
||||
@Column(name = "failure_count_30d")
|
||||
private Integer failureCount30d;
|
||||
|
||||
@Column(name = "maintenance_completion_rate", precision = 5, scale = 4)
|
||||
private BigDecimal maintenanceCompletionRate;
|
||||
|
||||
@Column(name = "equipment_age_years", precision = 10, scale = 2)
|
||||
private BigDecimal equipmentAgeYears;
|
||||
|
||||
@Column(name = "operation_hours_30d", precision = 12, scale = 2)
|
||||
private BigDecimal operationHours30d;
|
||||
|
||||
@Column(name = "mtbf_hours", precision = 12, scale = 2)
|
||||
private BigDecimal mtbfHours;
|
||||
|
||||
@Column(name = "mttr_hours", precision = 12, scale = 2)
|
||||
private BigDecimal mttrHours;
|
||||
|
||||
@Column(name = "health_level", length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HealthLevel healthLevel;
|
||||
|
||||
@Column(name = "calculated_at", nullable = false)
|
||||
private LocalDateTime calculatedAt;
|
||||
|
||||
@Column(name = "calculation_period_days")
|
||||
private Integer calculationPeriodDays;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
if (calculatedAt == null) {
|
||||
calculatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public enum HealthLevel {
|
||||
EXCELLENT("优秀", 90, 100),
|
||||
GOOD("良好", 75, 90),
|
||||
FAIR("一般", 60, 75),
|
||||
POOR("较差", 40, 60),
|
||||
CRITICAL("危急", 0, 40);
|
||||
|
||||
private final String desc;
|
||||
private final int minScore;
|
||||
private final int maxScore;
|
||||
|
||||
HealthLevel(String desc, int minScore, int maxScore) {
|
||||
this.desc = desc;
|
||||
this.minScore = minScore;
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public int getMinScore() {
|
||||
return minScore;
|
||||
}
|
||||
|
||||
public int getMaxScore() {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
public static HealthLevel fromScore(BigDecimal score) {
|
||||
if (score == null) {
|
||||
return CRITICAL;
|
||||
}
|
||||
double s = score.doubleValue();
|
||||
for (HealthLevel level : values()) {
|
||||
if (s >= level.minScore && s < level.maxScore) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return CRITICAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.ether.pms.mdm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ops_inspection_template")
|
||||
@Data
|
||||
public class InspectionTemplate {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "project_id", nullable = false)
|
||||
private UUID projectId;
|
||||
|
||||
@Column(name = "template_code", nullable = false, unique = true)
|
||||
private String templateCode;
|
||||
|
||||
@Column(name = "template_name", nullable = false)
|
||||
private String templateName;
|
||||
|
||||
@Column(name = "equipment_type", nullable = false)
|
||||
private String equipmentType;
|
||||
|
||||
@Column(name = "inspection_items", columnDefinition = "TEXT")
|
||||
private String inspectionItems;
|
||||
|
||||
@Column(name = "estimated_duration")
|
||||
private Integer estimatedDuration;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Status status = Status.ACTIVE;
|
||||
|
||||
public enum Status {
|
||||
ACTIVE, INACTIVE
|
||||
}
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer version = 1;
|
||||
|
||||
@Column(name = "created_by")
|
||||
private String createdBy;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.ether.pms.mdm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ops_spare_part")
|
||||
@Data
|
||||
public class SparePart {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "project_id", nullable = false)
|
||||
private UUID projectId;
|
||||
|
||||
@Column(name = "spare_part_code", nullable = false, unique = true)
|
||||
private String sparePartCode;
|
||||
|
||||
@Column(name = "spare_part_name", nullable = false)
|
||||
private String sparePartName;
|
||||
|
||||
@Column(name = "category_id")
|
||||
private UUID categoryId;
|
||||
|
||||
@Column
|
||||
private String specification;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String unit;
|
||||
|
||||
@Column(name = "safe_stock")
|
||||
private Integer safeStock;
|
||||
|
||||
@Column(name = "current_stock")
|
||||
private Integer currentStock;
|
||||
|
||||
@Column(name = "unit_price", precision = 10, scale = 2)
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
@Column
|
||||
private String supplier;
|
||||
|
||||
@Column(name = "supplier_contact")
|
||||
private String supplierContact;
|
||||
|
||||
@Column
|
||||
private String location;
|
||||
|
||||
@Column
|
||||
private String remarks;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Status status = Status.ACTIVE;
|
||||
|
||||
public enum Status {
|
||||
ACTIVE, INACTIVE
|
||||
}
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
if (currentStock == null) currentStock = 0;
|
||||
if (safeStock == null) safeStock = 0;
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.ether.pms.mdm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ops_spare_part_category")
|
||||
@Data
|
||||
public class SparePartCategory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "parent_id")
|
||||
private UUID parentId;
|
||||
|
||||
@Column(name = "category_code", nullable = false, unique = true)
|
||||
private String categoryCode;
|
||||
|
||||
@Column(name = "category_name", nullable = false)
|
||||
private String categoryName;
|
||||
|
||||
@Column
|
||||
private String description;
|
||||
|
||||
@Column(name = "sort_order")
|
||||
private Integer sortOrder;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
createdAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.ether.pms.mdm.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ops_spare_part_record")
|
||||
@Data
|
||||
public class SparePartRecord {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "record_code", nullable = false, unique = true)
|
||||
private String recordCode;
|
||||
|
||||
@Column(name = "record_type", nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private RecordType recordType;
|
||||
|
||||
public enum RecordType {
|
||||
IN("入库"),
|
||||
OUT("出库"),
|
||||
CHECK("盘点"),
|
||||
ADJUST("调整");
|
||||
|
||||
private final String desc;
|
||||
RecordType(String desc) { this.desc = desc; }
|
||||
public String getDesc() { return desc; }
|
||||
}
|
||||
|
||||
@Column(name = "spare_part_id", nullable = false)
|
||||
private UUID sparePartId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer quantity;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer balance;
|
||||
|
||||
@Column(name = "related_order_id")
|
||||
private UUID relatedOrderId;
|
||||
|
||||
@Column(name = "recorded_by")
|
||||
private UUID recordedBy;
|
||||
|
||||
@Column(name = "record_date")
|
||||
private LocalDateTime recordDate;
|
||||
|
||||
@Column
|
||||
private String remarks;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
if (recordDate == null) {
|
||||
recordDate = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.EnergyConsumption;
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface EnergyConsumptionRepository extends JpaRepository<EnergyConsumption, UUID> {
|
||||
List<EnergyConsumption> findByMeterIdOrderByConsumptionDateDesc(UUID meterId);
|
||||
|
||||
List<EnergyConsumption> findByMeterIdAndConsumptionDateBetween(UUID meterId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
@Query("SELECT SUM(e.consumption) FROM EnergyConsumption e WHERE e.meterId = :meterId AND e.consumptionDate BETWEEN :startDate AND :endDate")
|
||||
BigDecimal sumConsumptionByMeterIdAndDateRange(@Param("meterId") UUID meterId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
|
||||
|
||||
@Query("SELECT SUM(e.consumption) FROM EnergyConsumption e WHERE e.projectId = :projectId AND e.energyType = :energyType AND e.consumptionDate BETWEEN :startDate AND :endDate")
|
||||
BigDecimal sumConsumptionByProjectAndTypeAndDateRange(@Param("projectId") UUID projectId, @Param("energyType") EnergyMeter.EnergyType energyType, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
|
||||
|
||||
Optional<EnergyConsumption> findTopByMeterIdOrderByConsumptionDateDesc(UUID meterId);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface EnergyMeterRepository extends JpaRepository<EnergyMeter, UUID> {
|
||||
List<EnergyMeter> findByProjectIdAndStatus(UUID projectId, EnergyMeter.Status status);
|
||||
List<EnergyMeter> findByProjectIdAndEnergyType(UUID projectId, EnergyMeter.EnergyType energyType);
|
||||
Optional<EnergyMeter> findByMeterCode(String meterCode);
|
||||
boolean existsByMeterCode(String meterCode);
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.EquipmentFailureHistory;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface EquipmentFailureHistoryRepository extends JpaRepository<EquipmentFailureHistory, UUID> {
|
||||
|
||||
List<EquipmentFailureHistory> findByEquipmentIdOrderByFailureTimeDesc(UUID equipmentId);
|
||||
|
||||
List<EquipmentFailureHistory> findByProjectIdAndFailureTimeBetweenOrderByFailureTimeDesc(
|
||||
UUID projectId, LocalDateTime startTime, LocalDateTime endTime);
|
||||
|
||||
@Query("SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since ORDER BY f.failureTime DESC")
|
||||
List<EquipmentFailureHistory> findByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
|
||||
|
||||
@Query("SELECT COUNT(f) FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since")
|
||||
long countByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
|
||||
|
||||
@Query("SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.repairEndTime IS NOT NULL AND f.failureTime >= :since ORDER BY f.failureTime DESC")
|
||||
List<EquipmentFailureHistory> findRepairedFailuresByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.EquipmentHealthScore;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface EquipmentHealthScoreRepository extends JpaRepository<EquipmentHealthScore, UUID> {
|
||||
|
||||
List<EquipmentHealthScore> findByEquipmentIdOrderByCalculatedAtDesc(UUID equipmentId);
|
||||
|
||||
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId ORDER BY h.calculatedAt DESC LIMIT 1")
|
||||
Optional<EquipmentHealthScore> findLatestByEquipmentId(@Param("equipmentId") UUID equipmentId);
|
||||
|
||||
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId AND h.calculatedAt >= :since ORDER BY h.calculatedAt DESC")
|
||||
List<EquipmentHealthScore> findByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
|
||||
|
||||
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId ORDER BY h.calculatedAt DESC")
|
||||
List<EquipmentHealthScore> findByProjectIdOrderByCalculatedAtDesc(@Param("projectId") UUID projectId);
|
||||
|
||||
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId AND h.healthLevel IN :levels ORDER BY h.healthScore ASC")
|
||||
List<EquipmentHealthScore> findByProjectIdAndHealthLevelIn(@Param("projectId") UUID projectId, @Param("levels") List<EquipmentHealthScore.HealthLevel> levels);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.InspectionTemplate;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface InspectionTemplateRepository extends JpaRepository<InspectionTemplate, UUID> {
|
||||
List<InspectionTemplate> findByProjectId(UUID projectId);
|
||||
List<InspectionTemplate> findByEquipmentType(String equipmentType);
|
||||
Optional<InspectionTemplate> findByTemplateCode(String templateCode);
|
||||
boolean existsByTemplateCode(String templateCode);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.SparePartCategory;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface SparePartCategoryRepository extends JpaRepository<SparePartCategory, UUID> {
|
||||
List<SparePartCategory> findByParentIdIsNull();
|
||||
List<SparePartCategory> findByParentId(UUID parentId);
|
||||
Optional<SparePartCategory> findByCategoryCode(String categoryCode);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.SparePartRecord;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface SparePartRecordRepository extends JpaRepository<SparePartRecord, UUID> {
|
||||
List<SparePartRecord> findBySparePartIdOrderByRecordDateDesc(UUID sparePartId);
|
||||
Optional<SparePartRecord> findTopBySparePartIdOrderByRecordDateDesc(UUID sparePartId);
|
||||
List<SparePartRecord> findByRelatedOrderId(UUID relatedOrderId);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.ether.pms.mdm.repository;
|
||||
|
||||
import com.ether.pms.mdm.entity.SparePart;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface SparePartRepository extends JpaRepository<SparePart, UUID> {
|
||||
List<SparePart> findByProjectIdAndStatus(UUID projectId, SparePart.Status status);
|
||||
List<SparePart> findByCategoryId(UUID categoryId);
|
||||
Optional<SparePart> findBySparePartCode(String sparePartCode);
|
||||
boolean existsBySparePartCode(String sparePartCode);
|
||||
|
||||
@Query("SELECT s FROM SparePart s WHERE s.projectId = :projectId AND s.currentStock <= s.safeStock")
|
||||
List<SparePart> findLowStockParts(@Param("projectId") UUID projectId);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.ether.pms.mdm.service;
|
||||
|
||||
import com.ether.pms.mdm.entity.EnergyConsumption;
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface EnergyConsumptionService {
|
||||
EnergyConsumption recordConsumption(UUID meterId, BigDecimal currentReading, UUID recordedBy);
|
||||
List<EnergyConsumption> getConsumptionByMeter(UUID meterId);
|
||||
List<EnergyConsumption> getConsumptionByMeterAndDateRange(UUID meterId, LocalDate startDate, LocalDate endDate);
|
||||
Map<EnergyMeter.EnergyType, BigDecimal> getConsumptionByType(UUID projectId, LocalDate month);
|
||||
BigDecimal getUnitConsumption(UUID projectId, LocalDate month);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.ether.pms.mdm.service;
|
||||
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface EnergyMeterService {
|
||||
EnergyMeter createMeter(EnergyMeter meter);
|
||||
EnergyMeter updateMeter(UUID id, EnergyMeter meter);
|
||||
void deleteMeter(UUID id);
|
||||
EnergyMeter getMeterById(UUID id);
|
||||
List<EnergyMeter> getMetersByProject(UUID projectId);
|
||||
List<EnergyMeter> getMetersByType(UUID projectId, EnergyMeter.EnergyType energyType);
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.ether.pms.mdm.service;
|
||||
|
||||
import com.ether.pms.mdm.entity.EquipmentFailureHistory;
|
||||
import com.ether.pms.mdm.entity.EquipmentHealthScore;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface EquipmentHealthService {
|
||||
|
||||
/**
|
||||
* 计算设备健康度
|
||||
* 基础分 = 100
|
||||
* 故障率扣分 = 故障次数 × 5(近30天每故障扣5分)
|
||||
* 维保扣分 = (1 - 维保完成率) × 20
|
||||
* 年龄扣分 = 投入使用年限 × 2(最高扣10分)
|
||||
* 健康度 = 基础分 - 故障率扣分 - 维保扣分 - 年龄扣分
|
||||
*/
|
||||
EquipmentHealthScore calculateHealthScore(UUID equipmentId);
|
||||
|
||||
/**
|
||||
* 计算平均故障间隔时间 (MTBF)
|
||||
* MTBF = 运行时间 / 故障次数
|
||||
*/
|
||||
BigDecimal calculateMTBF(UUID equipmentId, Integer days);
|
||||
|
||||
/**
|
||||
* 计算平均修复时间 (MTTR)
|
||||
* MTTR = 总修复时间 / 修复次数
|
||||
*/
|
||||
BigDecimal calculateMTTR(UUID equipmentId, Integer days);
|
||||
|
||||
/**
|
||||
* 记录故障
|
||||
*/
|
||||
EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure);
|
||||
|
||||
/**
|
||||
* 获取设备健康度历史
|
||||
*/
|
||||
List<EquipmentHealthScore> getHealthHistory(UUID equipmentId);
|
||||
|
||||
/**
|
||||
* 获取设备最新健康度
|
||||
*/
|
||||
EquipmentHealthScore getLatestHealthScore(UUID equipmentId);
|
||||
|
||||
/**
|
||||
* 获取设备的故障历史
|
||||
*/
|
||||
List<EquipmentFailureHistory> getFailureHistory(UUID equipmentId);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.ether.pms.mdm.service;
|
||||
|
||||
import com.ether.pms.mdm.entity.InspectionTemplate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface InspectionTemplateService {
|
||||
InspectionTemplate createTemplate(InspectionTemplate template);
|
||||
InspectionTemplate updateTemplate(UUID id, InspectionTemplate template);
|
||||
InspectionTemplate copyTemplate(UUID templateId, String newName);
|
||||
List<InspectionTemplate> getTemplatesByType(String equipmentType);
|
||||
List<InspectionTemplate> getTemplatesByProject(UUID projectId);
|
||||
InspectionTemplate getTemplateById(UUID id);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.ether.pms.mdm.service;
|
||||
|
||||
import com.ether.pms.mdm.entity.SparePart;
|
||||
import com.ether.pms.mdm.entity.SparePartCategory;
|
||||
import com.ether.pms.mdm.entity.SparePartRecord;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface SparePartService {
|
||||
// 分类管理
|
||||
List<SparePartCategory> getCategories();
|
||||
SparePartCategory createCategory(SparePartCategory category);
|
||||
|
||||
// 备件管理
|
||||
SparePart createSparePart(SparePart sparePart);
|
||||
SparePart updateSparePart(UUID id, SparePart sparePart);
|
||||
void deleteSparePart(UUID id);
|
||||
SparePart getSparePartById(UUID id);
|
||||
List<SparePart> getSparePartsByProject(UUID projectId);
|
||||
List<SparePart> getSparePartsByCategory(UUID categoryId);
|
||||
List<SparePart> getLowStockParts(UUID projectId);
|
||||
|
||||
// 库存管理
|
||||
SparePartRecord inStock(UUID sparePartId, Integer quantity, UUID recordedBy, String remarks);
|
||||
SparePartRecord outStock(UUID sparePartId, Integer quantity, UUID relatedOrderId, UUID recordedBy, String remarks);
|
||||
List<SparePartRecord> getSparePartRecords(UUID sparePartId);
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package com.ether.pms.mdm.service.impl;
|
||||
|
||||
import com.ether.pms.common.BusinessException;
|
||||
import com.ether.pms.mdm.entity.EnergyConsumption;
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import com.ether.pms.mdm.repository.EnergyConsumptionRepository;
|
||||
import com.ether.pms.mdm.repository.EnergyMeterRepository;
|
||||
import com.ether.pms.mdm.service.EnergyConsumptionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EnergyConsumptionServiceImpl implements EnergyConsumptionService {
|
||||
|
||||
private final EnergyConsumptionRepository energyConsumptionRepository;
|
||||
private final EnergyMeterRepository energyMeterRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public EnergyConsumption recordConsumption(UUID meterId, BigDecimal currentReading, UUID recordedBy) {
|
||||
// 获取电表信息
|
||||
EnergyMeter meter = energyMeterRepository.findById(meterId)
|
||||
.orElseThrow(() -> new BusinessException(6101, "能源仪表不存在"));
|
||||
|
||||
if (meter.getStatus() != EnergyMeter.Status.ACTIVE) {
|
||||
throw new BusinessException(6102, "只能对ACTIVE状态的仪表进行抄表");
|
||||
}
|
||||
|
||||
// 获取上一个抄表记录
|
||||
Optional<EnergyConsumption> lastConsumption = energyConsumptionRepository
|
||||
.findTopByMeterIdOrderByConsumptionDateDesc(meterId);
|
||||
|
||||
BigDecimal previousReading = BigDecimal.ZERO;
|
||||
if (lastConsumption.isPresent()) {
|
||||
previousReading = lastConsumption.get().getCurrentReading();
|
||||
}
|
||||
|
||||
// 计算消耗量
|
||||
BigDecimal consumption = currentReading.subtract(previousReading);
|
||||
if (consumption.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new BusinessException(6103, "当前读数不能小于上次读数");
|
||||
}
|
||||
|
||||
// 计算费用:consumption × unitPrice
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (meter.getUnitPrice() != null && consumption.compareTo(BigDecimal.ZERO) > 0) {
|
||||
amount = consumption.multiply(meter.getUnitPrice());
|
||||
}
|
||||
|
||||
// 创建能耗记录
|
||||
EnergyConsumption record = new EnergyConsumption();
|
||||
record.setProjectId(meter.getProjectId());
|
||||
record.setMeterId(meterId);
|
||||
record.setConsumptionDate(LocalDate.now());
|
||||
record.setPreviousReading(previousReading);
|
||||
record.setCurrentReading(currentReading);
|
||||
record.setConsumption(consumption);
|
||||
record.setAmount(amount);
|
||||
record.setRecordedBy(recordedBy);
|
||||
record.setRecordMethod(EnergyConsumption.RecordMethod.MANUAL);
|
||||
|
||||
return energyConsumptionRepository.save(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnergyConsumption> getConsumptionByMeter(UUID meterId) {
|
||||
return energyConsumptionRepository.findByMeterIdOrderByConsumptionDateDesc(meterId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnergyConsumption> getConsumptionByMeterAndDateRange(UUID meterId, LocalDate startDate, LocalDate endDate) {
|
||||
return energyConsumptionRepository.findByMeterIdAndConsumptionDateBetween(meterId, startDate, endDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<EnergyMeter.EnergyType, BigDecimal> getConsumptionByType(UUID projectId, LocalDate month) {
|
||||
LocalDate startDate = month.withDayOfMonth(1);
|
||||
LocalDate endDate = month.withDayOfMonth(month.lengthOfMonth());
|
||||
|
||||
Map<EnergyMeter.EnergyType, BigDecimal> result = new EnumMap<>(EnergyMeter.EnergyType.class);
|
||||
|
||||
// 初始化所有能耗类型为0
|
||||
for (EnergyMeter.EnergyType type : EnergyMeter.EnergyType.values()) {
|
||||
result.put(type, BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 由于 EnergyConsumption 没有 energyType 字段,按项目统计总消耗
|
||||
BigDecimal totalConsumption = energyConsumptionRepository
|
||||
.sumConsumptionByProjectAndDateRange(projectId, startDate, endDate);
|
||||
if (totalConsumption != null) {
|
||||
// 将总消耗分配给 LIGHTING 类型作为展示(实际业务中应按 meter.energyType 汇总)
|
||||
result.put(EnergyMeter.EnergyType.LIGHTING, totalConsumption);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getUnitConsumption(UUID projectId, LocalDate month) {
|
||||
LocalDate startDate = month.withDayOfMonth(1);
|
||||
LocalDate endDate = month.withDayOfMonth(month.lengthOfMonth());
|
||||
|
||||
// 获取该项目所有ACTIVE状态的电表
|
||||
List<EnergyMeter> meters = energyMeterRepository.findByProjectIdAndStatus(projectId, EnergyMeter.Status.ACTIVE);
|
||||
|
||||
BigDecimal totalConsumption = BigDecimal.ZERO;
|
||||
for (EnergyMeter meter : meters) {
|
||||
BigDecimal meterConsumption = energyConsumptionRepository
|
||||
.sumConsumptionByMeterIdAndDateRange(meter.getId(), startDate, endDate);
|
||||
if (meterConsumption != null) {
|
||||
totalConsumption = totalConsumption.add(meterConsumption);
|
||||
}
|
||||
}
|
||||
|
||||
return totalConsumption;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.ether.pms.mdm.service.impl;
|
||||
|
||||
import com.ether.pms.common.BusinessException;
|
||||
import com.ether.pms.mdm.entity.EnergyMeter;
|
||||
import com.ether.pms.mdm.repository.EnergyMeterRepository;
|
||||
import com.ether.pms.mdm.service.EnergyMeterService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EnergyMeterServiceImpl implements EnergyMeterService {
|
||||
|
||||
private final EnergyMeterRepository energyMeterRepository;
|
||||
private static final DateTimeFormatter CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
@Override
|
||||
public EnergyMeter createMeter(EnergyMeter meter) {
|
||||
// 生成电表编码:EM + 时间戳
|
||||
String meterCode = generateMeterCode();
|
||||
meter.setMeterCode(meterCode);
|
||||
|
||||
// 设置默认状态为ACTIVE
|
||||
if (meter.getStatus() == null) {
|
||||
meter.setStatus(EnergyMeter.Status.ACTIVE);
|
||||
}
|
||||
|
||||
return energyMeterRepository.save(meter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public EnergyMeter updateMeter(UUID id, EnergyMeter meter) {
|
||||
EnergyMeter existingMeter = getMeterById(id);
|
||||
|
||||
// 更新非空字段
|
||||
if (meter.getMeterName() != null) {
|
||||
existingMeter.setMeterName(meter.getMeterName());
|
||||
}
|
||||
if (meter.getEnergyType() != null) {
|
||||
existingMeter.setEnergyType(meter.getEnergyType());
|
||||
}
|
||||
if (meter.getSpaceNodeId() != null) {
|
||||
existingMeter.setSpaceNodeId(meter.getSpaceNodeId());
|
||||
}
|
||||
if (meter.getInstallationLocation() != null) {
|
||||
existingMeter.setInstallationLocation(meter.getInstallationLocation());
|
||||
}
|
||||
if (meter.getRatedCapacity() != null) {
|
||||
existingMeter.setRatedCapacity(meter.getRatedCapacity());
|
||||
}
|
||||
if (meter.getUnitPrice() != null) {
|
||||
existingMeter.setUnitPrice(meter.getUnitPrice());
|
||||
}
|
||||
if (meter.getStatus() != null) {
|
||||
existingMeter.setStatus(meter.getStatus());
|
||||
}
|
||||
|
||||
return energyMeterRepository.save(existingMeter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteMeter(UUID id) {
|
||||
EnergyMeter meter = getMeterById(id);
|
||||
// 软删除:将状态设置为INACTIVE
|
||||
meter.setStatus(EnergyMeter.Status.INACTIVE);
|
||||
energyMeterRepository.save(meter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnergyMeter getMeterById(UUID id) {
|
||||
return energyMeterRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(6101, "能源仪表不存在"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnergyMeter> getMetersByProject(UUID projectId) {
|
||||
return energyMeterRepository.findByProjectIdAndStatus(projectId, EnergyMeter.Status.ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnergyMeter> getMetersByType(UUID projectId, EnergyMeter.EnergyType energyType) {
|
||||
return energyMeterRepository.findByProjectIdAndEnergyType(projectId, energyType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成电表编码:EM + yyyyMMddHHmmss
|
||||
*/
|
||||
private String generateMeterCode() {
|
||||
String timestamp = LocalDateTime.now().format(CODE_FORMATTER);
|
||||
String meterCode = "EM" + timestamp;
|
||||
|
||||
// 确保编码唯一
|
||||
int suffix = 1;
|
||||
while (energyMeterRepository.existsByMeterCode(meterCode)) {
|
||||
meterCode = "EM" + timestamp + "_" + suffix;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
return meterCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
package com.ether.pms.mdm.service.impl;
|
||||
|
||||
import com.ether.pms.common.BusinessException;
|
||||
import com.ether.pms.mdm.entity.EquipmentFailureHistory;
|
||||
import com.ether.pms.mdm.entity.EquipmentHealthScore;
|
||||
import com.ether.pms.mdm.entity.MaintenanceTask;
|
||||
import com.ether.pms.mdm.entity.SpaceNode;
|
||||
import com.ether.pms.mdm.repository.EquipmentFailureHistoryRepository;
|
||||
import com.ether.pms.mdm.repository.EquipmentHealthScoreRepository;
|
||||
import com.ether.pms.mdm.repository.MaintenanceTaskRepository;
|
||||
import com.ether.pms.mdm.repository.SpaceNodeRepository;
|
||||
import com.ether.pms.mdm.service.EquipmentHealthService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EquipmentHealthServiceImpl implements EquipmentHealthService {
|
||||
|
||||
private final EquipmentFailureHistoryRepository failureHistoryRepository;
|
||||
private final EquipmentHealthScoreRepository healthScoreRepository;
|
||||
private final SpaceNodeRepository spaceNodeRepository;
|
||||
private final MaintenanceTaskRepository maintenanceTaskRepository;
|
||||
|
||||
private static final BigDecimal BASE_SCORE = new BigDecimal("100");
|
||||
private static final BigDecimal FAILURE_DEDUCTION_PER_COUNT = new BigDecimal("5");
|
||||
private static final BigDecimal MAINTENANCE_DEDUCTION_FACTOR = new BigDecimal("20");
|
||||
private static final BigDecimal AGE_DEDUCTION_PER_YEAR = new BigDecimal("2");
|
||||
private static final BigDecimal MAX_AGE_DEDUCTION = new BigDecimal("10");
|
||||
private static final int DEFAULT_DAYS = 30;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public EquipmentHealthScore calculateHealthScore(UUID equipmentId) {
|
||||
SpaceNode equipment = spaceNodeRepository.findByIdAndIsDeletedFalse(equipmentId)
|
||||
.orElseThrow(() -> new BusinessException(6001, "设备不存在"));
|
||||
|
||||
if (!Boolean.TRUE.equals(equipment.getIsEquipment())) {
|
||||
throw new BusinessException(6002, "该空间节点不是设备");
|
||||
}
|
||||
|
||||
// 获取近30天故障次数
|
||||
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
|
||||
long failureCount30d = failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo);
|
||||
|
||||
// 计算故障率扣分
|
||||
BigDecimal failureDeduction = FAILURE_DEDUCTION_PER_COUNT.multiply(BigDecimal.valueOf(failureCount30d));
|
||||
|
||||
// 计算维保完成率
|
||||
List<MaintenanceTask> tasks = maintenanceTaskRepository.findByEquipmentIdAndStatusNot(equipmentId, MaintenanceTask.Status.CANCELLED);
|
||||
long totalTasks = tasks.size();
|
||||
long completedTasks = tasks.stream()
|
||||
.filter(t -> t.getStatus() == MaintenanceTask.Status.COMPLETED)
|
||||
.count();
|
||||
|
||||
BigDecimal maintenanceCompletionRate = BigDecimal.ONE;
|
||||
if (totalTasks > 0) {
|
||||
maintenanceCompletionRate = BigDecimal.valueOf(completedTasks)
|
||||
.divide(BigDecimal.valueOf(totalTasks), 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
BigDecimal maintenanceDeduction = BigDecimal.ONE.subtract(maintenanceCompletionRate)
|
||||
.multiply(MAINTENANCE_DEDUCTION_FACTOR);
|
||||
|
||||
// 计算年龄扣分
|
||||
BigDecimal equipmentAgeYears = calculateEquipmentAge(equipment);
|
||||
BigDecimal ageDeduction = equipmentAgeYears.multiply(AGE_DEDUCTION_PER_YEAR);
|
||||
if (ageDeduction.compareTo(MAX_AGE_DEDUCTION) > 0) {
|
||||
ageDeduction = MAX_AGE_DEDUCTION;
|
||||
}
|
||||
|
||||
// 计算健康度
|
||||
BigDecimal healthScore = BASE_SCORE
|
||||
.subtract(failureDeduction)
|
||||
.subtract(maintenanceDeduction)
|
||||
.subtract(ageDeduction);
|
||||
|
||||
// 确保健康度不低于0
|
||||
if (healthScore.compareTo(BigDecimal.ZERO) < 0) {
|
||||
healthScore = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 计算MTBF和MTTR
|
||||
BigDecimal mtbf = calculateMTBF(equipmentId, DEFAULT_DAYS);
|
||||
BigDecimal mttr = calculateMTTR(equipmentId, DEFAULT_DAYS);
|
||||
|
||||
// 创建健康度记录
|
||||
EquipmentHealthScore health = new EquipmentHealthScore();
|
||||
health.setProjectId(equipment.getProjectId());
|
||||
health.setEquipmentId(equipmentId);
|
||||
health.setEquipmentCode(equipment.getCode());
|
||||
health.setEquipmentName(equipment.getName());
|
||||
health.setHealthScore(healthScore.setScale(2, RoundingMode.HALF_UP));
|
||||
health.setFailureDeduction(failureDeduction.setScale(2, RoundingMode.HALF_UP));
|
||||
health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP));
|
||||
health.setAgeDeduction(ageDeduction.setScale(2, RoundingMode.HALF_UP));
|
||||
health.setFailureCount30d((int) failureCount30d);
|
||||
health.setMaintenanceCompletionRate(maintenanceCompletionRate.setScale(4, RoundingMode.HALF_UP));
|
||||
health.setEquipmentAgeYears(equipmentAgeYears.setScale(2, RoundingMode.HALF_UP));
|
||||
health.setMtbfHours(mtbf != null ? mtbf : BigDecimal.ZERO);
|
||||
health.setMttrHours(mttr != null ? mttr : BigDecimal.ZERO);
|
||||
health.setHealthLevel(EquipmentHealthScore.HealthLevel.fromScore(healthScore));
|
||||
health.setCalculatedAt(LocalDateTime.now());
|
||||
health.setCalculationPeriodDays(DEFAULT_DAYS);
|
||||
|
||||
return healthScoreRepository.save(health);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal calculateMTBF(UUID equipmentId, Integer days) {
|
||||
if (days == null || days <= 0) {
|
||||
days = DEFAULT_DAYS;
|
||||
}
|
||||
|
||||
LocalDateTime since = LocalDateTime.now().minusDays(days);
|
||||
List<EquipmentFailureHistory> failures = failureHistoryRepository.findByEquipmentIdSince(equipmentId, since);
|
||||
|
||||
if (failures.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 计算运行时间(从第一个故障到最后一个故障,或者到现在)
|
||||
LocalDateTime firstFailure = failures.get(failures.size() - 1).getFailureTime();
|
||||
LocalDateTime lastFailure = failures.get(0).getFailureTime();
|
||||
|
||||
// 如果只有一个故障,MTBF = 总时间 / 故障次数 = 0(因为还在运行)
|
||||
// 这里我们用总时间跨度 / 故障次数
|
||||
long totalHours = ChronoUnit.HOURS.between(firstFailure, lastFailure);
|
||||
if (totalHours <= 0) {
|
||||
totalHours = days * 24L; // 使用默认期间
|
||||
}
|
||||
|
||||
// MTBF = 运行时间 / 故障次数
|
||||
BigDecimal mtbf = BigDecimal.valueOf(totalHours)
|
||||
.divide(BigDecimal.valueOf(failures.size()), 2, RoundingMode.HALF_UP);
|
||||
|
||||
return mtbf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal calculateMTTR(UUID equipmentId, Integer days) {
|
||||
if (days == null || days <= 0) {
|
||||
days = DEFAULT_DAYS;
|
||||
}
|
||||
|
||||
LocalDateTime since = LocalDateTime.now().minusDays(days);
|
||||
List<EquipmentFailureHistory> repairedFailures = failureHistoryRepository
|
||||
.findRepairedFailuresByEquipmentIdSince(equipmentId, since);
|
||||
|
||||
if (repairedFailures.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 计算总修复时间
|
||||
double totalRepairHours = repairedFailures.stream()
|
||||
.filter(f -> f.getRepairDurationHours() != null)
|
||||
.mapToDouble(EquipmentFailureHistory::getRepairDurationHours)
|
||||
.sum();
|
||||
|
||||
// MTTR = 总修复时间 / 修复次数
|
||||
BigDecimal mttr = BigDecimal.valueOf(totalRepairHours)
|
||||
.divide(BigDecimal.valueOf(repairedFailures.size()), 2, RoundingMode.HALF_UP);
|
||||
|
||||
return mttr;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) {
|
||||
// 校验设备存在
|
||||
SpaceNode equipment = spaceNodeRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId())
|
||||
.orElseThrow(() -> new BusinessException(6001, "设备不存在"));
|
||||
|
||||
if (!Boolean.TRUE.equals(equipment.getIsEquipment())) {
|
||||
throw new BusinessException(6002, "该空间节点不是设备");
|
||||
}
|
||||
|
||||
// 设置项目ID
|
||||
if (failure.getProjectId() == null) {
|
||||
failure.setProjectId(equipment.getProjectId());
|
||||
}
|
||||
|
||||
// 设置设备信息
|
||||
if (failure.getEquipmentCode() == null) {
|
||||
failure.setEquipmentCode(equipment.getCode());
|
||||
}
|
||||
if (failure.getEquipmentName() == null) {
|
||||
failure.setEquipmentName(equipment.getName());
|
||||
}
|
||||
|
||||
// 计算修复时长
|
||||
if (failure.getRepairStartTime() != null && failure.getRepairEndTime() != null) {
|
||||
long minutes = ChronoUnit.MINUTES.between(failure.getRepairStartTime(), failure.getRepairEndTime());
|
||||
double hours = minutes / 60.0;
|
||||
failure.setRepairDurationHours(hours);
|
||||
|
||||
// 计算停机时长(如果故障时间和修复开始时间不同)
|
||||
if (failure.getFailureTime() != null && failure.getRepairStartTime().isAfter(failure.getFailureTime())) {
|
||||
long downtimeMinutes = ChronoUnit.MINUTES.between(failure.getFailureTime(), failure.getRepairStartTime());
|
||||
failure.setDowntimeHours(downtimeMinutes / 60.0);
|
||||
}
|
||||
}
|
||||
|
||||
return failureHistoryRepository.save(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EquipmentHealthScore> getHealthHistory(UUID equipmentId) {
|
||||
return healthScoreRepository.findByEquipmentIdOrderByCalculatedAtDesc(equipmentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EquipmentHealthScore getLatestHealthScore(UUID equipmentId) {
|
||||
return healthScoreRepository.findLatestByEquipmentId(equipmentId)
|
||||
.orElseThrow(() -> new BusinessException(6003, "没有健康度记录"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EquipmentFailureHistory> getFailureHistory(UUID equipmentId) {
|
||||
return failureHistoryRepository.findByEquipmentIdOrderByFailureTimeDesc(equipmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算设备年龄(年)
|
||||
*/
|
||||
private BigDecimal calculateEquipmentAge(SpaceNode equipment) {
|
||||
LocalDate installationDate = equipment.getMaintenanceContractStart();
|
||||
if (installationDate == null) {
|
||||
// 如果没有安装日期,使用创建日期
|
||||
if (equipment.getCreatedAt() != null) {
|
||||
installationDate = equipment.getCreatedAt().toLocalDate();
|
||||
} else {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
Period period = Period.between(installationDate, LocalDate.now());
|
||||
int years = period.getYears();
|
||||
int months = period.getMonths();
|
||||
|
||||
return BigDecimal.valueOf(years).add(BigDecimal.valueOf(months).divide(BigDecimal.valueOf(12), 2, RoundingMode.HALF_UP));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.ether.pms.mdm.service.impl;
|
||||
|
||||
import com.ether.pms.common.BusinessException;
|
||||
import com.ether.pms.common.ErrorCode;
|
||||
import com.ether.pms.mdm.entity.InspectionTemplate;
|
||||
import com.ether.pms.mdm.repository.InspectionTemplateRepository;
|
||||
import com.ether.pms.mdm.service.InspectionTemplateService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InspectionTemplateServiceImpl implements InspectionTemplateService {
|
||||
|
||||
private final InspectionTemplateRepository inspectionTemplateRepository;
|
||||
private static final DateTimeFormatter CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public InspectionTemplate createTemplate(InspectionTemplate template) {
|
||||
String templateCode = generateTemplateCode();
|
||||
template.setTemplateCode(templateCode);
|
||||
|
||||
if (template.getStatus() == null) {
|
||||
template.setStatus(InspectionTemplate.Status.ACTIVE);
|
||||
}
|
||||
|
||||
return inspectionTemplateRepository.save(template);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public InspectionTemplate updateTemplate(UUID id, InspectionTemplate template) {
|
||||
InspectionTemplate existing = getTemplateById(id);
|
||||
|
||||
if (existing.getStatus() != InspectionTemplate.Status.ACTIVE) {
|
||||
throw new BusinessException(6001, "只有ACTIVE状态的模板才能更新");
|
||||
}
|
||||
|
||||
if (template.getTemplateName() != null) {
|
||||
existing.setTemplateName(template.getTemplateName());
|
||||
}
|
||||
if (template.getEquipmentType() != null) {
|
||||
existing.setEquipmentType(template.getEquipmentType());
|
||||
}
|
||||
if (template.getInspectionItems() != null) {
|
||||
existing.setInspectionItems(template.getInspectionItems());
|
||||
}
|
||||
if (template.getEstimatedDuration() != null) {
|
||||
existing.setEstimatedDuration(template.getEstimatedDuration());
|
||||
}
|
||||
if (template.getStatus() != null) {
|
||||
existing.setStatus(template.getStatus());
|
||||
}
|
||||
|
||||
return inspectionTemplateRepository.save(existing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public InspectionTemplate copyTemplate(UUID templateId, String newName) {
|
||||
InspectionTemplate original = getTemplateById(templateId);
|
||||
|
||||
InspectionTemplate copy = new InspectionTemplate();
|
||||
copy.setProjectId(original.getProjectId());
|
||||
copy.setTemplateName(newName);
|
||||
copy.setEquipmentType(original.getEquipmentType());
|
||||
copy.setInspectionItems(original.getInspectionItems());
|
||||
copy.setEstimatedDuration(original.getEstimatedDuration());
|
||||
copy.setCreatedBy(original.getCreatedBy());
|
||||
copy.setVersion(original.getVersion() + 1);
|
||||
copy.setStatus(InspectionTemplate.Status.ACTIVE);
|
||||
|
||||
String newTemplateCode = generateTemplateCode();
|
||||
copy.setTemplateCode(newTemplateCode);
|
||||
|
||||
return inspectionTemplateRepository.save(copy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InspectionTemplate> getTemplatesByType(String equipmentType) {
|
||||
return inspectionTemplateRepository.findByEquipmentType(equipmentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InspectionTemplate> getTemplatesByProject(UUID projectId) {
|
||||
return inspectionTemplateRepository.findByProjectId(projectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InspectionTemplate getTemplateById(UUID id) {
|
||||
return inspectionTemplateRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "点检模板不存在"));
|
||||
}
|
||||
|
||||
private String generateTemplateCode() {
|
||||
String timestamp = LocalDateTime.now().format(CODE_FORMATTER);
|
||||
String templateCode = "IT" + timestamp;
|
||||
|
||||
int suffix = 1;
|
||||
while (inspectionTemplateRepository.existsByTemplateCode(templateCode)) {
|
||||
templateCode = "IT" + timestamp + "_" + suffix;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
return templateCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package com.ether.pms.mdm.service.impl;
|
||||
|
||||
import com.ether.pms.common.BusinessException;
|
||||
import com.ether.pms.mdm.entity.SparePart;
|
||||
import com.ether.pms.mdm.entity.SparePartCategory;
|
||||
import com.ether.pms.mdm.entity.SparePartRecord;
|
||||
import com.ether.pms.mdm.repository.SparePartCategoryRepository;
|
||||
import com.ether.pms.mdm.repository.SparePartRecordRepository;
|
||||
import com.ether.pms.mdm.repository.SparePartRepository;
|
||||
import com.ether.pms.mdm.service.SparePartService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SparePartServiceImpl implements SparePartService {
|
||||
|
||||
private final SparePartRepository sparePartRepository;
|
||||
private final SparePartCategoryRepository sparePartCategoryRepository;
|
||||
private final SparePartRecordRepository sparePartRecordRepository;
|
||||
|
||||
private static final DateTimeFormatter CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
// ==================== 分类管理 ====================
|
||||
|
||||
@Override
|
||||
public List<SparePartCategory> getCategories() {
|
||||
return sparePartCategoryRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SparePartCategory createCategory(SparePartCategory category) {
|
||||
// 生成分类编码 CC + 时间戳
|
||||
String categoryCode = generateCategoryCode();
|
||||
category.setCategoryCode(categoryCode);
|
||||
return sparePartCategoryRepository.save(category);
|
||||
}
|
||||
|
||||
// ==================== 备件管理 ====================
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SparePart createSparePart(SparePart sparePart) {
|
||||
// 生成备件编码 SP + 时间戳
|
||||
String sparePartCode = generateSparePartCode();
|
||||
sparePart.setSparePartCode(sparePartCode);
|
||||
|
||||
// 设置默认状态为ACTIVE
|
||||
if (sparePart.getStatus() == null) {
|
||||
sparePart.setStatus(SparePart.Status.ACTIVE);
|
||||
}
|
||||
|
||||
// 初始化库存为0
|
||||
if (sparePart.getCurrentStock() == null) {
|
||||
sparePart.setCurrentStock(0);
|
||||
}
|
||||
|
||||
return sparePartRepository.save(sparePart);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SparePart updateSparePart(UUID id, SparePart sparePart) {
|
||||
SparePart existing = getSparePartById(id);
|
||||
|
||||
if (sparePart.getSparePartName() != null) {
|
||||
existing.setSparePartName(sparePart.getSparePartName());
|
||||
}
|
||||
if (sparePart.getCategoryId() != null) {
|
||||
existing.setCategoryId(sparePart.getCategoryId());
|
||||
}
|
||||
if (sparePart.getSpecification() != null) {
|
||||
existing.setSpecification(sparePart.getSpecification());
|
||||
}
|
||||
if (sparePart.getUnit() != null) {
|
||||
existing.setUnit(sparePart.getUnit());
|
||||
}
|
||||
if (sparePart.getSafeStock() != null) {
|
||||
existing.setSafeStock(sparePart.getSafeStock());
|
||||
}
|
||||
if (sparePart.getUnitPrice() != null) {
|
||||
existing.setUnitPrice(sparePart.getUnitPrice());
|
||||
}
|
||||
if (sparePart.getSupplier() != null) {
|
||||
existing.setSupplier(sparePart.getSupplier());
|
||||
}
|
||||
if (sparePart.getSupplierContact() != null) {
|
||||
existing.setSupplierContact(sparePart.getSupplierContact());
|
||||
}
|
||||
if (sparePart.getLocation() != null) {
|
||||
existing.setLocation(sparePart.getLocation());
|
||||
}
|
||||
if (sparePart.getRemarks() != null) {
|
||||
existing.setRemarks(sparePart.getRemarks());
|
||||
}
|
||||
if (sparePart.getStatus() != null) {
|
||||
existing.setStatus(sparePart.getStatus());
|
||||
}
|
||||
|
||||
return sparePartRepository.save(existing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteSparePart(UUID id) {
|
||||
SparePart sparePart = getSparePartById(id);
|
||||
sparePart.setStatus(SparePart.Status.INACTIVE);
|
||||
sparePartRepository.save(sparePart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SparePart getSparePartById(UUID id) {
|
||||
return sparePartRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(6101, "备件不存在"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SparePart> getSparePartsByProject(UUID projectId) {
|
||||
return sparePartRepository.findByProjectIdAndStatus(projectId, SparePart.Status.ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SparePart> getSparePartsByCategory(UUID categoryId) {
|
||||
return sparePartRepository.findByCategoryId(categoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SparePart> getLowStockParts(UUID projectId) {
|
||||
return sparePartRepository.findLowStockParts(projectId);
|
||||
}
|
||||
|
||||
// ==================== 库存管理 ====================
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SparePartRecord inStock(UUID sparePartId, Integer quantity, UUID recordedBy, String remarks) {
|
||||
if (quantity == null || quantity <= 0) {
|
||||
throw new BusinessException(6102, "入库数量必须大于0");
|
||||
}
|
||||
|
||||
SparePart sparePart = getSparePartById(sparePartId);
|
||||
|
||||
// 更新库存
|
||||
sparePart.setCurrentStock(sparePart.getCurrentStock() + quantity);
|
||||
sparePartRepository.save(sparePart);
|
||||
|
||||
// 创建入库记录
|
||||
return createSparePartRecord(sparePartId, SparePartRecord.RecordType.IN, quantity, recordedBy, remarks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SparePartRecord outStock(UUID sparePartId, Integer quantity, UUID relatedOrderId, UUID recordedBy, String remarks) {
|
||||
if (quantity == null || quantity <= 0) {
|
||||
throw new BusinessException(6103, "出库数量必须大于0");
|
||||
}
|
||||
|
||||
SparePart sparePart = getSparePartById(sparePartId);
|
||||
|
||||
// 校验库存是否充足
|
||||
if (sparePart.getCurrentStock() < quantity) {
|
||||
throw new BusinessException(6104, "库存不足,当前库存:" + sparePart.getCurrentStock() + ",需要出库:" + quantity);
|
||||
}
|
||||
|
||||
// 更新库存
|
||||
sparePart.setCurrentStock(sparePart.getCurrentStock() - quantity);
|
||||
sparePartRepository.save(sparePart);
|
||||
|
||||
// 创建出库记录
|
||||
return createSparePartRecordWithOrder(sparePartId, SparePartRecord.RecordType.OUT, quantity, relatedOrderId, recordedBy, remarks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SparePartRecord> getSparePartRecords(UUID sparePartId) {
|
||||
return sparePartRecordRepository.findBySparePartIdOrderByRecordDateDesc(sparePartId);
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 生成备件编码:SP + yyyyMMddHHmmss
|
||||
*/
|
||||
private String generateSparePartCode() {
|
||||
String timestamp = LocalDateTime.now().format(CODE_FORMATTER);
|
||||
String sparePartCode = "SP" + timestamp;
|
||||
|
||||
// 确保编码唯一
|
||||
int suffix = 1;
|
||||
while (sparePartRepository.existsBySparePartCode(sparePartCode)) {
|
||||
sparePartCode = "SP" + timestamp + "_" + suffix;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
return sparePartCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成分类编码:CC + yyyyMMddHHmmss
|
||||
*/
|
||||
private String generateCategoryCode() {
|
||||
String timestamp = LocalDateTime.now().format(CODE_FORMATTER);
|
||||
String categoryCode = "CC" + timestamp;
|
||||
|
||||
// 检查编码是否存在
|
||||
int suffix = 1;
|
||||
while (sparePartCategoryRepository.findByCategoryCode(categoryCode).isPresent()) {
|
||||
categoryCode = "CC" + timestamp + "_" + suffix;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
return categoryCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建库存记录
|
||||
*/
|
||||
private SparePartRecord createSparePartRecord(UUID sparePartId, SparePartRecord.RecordType recordType,
|
||||
Integer quantity, UUID recordedBy, String remarks) {
|
||||
SparePart sparePart = getSparePartById(sparePartId);
|
||||
|
||||
SparePartRecord record = new SparePartRecord();
|
||||
record.setRecordCode(generateRecordCode(recordType));
|
||||
record.setRecordType(recordType);
|
||||
record.setSparePartId(sparePartId);
|
||||
record.setQuantity(quantity);
|
||||
record.setBalance(sparePart.getCurrentStock());
|
||||
record.setRecordedBy(recordedBy);
|
||||
record.setRemarks(remarks);
|
||||
record.setRecordDate(LocalDateTime.now());
|
||||
|
||||
return sparePartRecordRepository.save(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带订单信息的库存记录
|
||||
*/
|
||||
private SparePartRecord createSparePartRecordWithOrder(UUID sparePartId, SparePartRecord.RecordType recordType,
|
||||
Integer quantity, UUID relatedOrderId, UUID recordedBy, String remarks) {
|
||||
SparePart sparePart = getSparePartById(sparePartId);
|
||||
|
||||
SparePartRecord record = new SparePartRecord();
|
||||
record.setRecordCode(generateRecordCode(recordType));
|
||||
record.setRecordType(recordType);
|
||||
record.setSparePartId(sparePartId);
|
||||
record.setQuantity(quantity);
|
||||
record.setBalance(sparePart.getCurrentStock());
|
||||
record.setRelatedOrderId(relatedOrderId);
|
||||
record.setRecordedBy(recordedBy);
|
||||
record.setRemarks(remarks);
|
||||
record.setRecordDate(LocalDateTime.now());
|
||||
|
||||
return sparePartRecordRepository.save(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成记录编码:REC + 类型 + 时间戳
|
||||
*/
|
||||
private String generateRecordCode(SparePartRecord.RecordType recordType) {
|
||||
String typeCode = recordType.name();
|
||||
String timestamp = LocalDateTime.now().format(CODE_FORMATTER);
|
||||
return "REC" + typeCode + timestamp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
-- ============================================
|
||||
-- V3.0__ops_maintenance_and_energy.sql
|
||||
-- 运营管理模块 - 维保计划、维保任务、能耗监控
|
||||
-- ============================================
|
||||
|
||||
-- ============================================
|
||||
-- 维保计划表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_maintenance_plan (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
plan_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
plan_name VARCHAR(100) NOT NULL,
|
||||
equipment_type VARCHAR(50),
|
||||
trigger_type VARCHAR(30) NOT NULL,
|
||||
trigger_value INTEGER,
|
||||
trigger_unit VARCHAR(20),
|
||||
maintenance_items TEXT,
|
||||
estimated_duration INTEGER,
|
||||
assigned_to UUID,
|
||||
sla_response_hours INTEGER,
|
||||
sla_complete_hours INTEGER,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_plan_project ON ops_maintenance_plan(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_plan_status ON ops_maintenance_plan(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_plan_trigger ON ops_maintenance_plan(trigger_type);
|
||||
|
||||
-- ============================================
|
||||
-- 维保任务表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_maintenance_task (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
task_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
plan_id UUID,
|
||||
equipment_id UUID,
|
||||
task_type VARCHAR(20) DEFAULT 'PREVENTIVE',
|
||||
trigger_type VARCHAR(30),
|
||||
maintenance_items TEXT,
|
||||
status VARCHAR(20) DEFAULT 'PENDING',
|
||||
assigned_to UUID,
|
||||
scheduled_date TIMESTAMP,
|
||||
actual_start_date TIMESTAMP,
|
||||
actual_end_date TIMESTAMP,
|
||||
labor_hours DECIMAL(10,2),
|
||||
materials_cost DECIMAL(12,2),
|
||||
remarks TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_task_project ON ops_maintenance_task(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_task_status ON ops_maintenance_task(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_task_equipment ON ops_maintenance_task(equipment_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_task_assignee ON ops_maintenance_task(assigned_to);
|
||||
CREATE INDEX IF NOT EXISTS idx_maintenance_task_scheduled ON ops_maintenance_task(scheduled_date);
|
||||
|
||||
-- ============================================
|
||||
-- 能耗计量点表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_energy_meter (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
meter_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
meter_name VARCHAR(100) NOT NULL,
|
||||
energy_type VARCHAR(20) NOT NULL,
|
||||
space_node_id UUID,
|
||||
installation_location VARCHAR(200),
|
||||
rated_capacity DECIMAL(10,2),
|
||||
unit_price DECIMAL(10,4),
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_energy_meter_project ON ops_energy_meter(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_energy_meter_type ON ops_energy_meter(energy_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_energy_meter_space ON ops_energy_meter(space_node_id);
|
||||
|
||||
-- ============================================
|
||||
-- 能耗记录表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_energy_consumption (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
meter_id UUID NOT NULL,
|
||||
consumption_date DATE NOT NULL,
|
||||
previous_reading DECIMAL(12,2),
|
||||
current_reading DECIMAL(12,2) NOT NULL,
|
||||
consumption DECIMAL(12,2) NOT NULL,
|
||||
amount DECIMAL(10,2),
|
||||
recorded_by UUID,
|
||||
record_method VARCHAR(20) DEFAULT 'MANUAL',
|
||||
remarks TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_energy_consumption_meter ON ops_energy_consumption(meter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_energy_consumption_date ON ops_energy_consumption(consumption_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_energy_consumption_project ON ops_energy_consumption(project_id);
|
||||
|
||||
-- ============================================
|
||||
-- 备件分类表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_spare_part_category (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
parent_id UUID,
|
||||
category_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_category_parent ON ops_spare_part_category(parent_id);
|
||||
|
||||
-- ============================================
|
||||
-- 备件表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_spare_part (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
spare_part_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
spare_part_name VARCHAR(100) NOT NULL,
|
||||
category_id UUID,
|
||||
specification VARCHAR(200),
|
||||
unit VARCHAR(20) NOT NULL,
|
||||
safe_stock INTEGER DEFAULT 0,
|
||||
current_stock INTEGER DEFAULT 0,
|
||||
unit_price DECIMAL(10,2),
|
||||
supplier VARCHAR(200),
|
||||
supplier_contact VARCHAR(100),
|
||||
location VARCHAR(200),
|
||||
remarks TEXT,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_project ON ops_spare_part(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_category ON ops_spare_part(category_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_stock ON ops_spare_part(current_stock);
|
||||
|
||||
-- ============================================
|
||||
-- 备件记录表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_spare_part_record (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
record_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
record_type VARCHAR(20) NOT NULL,
|
||||
spare_part_id UUID NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
balance INTEGER NOT NULL,
|
||||
related_order_id UUID,
|
||||
recorded_by UUID,
|
||||
record_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
remarks TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_record_part ON ops_spare_part_record(spare_part_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_record_order ON ops_spare_part_record(related_order_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_spare_part_record_date ON ops_spare_part_record(record_date);
|
||||
|
||||
-- ============================================
|
||||
-- 设备故障历史表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_equipment_failure_history (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
equipment_id UUID NOT NULL,
|
||||
failure_date TIMESTAMP NOT NULL,
|
||||
failure_type VARCHAR(50),
|
||||
failure_cause VARCHAR(200),
|
||||
failure_description TEXT,
|
||||
repair_start_time TIMESTAMP,
|
||||
repair_end_time TIMESTAMP,
|
||||
repair_duration DECIMAL(10,2),
|
||||
repair_cost DECIMAL(12,2),
|
||||
spare_parts_used TEXT,
|
||||
work_order_id UUID,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_equipment_failure_equipment ON ops_equipment_failure_history(equipment_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_equipment_failure_date ON ops_equipment_failure_history(failure_date);
|
||||
|
||||
-- ============================================
|
||||
-- 设备健康度表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_equipment_health_score (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
equipment_id UUID NOT NULL,
|
||||
score_date DATE NOT NULL,
|
||||
health_score DECIMAL(5,2),
|
||||
mtbf DECIMAL(10,2),
|
||||
mttr DECIMAL(10,2),
|
||||
failure_count INTEGER,
|
||||
maintenance_completion_rate DECIMAL(5,2),
|
||||
alert_level VARCHAR(20) DEFAULT 'NORMAL',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_equipment_health_equipment ON ops_equipment_health_score(equipment_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_equipment_health_date ON ops_equipment_health_score(score_date);
|
||||
|
||||
-- ============================================
|
||||
-- 点检模板表
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS ops_inspection_template (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
template_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
template_name VARCHAR(100) NOT NULL,
|
||||
equipment_type VARCHAR(50),
|
||||
inspection_items TEXT,
|
||||
estimated_duration INTEGER,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
version INTEGER DEFAULT 1,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_inspection_template_project ON ops_inspection_template(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_inspection_template_type ON ops_inspection_template(equipment_type);
|
||||
Loading…
Reference in New Issue