ether-docs/plans/2026-03-23-equipment-enhanc...

1462 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 设施设备管理增强功能开发计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 实现非居物业设施设备管理增强功能M02-13~18包括设备技术参数扩展、预防性维护引擎、能耗监控、备件库存、故障预测、点检标准库
**Architecture:** 基于现有 ether-pms 单体架构,在 module-mdm 模块新增设备管理相关实体和服务,前端新增设备管理页面。能耗监控和故障预测作为独立功能模块开发。
**Tech Stack:** Spring Boot 3.x + JPA + PostgreSQL + Vue3 + TypeScript + Ant Design Vue
---
## 开发阶段总览
```
阶段一M02-13设备技术参数扩展 - 约3天
阶段二M02-14预防性维护引擎 - 约5天
阶段三M02-15能耗监控管理 - 约4天
阶段四M02-16备件库存管理 - 约4天
阶段五M02-17设备故障预测 - 约3天
阶段六M02-18设备点检标准库 - 约3天
─────────────────────────────────────────
总计 约22天
```
---
## 阶段一M02-13 设备技术参数扩展
### 数据库迁移
**文件:** `ether-pms/src/main/resources/db/migration/V10__add_equipment_extension_fields.sql`
```sql
-- 添加设备扩展字段到 mdm_space_node 表(设备关联空间节点)
ALTER TABLE mdm_space_node ADD COLUMN design_life_years INTEGER;
ALTER TABLE mdm_space_node ADD COLUMN rated_power DECIMAL(10,2);
ALTER TABLE mdm_space_node ADD COLUMN rated_voltage VARCHAR(20);
ALTER TABLE mdm_space_node ADD COLUMN rated_current DECIMAL(10,2);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_vendor VARCHAR(100);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_vendor_contact VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_vendor_phone VARCHAR(20);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_contract_no VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN maintenance_contract_start DATE;
ALTER TABLE mdm_space_node ADD COLUMN maintenance_contract_end DATE;
ALTER TABLE mdm_space_node ADD COLUMN special_equipment_type VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN special_equipment_cert VARCHAR(100);
ALTER TABLE mdm_space_node ADD COLUMN inspection_cycle INTEGER;
ALTER TABLE mdm_space_node ADD COLUMN next_inspection_date DATE;
ALTER TABLE mdm_space_node ADD COLUMN last_inspection_date DATE;
ALTER TABLE mdm_space_node ADD COLUMN last_inspection_result VARCHAR(20);
ALTER TABLE mdm_space_node ADD COLUMN common_spare_parts JSONB;
ALTER TABLE mdm_space_node ADD COLUMN energy_consumption_standard DECIMAL(12,2);
ALTER TABLE mdm_space_node ADD COLUMN installation_environment VARCHAR(50);
ALTER TABLE mdm_space_node ADD COLUMN protection_level VARCHAR(20);
```
---
### Task 1.1: 创建设备分类枚举
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/enums/EquipmentCategory.java`
```java
package com.ether.pms.mdm.enums;
public enum EquipmentCategory {
HVAC("暖通空调"),
ELECTRICAL("电气设备"),
FIRE("消防设备"),
ELEVATOR("电梯设备"),
SECURITY("安防设备"),
给排水("给排水设备"),
LIGHTING("照明设备"),
SPECIAL("特种设备");
private final String desc;
EquipmentCategory(String desc) { this.desc = desc; }
public String getDesc() { return desc; }
}
```
**Step 1:** 创建枚举类
**Step 2:** 验证编译通过
**Step 3:** Commit: `feat: add equipment category enum`
---
### Task 1.2: 创建设备类型枚举
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/enums/EquipmentType.java`
```java
package com.ether.pms.mdm.enums;
public enum EquipmentType {
CENTRAL_AC("中央空调", EquipmentCategory.HVAC),
AIR_CONDITIONER("分体空调", EquipmentCategory.HVAC),
AIR_HANDLING_UNIT("空气处理机组", EquipmentCategory.HVAC),
FAN_COIL("风机盘管", EquipmentCategory.HVAC),
LOW_VOLTAGE_CABINET("低压配电柜", EquipmentCategory.ELECTRICAL),
TRANSFORMER("变压器", EquipmentCategory.ELECTRICAL),
GENERATOR("发电机", EquipmentCategory.ELECTRICAL),
UPS("不间断电源", EquipmentCategory.ELECTRICAL),
FIRE_PUMP("消防泵", EquipmentCategory.FIRE),
SPRINKLER("喷淋系统", EquipmentCategory.FIRE),
FIRE_ALARM("火灾报警系统", EquipmentCategory.FIRE),
ELEVATOR("电梯", EquipmentCategory.ELEVATOR),
CCTV("监控系统", EquipmentCategory.SECURITY),
ACCESS_CONTROL("门禁系统", EquipmentCategory.SECURITY),
WATER_PUMP("给水泵", EquipmentCategory.WATER_DRAINAGE),
DRAINAGE_PUMP("排水泵", EquipmentCategory.WATER_DRAINAGE),
LED_LIGHT("LED灯具", EquipmentCategory.LIGHTING),
HIGH_BAY_LIGHT("工矿灯", EquipmentCategory.LIGHTING);
private final String desc;
private final EquipmentCategory category;
EquipmentType(String desc, EquipmentCategory category) {
this.desc = desc;
this.category = category;
}
public String getDesc() { return desc; }
public EquipmentCategory getCategory() { return category; }
}
```
**Step 1:** 创建枚举类
**Step 2:** 验证编译通过
**Step 3:** Commit: `feat: add equipment type enum`
---
### Task 1.3: 更新SpaceNode实体
**Files:**
- Modify: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java`
在现有实体末尾添加扩展字段:
```java
// ========== 设备扩展字段 ==========
@Column(name = "is_equipment")
private Boolean isEquipment = false;
@Column(name = "design_life_years")
private Integer designLifeYears;
@Column(name = "rated_power", precision = 10, scale = 2)
private BigDecimal ratedPower;
@Column(name = "rated_voltage", length = 20)
private String ratedVoltage;
@Column(name = "rated_current", precision = 10, scale = 2)
private BigDecimal ratedCurrent;
@Column(name = "maintenance_vendor", length = 100)
private String maintenanceVendor;
@Column(name = "maintenance_vendor_contact", length = 50)
private String maintenanceVendorContact;
@Column(name = "maintenance_vendor_phone", length = 20)
private String maintenanceVendorPhone;
@Column(name = "maintenance_contract_no", length = 50)
private String maintenanceContractNo;
@Column(name = "maintenance_contract_start")
private LocalDate maintenanceContractStart;
@Column(name = "maintenance_contract_end")
private LocalDate maintenanceContractEnd;
@Column(name = "special_equipment_type", length = 50)
private String specialEquipmentType;
@Column(name = "special_equipment_cert", length = 100)
private String specialEquipmentCert;
@Column(name = "inspection_cycle")
private Integer inspectionCycle;
@Column(name = "next_inspection_date")
private LocalDate nextInspectionDate;
@Column(name = "last_inspection_date")
private LocalDate lastInspectionDate;
@Column(name = "last_inspection_result", length = 20)
private String lastInspectionResult;
@Column(name = "common_spare_parts", columnDefinition = "TEXT")
private String commonSpareParts;
@Column(name = "energy_consumption_standard", precision = 12, scale = 2)
private BigDecimal energyConsumptionStandard;
@Column(name = "installation_environment", length = 50)
private String installationEnvironment;
@Column(name = "protection_level", length = 20)
private String protectionLevel;
// ========== 设备扩展字段结束 ==========
```
**Step 1:** 添加扩展字段到 SpaceNode 实体
**Step 2:** 运行 `cd ether-pms && mvn compile` 验证
**Step 3:** Commit: `feat: add equipment extension fields to SpaceNode`
---
### Task 1.4: 创建SpaceNode DTO
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeEquipmentDTO.java`
```java
package com.ether.pms.mdm.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
public class SpaceNodeEquipmentDTO extends SpaceNodeDTO {
private Boolean isEquipment;
private Integer designLifeYears;
private BigDecimal ratedPower;
private String ratedVoltage;
private BigDecimal ratedCurrent;
private String maintenanceVendor;
private String maintenanceVendorContact;
private String maintenanceVendorPhone;
private String maintenanceContractNo;
private LocalDate maintenanceContractStart;
private LocalDate maintenanceContractEnd;
private String specialEquipmentType;
private String specialEquipmentCert;
private Integer inspectionCycle;
private LocalDate nextInspectionDate;
private LocalDate lastInspectionDate;
private String lastInspectionResult;
private List<SparePartInfo> commonSpareParts;
private BigDecimal energyConsumptionStandard;
private String installationEnvironment;
private String protectionLevel;
@Data
public static class SparePartInfo {
private String name;
private String model;
private Integer quantity;
}
}
```
**Step 1:** 创建 DTO 类
**Step 2:** 运行 `cd ether-pms && mvn compile` 验证
**Step 3:** Commit: `feat: add SpaceNodeEquipmentDTO`
---
### Task 1.5: 更新SpaceNodeService
**Files:**
- Modify: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java`
添加设备相关方法:
```java
public SpaceNodeEquipmentDTO getEquipmentById(UUID id) {
SpaceNode node = spaceNodeRepository.findById(id)
.orElseThrow(() -> new BusinessException("EQUIPMENT_NOT_FOUND"));
return convertToEquipmentDTO(node);
}
public List<SpaceNodeEquipmentDTO> getSpecialEquipmentList(UUID projectId) {
List<SpaceNode> list = spaceNodeRepository.findByProjectIdAndIsEquipmentAndSpecialEquipmentTypeIsNotNull(
projectId, true);
return list.stream().map(this::convertToEquipmentDTO).collect(Collectors.toList());
}
public List<SpaceNodeEquipmentDTO> getExpiringInspectionEquipment(UUID projectId, Integer daysAhead) {
LocalDate threshold = LocalDate.now().plusDays(daysAhead);
List<SpaceNode> list = spaceNodeRepository
.findByProjectIdAndIsEquipmentAndNextInspectionDateBefore(projectId, true, threshold);
return list.stream().map(this::convertToEquipmentDTO).collect(Collectors.toList());
}
```
**Step 1:** 添加服务方法
**Step 2:** 添加单元测试
**Step 3:** 运行测试验证
**Step 4:** Commit: `feat: add equipment service methods`
---
### Task 1.6: 更新SpaceNodeController
**Files:**
- Modify: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java`
添加设备相关API
```java
@GetMapping("/{id}/equipment")
public ApiResponse<SpaceNodeEquipmentDTO> getEquipment(@PathVariable UUID id) {
return ApiResponse.success(spaceNodeService.getEquipmentById(id));
}
@GetMapping("/special-equipment")
public ApiResponse<List<SpaceNodeEquipmentDTO>> getSpecialEquipment(
@RequestParam UUID projectId) {
return ApiResponse.success(spaceNodeService.getSpecialEquipmentList(projectId));
}
@GetMapping("/expiring-inspection")
public ApiResponse<List<SpaceNodeEquipmentDTO>> getExpiringInspection(
@RequestParam UUID projectId,
@RequestParam(defaultValue = "90") Integer daysAhead) {
return ApiResponse.success(spaceNodeService.getExpiringInspectionEquipment(projectId, daysAhead));
}
```
**Step 1:** 添加控制器端点
**Step 2:** 使用 curl 测试端点
**Step 3:** Commit: `feat: add equipment API endpoints`
---
### Task 1.7: 前端 - 创建设备管理页面
**Files:**
- Create: `ether-admin/src/views/equipment/EquipmentList.vue`
- Create: `ether-admin/src/views/equipment/EquipmentDetail.vue`
- Create: `ether-admin/src/api/equipment.ts`
**Step 1:** 创建 API 模块 `ether-admin/src/api/equipment.ts`
```typescript
import request from '@/utils/request'
export interface EquipmentForm {
id?: string
code: string
name: string
nodeType: string
locationDesc?: string
designLifeYears?: number
ratedPower?: number
ratedVoltage?: string
maintenanceVendor?: string
maintenanceVendorPhone?: string
specialEquipmentType?: string
inspectionCycle?: number
nextInspectionDate?: string
[key: string]: any
}
export function getEquipmentList(params: any) {
return request({
url: '/api/v1/mdm/space-nodes/equipment',
method: 'get',
params
})
}
export function getEquipmentDetail(id: string) {
return request({
url: `/api/v1/mdm/space-nodes/${id}/equipment`,
method: 'get'
})
}
export function getSpecialEquipment(projectId: string) {
return request({
url: '/api/v1/mdm/space-nodes/special-equipment',
method: 'get',
params: { projectId }
})
}
export function getExpiringInspection(projectId: string, daysAhead?: number) {
return request({
url: '/api/v1/mdm/space-nodes/expiring-inspection',
method: 'get',
params: { projectId, daysAhead }
})
}
```
**Step 2:** 创建设备列表页面 `EquipmentList.vue`
**Step 3:** 创建设备详情页面 `EquipmentDetail.vue`
**Step 4:** 运行 `npm run build` 验证前端编译
**Step 5:** Commit: `feat: add equipment management pages`
---
## 阶段二M02-14 预防性维护引擎
### Task 2.1: 创建维保计划实体
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/MaintenancePlan.java`
```java
package com.ether.pms.mdm.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "ops_maintenance_plan")
@Data
public class MaintenancePlan {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "project_id", nullable = false)
private UUID projectId;
@Column(name = "plan_code", nullable = false, unique = true)
private String planCode;
@Column(name = "plan_name", nullable = false)
private String planName;
@Column(name = "equipment_type")
private String equipmentType;
@Column(name = "trigger_type", nullable = false)
@Enumerated(EnumType.STRING)
private TriggerType triggerType;
public enum TriggerType {
TIME_BASED, // 时间触发
HOURS_BASED, // 运行小时触发
CYCLES_BASED, // 次数触发
CONDITION_BASED // 条件触发
}
@Column(name = "trigger_value")
private Integer triggerValue;
@Column(name = "trigger_unit")
private String triggerUnit;
@Column(name = "maintenance_items", columnDefinition = "TEXT")
private String maintenanceItems;
@Column(name = "estimated_duration")
private Integer estimatedDuration;
@Column(name = "assigned_to")
private UUID assignedTo;
@Column(name = "sla_response_hours")
private Integer slaResponseHours;
@Column(name = "sla_complete_hours")
private Integer slaCompleteHours;
@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();
}
@PreUpdate
public void preUpdate() {
updatedAt = LocalDateTime.now();
}
}
```
**Step 1:** 创建实体类
**Step 2:** 运行 `mvn compile` 验证
**Step 3:** Commit: `feat: add MaintenancePlan entity`
---
### Task 2.2: 创建维保任务实体
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/MaintenanceTask.java`
```java
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_maintenance_task")
@Data
public class MaintenanceTask {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "project_id", nullable = false)
private UUID projectId;
@Column(name = "task_code", nullable = false, unique = true)
private String taskCode;
@Column(name = "plan_id")
private UUID planId;
@Column(name = "equipment_id")
private UUID equipmentId;
@Column(name = "task_type")
@Enumerated(EnumType.STRING)
private TaskType taskType = TaskType.PREVENTIVE;
public enum TaskType {
PREVENTIVE, // 预防性维护
CORRECTIVE // 纠正性维护
}
@Column(name = "trigger_type")
@Enumerated(EnumType.STRING)
private MaintenancePlan.TriggerType triggerType;
@Column(name = "maintenance_items", columnDefinition = "TEXT")
private String maintenanceItems;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Status status = Status.PENDING;
public enum Status {
PENDING, // 待接受
ACCEPTED, // 已接受
IN_PROGRESS, // 执行中
COMPLETED, // 已完成
CANCELLED // 已取消
}
@Column(name = "assigned_to")
private UUID assignedTo;
@Column(name = "scheduled_date")
private LocalDateTime scheduledDate;
@Column(name = "actual_start_date")
private LocalDateTime actualStartDate;
@Column(name = "actual_end_date")
private LocalDateTime actualEndDate;
@Column(name = "labor_hours", precision = 10, scale = 2)
private BigDecimal laborHours;
@Column(name = "materials_cost", precision = 12, scale = 2)
private BigDecimal materialsCost;
@Column(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();
}
@PreUpdate
public void preUpdate() {
updatedAt = LocalDateTime.now();
}
}
```
**Step 1:** 创建实体类
**Step 2:** 运行 `mvn compile` 验证
**Step 3:** Commit: `feat: add MaintenanceTask entity`
---
### Task 2.3: 创建维保Repository
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/repository/MaintenancePlanRepository.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/repository/MaintenanceTaskRepository.java`
```java
package com.ether.pms.mdm.repository;
public interface MaintenancePlanRepository extends JpaRepository<MaintenancePlan, UUID> {
List<MaintenancePlan> findByProjectIdAndStatus(UUID projectId, MaintenancePlan.Status status);
Optional<MaintenancePlan> findByPlanCode(String planCode);
}
public interface MaintenanceTaskRepository extends JpaRepository<MaintenanceTask, UUID> {
List<MaintenanceTask> findByProjectIdAndStatus(UUID projectId, MaintenanceTask.Status status);
List<MaintenanceTask> findByAssignedToAndStatus(UUID assignedTo, MaintenanceTask.Status status);
List<MaintenanceTask> findByEquipmentIdAndStatusNot(UUID equipmentId, MaintenanceTask.Status status);
Optional<MaintenanceTask> findByTaskCode(String taskCode);
}
```
**Step 1:** 创建 Repository
**Step 2:** 添加查询方法单元测试
**Step 3:** Commit: `feat: add maintenance repositories`
---
### Task 2.4: 创建维保Service
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/MaintenancePlanService.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/MaintenanceTaskService.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/MaintenancePlanServiceImpl.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/MaintenanceTaskServiceImpl.java`
**Service核心方法**
```java
// MaintenancePlanService
UUID createPlan(MaintenancePlan plan);
List<MaintenancePlan> getActivePlansByProject(UUID projectId);
void updatePlan(UUID id, MaintenancePlan plan);
void deactivatePlan(UUID id);
// MaintenanceTaskService
List<MaintenanceTask> getTasksByAssignee(UUID assignee, String status);
void acceptTask(UUID taskId, UUID userId);
void startTask(UUID taskId);
void completeTask(UUID taskId, BigDecimal laborHours, BigDecimal materialsCost, String remarks);
List<MaintenanceTask> getOverdueTasks();
```
**Step 1:** 创建 Service 接口和实现
**Step 2:** 添加单元测试
**Step 3:** 运行测试验证
**Step 4:** Commit: `feat: add maintenance service`
---
### Task 2.5: 创建维保Controller
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/MaintenancePlanController.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/MaintenanceTaskController.java`
**API 设计:**
| 方法 | 路径 | 描述 |
|------|------|------|
| POST | /api/v1/ops/maintenance-plans | 创建维保计划 |
| GET | /api/v1/ops/maintenance-plans | 获取维保计划列表 |
| PUT | /api/v1/ops/maintenance-plans/{id} | 更新维保计划 |
| DELETE | /api/v1/ops/maintenance-plans/{id} | 删除维保计划 |
| GET | /api/v1/ops/maintenance-tasks | 获取维保任务列表 |
| POST | /api/v1/ops/maintenance-tasks/{id}/accept | 接受任务 |
| POST | /api/v1/ops/maintenance-tasks/{id}/start | 开始执行 |
| POST | /api/v1/ops/maintenance-tasks/{id}/complete | 完成维保 |
**Step 1:** 创建 Controller
**Step 2:** 使用 curl 测试 API
**Step 3:** Commit: `feat: add maintenance controllers`
---
### Task 2.6: 创建定时任务 - 维保触发检查
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/scheduler/MaintenanceScheduler.java`
```java
package com.ether.pms.mdm.scheduler;
@Component
@RequiredArgsConstructor
public class MaintenanceScheduler {
private final MaintenancePlanService planService;
private final MaintenanceTaskService taskService;
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点
public void checkTimeBasedMaintenance() {
log.info("检查时间触发的维保计划...");
List<MaintenancePlan> plans = planService.getTimeBasedActivePlans();
plans.forEach(plan -> taskService.generateTasksFromPlan(plan));
}
@Scheduled(cron = "0 0 * * * ?") // 每小时
public void checkHourlyMaintenance() {
log.info("检查运行小时触发的维保计划...");
// 检查运行小时触发的计划
}
}
```
**Step 1:** 创建定时任务类
**Step 2:** 在 PmsApplication 添加 `@EnableScheduling`
**Step 3:** 测试定时任务执行
**Step 4:** Commit: `feat: add maintenance scheduler`
---
## 阶段三M02-15 能耗监控管理
### Task 3.1: 创建能耗相关实体
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java`
```java
@Entity
@Table(name = "ops_energy_meter")
@Data
public class EnergyMeter {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "project_id", nullable = false)
private UUID projectId;
@Column(name = "meter_code", nullable = false, unique = true)
private String meterCode;
@Column(name = "meter_name", nullable = false)
private String meterName;
@Column(name = "energy_type", nullable = false)
@Enumerated(EnumType.STRING)
private EnergyType energyType;
public enum EnergyType {
LIGHTING, // 照明插座用电
HVAC, // 空调用电
POWER, // 动力用电
SPECIAL, // 特殊用电
WATER, // 给排水
GAS // 燃气
}
@Column(name = "space_node_id")
private UUID spaceNodeId;
@Column(name = "installation_location")
private String installationLocation;
@Column(name = "unit_price", precision = 10, scale = 4)
private BigDecimal unitPrice;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Status status = Status.ACTIVE;
public enum Status { ACTIVE, INACTIVE }
}
@Entity
@Table(name = "ops_energy_consumption")
@Data
public class EnergyConsumption {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "project_id", nullable = false)
private UUID projectId;
@Column(name = "meter_id", nullable = false)
private UUID meterId;
@Column(name = "consumption_date", nullable = false)
private LocalDate consumptionDate;
@Column(name = "previous_reading", precision = 12, scale = 2)
private BigDecimal previousReading;
@Column(name = "current_reading", precision = 12, scale = 2)
private BigDecimal currentReading;
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal consumption;
@Column(precision = 10, scale = 2)
private BigDecimal amount;
@Column(name = "recorded_by")
private UUID recordedBy;
@Column(name = "record_method")
@Enumerated(EnumType.STRING)
private RecordMethod recordMethod = RecordMethod.MANUAL;
public enum RecordMethod { MANUAL, IOT }
}
```
**Step 1:** 创建实体
**Step 2:** 运行编译验证
**Step 3:** Commit: `feat: add energy management entities`
---
### Task 3.2: 创建能耗Service
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyMeterService.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyConsumptionService.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyStatisticsService.java`
```java
// EnergyConsumptionService
void recordConsumption(UUID meterId, BigDecimal currentReading, UUID recordedBy);
List<EnergyConsumption> getConsumptionByMeter(UUID meterId, LocalDate startDate, LocalDate endDate);
// EnergyStatisticsService
Map<EnergyType, BigDecimal> getConsumptionByType(UUID projectId, LocalDate month);
BigDecimal getUnitConsumption(UUID projectId, LocalDate month); // kWh/m²
Map<String, BigDecimal> getConsumptionTrend(UUID projectId, Integer months);
List<EnergyAlert> getAlerts(UUID projectId);
```
**Step 1:** 创建 Service
**Step 2:** 添加单元测试
**Step 3:** Commit: `feat: add energy service`
---
### Task 3.3: 创建能耗Controller
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/EnergyController.java`
**API 设计:**
| 方法 | 路径 | 描述 |
|------|------|------|
| GET | /api/v1/ops/energy-meters | 获取计量点列表 |
| POST | /api/v1/ops/energy-meters | 创建计量点 |
| POST | /api/v1/ops/energy-consumption | 录入能耗数据 |
| GET | /api/v1/ops/energy-statistics/by-type | 按分项统计 |
| GET | /api/v1/ops/energy-statistics/unit-consumption | 单方能耗统计 |
| GET | /api/v1/ops/energy-statistics/trend | 能耗趋势 |
**Step 1:** 创建 Controller
**Step 2:** 测试 API
**Step 3:** Commit: `feat: add energy controller`
---
## 阶段四M02-16 备件库存管理
### Task 4.1: 创建备件实体
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePart.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartRecord.java`
```java
@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 = "location")
private String location;
@Column
@Enumerated(EnumType.STRING)
private Status status = Status.ACTIVE;
}
@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 // 调整
}
@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;
}
```
**Step 1:** 创建实体
**Step 2:** 编译验证
**Step 3:** Commit: `feat: add spare part entities`
---
### Task 4.2: 创建备件Service
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/SparePartService.java`
```java
public interface SparePartService {
SparePart create(SparePart sparePart);
void updateStock(UUID sparePartId, Integer quantity, SparePartRecord.RecordType type, UUID relatedOrderId);
List<SparePart> getLowStockParts(UUID projectId);
List<SparePartRecord> getRecordsByPart(UUID sparePartId);
}
```
**Step 1:** 创建 Service
**Step 2:** 添加库存扣减逻辑(事务处理)
**Step 3:** 添加单元测试
**Step 4:** Commit: `feat: add spare part service`
---
### Task 4.3: 创建备件Controller
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/SparePartController.java`
**API 设计:**
| 方法 | 路径 | 描述 |
|------|------|------|
| GET | /api/v1/ops/spare-parts | 获取备件列表 |
| POST | /api/v1/ops/spare-parts | 创建备件 |
| GET | /api/v1/ops/spare-parts/{id} | 获取备件详情 |
| GET | /api/v1/ops/spare-parts/low-stock | 获取低库存备件 |
| POST | /api/v1/ops/spare-part-records | 创建备件记录 |
| GET | /api/v1/ops/spare-part-records/{sparePartId} | 获取备件记录 |
**Step 1:** 创建 Controller
**Step 2:** 测试 API
**Step 3:** Commit: `feat: add spare part controller`
---
## 阶段五M02-17 设备故障预测
### Task 5.1: 创建故障历史实体
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EquipmentFailureHistory.java`
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EquipmentHealthScore.java`
```java
@Entity
@Table(name = "ops_equipment_failure_history")
@Data
public class EquipmentFailureHistory {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "equipment_id", nullable = false)
private UUID equipmentId;
@Column(name = "failure_date", nullable = false)
private LocalDateTime failureDate;
@Column(name = "failure_type")
private String failureType;
@Column(name = "failure_cause")
private String failureCause;
@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", precision = 10, scale = 2)
private BigDecimal repairDuration;
@Column(name = "repair_cost", precision = 12, scale = 2)
private BigDecimal repairCost;
@Column(name = "spare_parts_used", columnDefinition = "TEXT")
private String sparePartsUsed;
@Column(name = "work_order_id")
private UUID workOrderId;
}
@Entity
@Table(name = "ops_equipment_health_score")
@Data
public class EquipmentHealthScore {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "equipment_id", nullable = false)
private UUID equipmentId;
@Column(name = "score_date", nullable = false)
private LocalDate scoreDate;
@Column(name = "health_score", precision = 5, scale = 2)
private BigDecimal healthScore;
@Column(precision = 10, scale = 2)
private BigDecimal mtbf;
@Column(precision = 10, scale = 2)
private BigDecimal mttr;
@Column(name = "failure_count")
private Integer failureCount;
@Column(name = "maintenance_completion_rate", precision = 5, scale = 2)
private BigDecimal maintenanceCompletionRate;
@Column(name = "alert_level")
@Enumerated(EnumType.STRING)
private AlertLevel alertLevel;
public enum AlertLevel { NORMAL, WARNING, CRITICAL }
}
```
**Step 1:** 创建实体
**Step 2:** 编译验证
**Step 3:** Commit: `feat: add equipment health entities`
---
### Task 5.2: 创建故障预测Service
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/EquipmentHealthService.java`
```java
public interface EquipmentHealthService {
// 计算设备健康度
EquipmentHealthScore calculateHealthScore(UUID equipmentId);
// 获取设备MTBF
BigDecimal calculateMTBF(UUID equipmentId, Integer days);
// 获取设备MTTR
BigDecimal calculateMTTR(UUID equipmentId, Integer days);
// 获取设备健康度历史
List<EquipmentHealthScore> getHealthHistory(UUID equipmentId);
// 获取预警设备列表
List<UUID> getAlertEquipmentIds(UUID projectId, AlertLevel level);
// 记录故障
void recordFailure(EquipmentFailureHistory failure);
}
```
**健康度算法实现:**
```java
public EquipmentHealthScore calculateHealthScore(UUID equipmentId) {
// 基础分 = 100
BigDecimal baseScore = new BigDecimal("100");
// 故障率扣分 = 故障次数 × 5近30天
int failureCount = failureHistoryRepository.countByEquipmentIdAndFailureDateAfter(
equipmentId, LocalDateTime.now().minusDays(30));
BigDecimal failureDeduction = new BigDecimal(failureCount * 5);
// 维保扣分 = (1 - 维保完成率) × 20
BigDecimal maintenanceRate = maintenanceTaskService.getCompletionRate(equipmentId);
BigDecimal maintenanceDeduction = new BigDecimal("1").subtract(maintenanceRate)
.multiply(new BigDecimal("20"));
// 年龄扣分 = 投入使用年限 × 2最高扣10分
SpaceNode equipment = spaceNodeRepository.findById(equipmentId).orElseThrow();
int yearsInService = calculateYearsInService(equipment.getCreatedAt());
BigDecimal ageDeduction = new BigDecimal(Math.min(yearsInService * 2, 10));
BigDecimal healthScore = baseScore
.subtract(failureDeduction)
.subtract(maintenanceDeduction)
.subtract(ageDeduction);
return healthScore;
}
```
**Step 1:** 创建 Service 接口
**Step 2:** 实现计算逻辑
**Step 3:** 添加单元测试
**Step 4:** Commit: `feat: add equipment health service`
---
## 阶段六M02-18 设备点检标准库
### Task 6.1: 创建点检模板实体
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionTemplate.java`
```java
@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")
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
private Integer version;
@Column(name = "created_by")
private UUID createdBy;
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
public void prePersist() {
createdAt = LocalDateTime.now();
if (version == null) version = 1;
}
}
```
**Step 1:** 创建实体
**Step 2:** 编译验证
**Step 3:** Commit: `feat: add inspection template entity`
---
### Task 6.2: 创建点检Service
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionTemplateService.java`
```java
public interface InspectionTemplateService {
InspectionTemplate create(InspectionTemplate template);
InspectionTemplate copyTemplate(UUID templateId, String newName);
List<InspectionTemplate> getTemplatesByType(String equipmentType);
void updateTemplate(UUID id, InspectionTemplate template);
}
```
**点检项目JSON结构**
```json
{
"items": [
{
"itemCode": "I001",
"itemName": "电机温度检查",
"inspectionPoint": "电机外壳",
"inspectionMethod": "HAND_FEEL",
"standard": "温度正常,无明显过热",
"normalValue": "≤60°C",
"abnormalValue": ">70°C",
"handlingMethod": "停机检查,联系维修",
"isRequired": true
}
]
}
```
**Step 1:** 创建 Service
**Step 2:** 添加 JSON 解析/存储逻辑
**Step 3:** Commit: `feat: add inspection template service`
---
### Task 6.3: 创建点检Controller
**Files:**
- Create: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionTemplateController.java`
**API 设计:**
| 方法 | 路径 | 描述 |
|------|------|------|
| GET | /api/v1/ops/inspection-templates | 获取点检模板列表 |
| POST | /api/v1/ops/inspection-templates | 创建点检模板 |
| GET | /api/v1/ops/inspection-templates/{id} | 获取模板详情 |
| PUT | /api/v1/ops/inspection-templates/{id} | 更新模板 |
| POST | /api/v1/ops/inspection-templates/{id}/copy | 复制模板 |
| GET | /api/v1/ops/inspection-templates/by-type/{equipmentType} | 按设备类型获取 |
**Step 1:** 创建 Controller
**Step 2:** 测试 API
**Step 3:** Commit: `feat: add inspection template controller`
---
## 前端页面开发(统一在最后)
### Task F.1: 设备管理页面
**Files:**
- Create: `ether-admin/src/views/equipment/EquipmentList.vue`
- Create: `ether-admin/src/views/equipment/EquipmentDetail.vue`
- Create: `ether-admin/src/views/equipment/EquipmentForm.vue`
- Modify: `ether-admin/src/views/Layout.vue` - 添加设备管理菜单
**Step 1:** 创建设备列表页面
**Step 2:** 创建设备详情页面
**Step 3:** 添加设备表单组件
**Step 4:** 更新 Layout 添加菜单
**Step 5:** 运行 `npm run build` 验证
**Step 6:** Commit: `feat: add equipment management frontend`
---
### Task F.2: 维保管理页面
**Files:**
- Create: `ether-admin/src/views/maintenance/PlanList.vue`
- Create: `ether-admin/src/views/maintenance/TaskList.vue`
**Step 1:** 创建维保计划列表
**Step 2:** 创建维保任务列表
**Step 3:** 编译验证
**Step 4:** Commit: `feat: add maintenance management frontend`
---
### Task F.3: 能耗监控页面
**Files:**
- Create: `ether-admin/src/views/energy/EnergyMeterList.vue`
- Create: `ether-admin/src/views/energy/EnergyStatistics.vue`
**Step 1:** 创建计量点管理页面
**Step 2:** 创建能耗统计页面
**Step 3:** 编译验证
**Step 4:** Commit: `feat: add energy monitoring frontend`
---
### Task F.4: 备件管理页面
**Files:**
- Create: `ether-admin/src/views/sparepart/SparePartList.vue`
- Create: `ether-admin/src/views/sparepart/SparePartRecord.vue`
**Step 1:** 创建备件列表页面
**Step 2:** 创建备件记录页面
**Step 3:** 编译验证
**Step 4:** Commit: `feat: add spare part management frontend`
---
## 测试验证
### Task T.1: 后端单元测试
为每个 Service 创建单元测试:
- `SpaceNodeServiceTest`
- `MaintenancePlanServiceTest`
- `MaintenanceTaskServiceTest`
- `EnergyConsumptionServiceTest`
- `SparePartServiceTest`
- `EquipmentHealthServiceTest`
```bash
cd ether-pms && mvn test -Dtest=*ServiceTest
```
---
### Task T.2: 后端集成测试
```bash
# 使用 H2 内存数据库进行集成测试
cd ether-pms && mvn verify
```
---
### Task T.3: 前端构建验证
```bash
cd ether-admin && npm run build
```
---
### Task T.4: E2E 测试
使用 Playwright 进行 E2E 测试:
```bash
# 设备管理 E2E
npx playwright test tests/equipment.spec.ts
# 维保管理 E2E
npx playwright test tests/maintenance.spec.ts
```
---
## 数据库迁移脚本汇总
**文件列表:**
1. `V10__add_equipment_extension_fields.sql` - 设备扩展字段
2. `V11__create_maintenance_tables.sql` - 维保计划和任务表
3. `V12__create_energy_tables.sql` - 能耗表
4. `V13__create_spare_part_tables.sql` - 备件表
5. `V14__create_equipment_health_tables.sql` - 设备健康表
6. `V15__create_inspection_template_table.sql` - 点检模板表
---
## 部署检查清单
- [ ] 数据库迁移脚本执行
- [ ] 后端编译通过
- [ ] 后端单元测试通过
- [ ] 前端编译通过
- [ ] API 端点验证
- [ ] E2E 测试通过
- [ ] 文档更新
---
## 风险与缓解
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 定时任务性能 | 维保检查任务执行慢 | 使用 @Async 或分布式任务 |
| 能耗数据量大 | 查询性能问题 | 添加索引,分区表 |
| IoT 数据接入 | 协议兼容问题 | 预留适配器接口 |
| 历史数据不足 | 故障预测不准 | 初期使用规则判断 |
---
*计划版本: v1.0*
*最后更新: 2026-03-23*