运营与服务域 - 详细设计
文档编号: DETAIL-OPS
版本: v1.0
模块: module-wo + module-mdm(备件/能耗部分)
生成日期: 2026-05-18
数据来源: ether-pms/module-wo + module-mdm 实际代码 + REVERSE-OPS.md 反推文档
一、功能点清单
| 功能ID |
功能名称 |
优先级 |
实现状态 |
对应需求ID |
| OPS-001 |
工单创建 |
P0 |
已完成 |
02-OPERATIONS-001 |
| OPS-002 |
工单查询(分页) |
P0 |
已完成 |
02-OPERATIONS-001 |
| OPS-003 |
工单详情 |
P0 |
已完成 |
02-OPERATIONS-001 |
| OPS-004 |
工单更新 |
P1 |
已完成 |
02-OPERATIONS-001 |
| OPS-005 |
工单删除(逻辑删除) |
P1 |
已完成 |
02-OPERATIONS-001 |
| OPS-006 |
工单派单 |
P0 |
已完成 |
02-OPERATIONS-002 |
| OPS-007 |
工单开始执行 |
P0 |
已完成 |
02-OPERATIONS-002 |
| OPS-008 |
工单完成 |
P0 |
已完成 |
02-OPERATIONS-002 |
| OPS-009 |
工单验收 |
P0 |
已完成 |
02-OPERATIONS-002 |
| OPS-010 |
工单取消 |
P1 |
已完成 |
02-OPERATIONS-002 |
| OPS-011 |
工单挂起 |
P1 |
已完成 |
02-OPERATIONS-002 |
| OPS-012 |
工单恢复 |
P1 |
已完成 |
02-OPERATIONS-002 |
| OPS-013 |
工单退回 |
P1 |
已完成 |
02-OPERATIONS-002 |
| OPS-014 |
工单统计 |
P1 |
已完成 |
02-OPERATIONS-003 |
| OPS-015 |
工单明细查询 |
P1 |
已完成 |
02-OPERATIONS-004 |
| OPS-016 |
工单明细批量添加 |
P1 |
已完成 |
02-OPERATIONS-004 |
| OPS-017 |
维保任务创建 |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-018 |
维保任务查询 |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-019 |
维保任务分配 |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-020 |
维保任务开始执行 |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-021 |
维保任务完成(简版) |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-022 |
维保任务完成(详版) |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-023 |
维保任务验收 |
P0 |
已完成 |
02-OPERATIONS-005 |
| OPS-024 |
维保任务取消 |
P1 |
已完成 |
02-OPERATIONS-005 |
| OPS-025 |
维保任务评分 |
P2 |
已完成 |
02-OPERATIONS-005 |
| OPS-026 |
维保任务统计 |
P1 |
已完成 |
02-OPERATIONS-006 |
| OPS-027 |
维保计划管理 |
P1 |
已完成 |
02-OPERATIONS-007 |
| OPS-028 |
巡检模板管理 |
P1 |
已完成 |
02-OPERATIONS-008 |
| OPS-029 |
巡检模板复制 |
P2 |
已完成 |
02-OPERATIONS-008 |
| OPS-030 |
备件CRUD |
P0 |
已完成 |
02-OPERATIONS-009 |
| OPS-031 |
备件分类管理 |
P1 |
已完成 |
02-OPERATIONS-009 |
| OPS-032 |
备件入库 |
P0 |
已完成 |
02-OPERATIONS-010 |
| OPS-033 |
备件出库 |
P0 |
已完成 |
02-OPERATIONS-010 |
| OPS-034 |
备件低库存预警 |
P1 |
已完成 |
02-OPERATIONS-010 |
| OPS-035 |
备件出入库记录 |
P1 |
已完成 |
02-OPERATIONS-010 |
| OPS-036 |
能源计量点管理 |
P1 |
已完成 |
02-OPERATIONS-011 |
| OPS-037 |
能耗抄表记录 |
P0 |
已完成 |
02-OPERATIONS-012 |
| OPS-038 |
能耗按类型统计 |
P1 |
已完成 |
02-OPERATIONS-013 |
| OPS-039 |
单位面积能耗 |
P2 |
已完成 |
02-OPERATIONS-013 |
| OPS-040 |
消息通知系统 |
P1 |
未实现 |
02-OPERATIONS-014 |
| OPS-041 |
工单流转记录 |
P2 |
未实现 |
02-OPERATIONS-015 |
| OPS-042 |
SLA监控 |
P2 |
未实现 |
02-OPERATIONS-016 |
| OPS-043 |
智能派单 |
P3 |
未实现 |
02-OPERATIONS-017 |
二、数据结构设计
2.1 WorkOrder(工单)
表名: ops_work_order
实体类: com.ether.pms.ops.entity.WorkOrder
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| workNo |
VARCHAR(50) |
是 |
自动生成 |
工单编号,格式: WO-YYYYMMDD-XXXX,UNIQUE |
| source |
ENUM |
是 |
- |
工单来源(6种) |
| type |
ENUM |
是 |
- |
工单类型(6种) |
| priority |
ENUM |
否 |
MEDIUM |
优先级(4级) |
| status |
ENUM |
否 |
PENDING |
工单状态(8种) |
| previousStatus |
ENUM |
否 |
- |
前一状态(挂起恢复用) |
| title |
VARCHAR(200) |
是 |
- |
工单标题 |
| description |
VARCHAR(2000) |
否 |
- |
工单描述 |
| projectId |
UUID |
是 |
- |
所属项目 |
| equipmentId |
UUID |
否 |
- |
关联设备 |
| spaceId |
UUID |
否 |
- |
关联空间 |
| planId |
UUID |
否 |
- |
关联维保计划 |
| triggerType |
ENUM |
否 |
- |
触发类型(4种) |
| assignedTo |
VARCHAR(200) |
否 |
- |
负责人 |
| assignedVendor |
VARCHAR(200) |
否 |
- |
服务商 |
| assignedDate |
DATE |
否 |
- |
派单日期 |
| actualStart |
TIMESTAMP |
否 |
- |
实际开始时间 |
| actualEnd |
TIMESTAMP |
否 |
- |
实际结束时间 |
| actualHours |
DECIMAL(6,2) |
否 |
- |
实际工时(小时) |
| faultCause |
VARCHAR(2000) |
否 |
- |
故障原因 |
| solution |
VARCHAR(5000) |
否 |
- |
解决方案 |
| result |
VARCHAR(2000) |
否 |
- |
处理结果 |
| laborCost |
DECIMAL(12,2) |
否 |
- |
人工费 |
| partsCost |
DECIMAL(12,2) |
否 |
- |
备件费 |
| totalCost |
DECIMAL(12,2) |
否 |
- |
总费用 |
| completedBy |
VARCHAR(200) |
否 |
- |
完成人 |
| completedDate |
DATE |
否 |
- |
完成日期 |
| verifiedBy |
VARCHAR(200) |
否 |
- |
验收人 |
| verifiedDate |
DATE |
否 |
- |
验收日期 |
| rating |
INTEGER |
否 |
- |
评分(1-5星) |
| remark |
VARCHAR(1000) |
否 |
- |
备注 |
| photos |
JSONB |
否 |
- |
照片URL列表(List<String>) |
| signature |
VARCHAR(2000) |
否 |
- |
签名 |
| isDeleted |
BOOLEAN |
否 |
false |
逻辑删除标记 |
| createdAt |
TIMESTAMP |
是 |
now() |
创建时间 |
| updatedAt |
TIMESTAMP |
是 |
now() |
更新时间 |
| createdBy |
VARCHAR(200) |
否 |
- |
创建人 |
枚举定义:
| 枚举 |
值 |
中文 |
| Source |
OWNER |
业主报修 |
|
MAINTENANCE |
维保计划 |
|
INSPECTION |
巡检触发 |
|
FAULT |
故障触发 |
|
REGULATORY |
法规巡检 |
|
MANUAL |
手动创建 |
| Type |
REPAIR |
维修 |
|
INSPECTION |
巡检 |
|
SECURITY |
安保 |
|
CLEANING |
保洁 |
|
PROPERTY |
物业 |
|
CONSULTATION |
咨询 |
| Priority |
LOW / MEDIUM / HIGH / URGENT |
低/中/高/紧急 |
| Status |
PENDING / ASSIGNED / IN_PROGRESS / SUSPENDED / RETURNED / COMPLETED / VERIFIED / CANCELLED |
见状态机 |
| TriggerType |
PLAN / INSPECTION / FAULT / MANUAL |
计划/巡检/故障/手动 |
索引:
| 索引名 |
字段 |
用途 |
| idx_wo_project_status |
project_id, status |
项目+状态查询 |
| idx_wo_priority_status |
priority, status |
优先级+状态查询 |
| idx_wo_plan_createdat |
plan_id, created_at |
计划关联查询 |
| idx_wo_status_createdat |
status, created_at |
状态+时间查询 |
| idx_wo_createdat_desc |
created_at DESC |
时间倒序 |
2.2 WorkOrderItem(工单明细)
表名: ops_work_order_item
实体类: com.ether.pms.ops.entity.WorkOrderItem
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| workOrderId |
UUID |
是 |
- |
所属工单ID |
| itemType |
ENUM |
是 |
- |
明细类型(3种) |
| itemName |
VARCHAR(200) |
是 |
- |
明细名称 |
| quantity |
DECIMAL(10,2) |
否 |
1 |
数量 |
| unit |
VARCHAR(50) |
否 |
- |
单位 |
| unitPrice |
DECIMAL(12,2) |
否 |
- |
单价 |
| totalPrice |
DECIMAL(12,2) |
否 |
- |
总价 |
| isNormal |
BOOLEAN |
否 |
- |
是否正常 |
| observation |
VARCHAR(2000) |
否 |
- |
观察记录 |
| suggestion |
VARCHAR(2000) |
否 |
- |
建议 |
| sortOrder |
INTEGER |
否 |
0 |
排序序号 |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
枚举定义:
| 枚举 |
值 |
中文 |
| ItemType |
PART |
备件 |
|
INSPECTION_ITEM |
巡检项 |
|
CHECKPOINT |
检查点 |
2.3 MaintenancePlan(维保计划)
表名: ops_maintenance_plan
实体类: com.ether.pms.ops.entity.MaintenancePlan
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| planCode |
VARCHAR(50) |
是 |
自动生成 |
计划编码(UNIQUE) |
| planName |
VARCHAR(200) |
是 |
- |
计划名称 |
| planContent |
VARCHAR(5000) |
否 |
- |
计划内容 |
| projectId |
UUID |
否 |
- |
所属项目 |
| equipmentId |
UUID |
是 |
- |
关联设备 |
| planType |
ENUM |
是 |
- |
计划类型(2种) |
| cycleDays |
INTEGER |
否 |
- |
周期天数 |
| estimatedHours |
DECIMAL(6,2) |
否 |
- |
预估工时 |
| assignedVendor |
VARCHAR(200) |
否 |
- |
指定服务商 |
| status |
ENUM |
否 |
ACTIVE |
计划状态(3种) |
| lastDate |
DATE |
否 |
- |
上次执行日期 |
| nextDate |
DATE |
否 |
- |
下次执行日期 |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
| updatedAt |
TIMESTAMP |
否 |
now() |
更新时间 |
| createdBy |
VARCHAR(200) |
否 |
- |
创建人 |
枚举定义:
| 枚举 |
值 |
中文 |
| PlanType |
PREVENTIVE |
预防性维护 |
|
CORRECTIVE |
纠正性维护 |
| PlanStatus |
ACTIVE |
启用 |
|
INACTIVE |
停用 |
|
SUSPENDED |
暂停 |
2.4 MaintenanceTask(维保任务)
表名: ops_maintenance_task
实体类: com.ether.pms.ops.entity.MaintenanceTask
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| taskNo |
VARCHAR(50) |
否 |
自动生成 |
任务编号,格式: EQ-YYYYMMDD-XXXX,UNIQUE |
| planId |
UUID |
否 |
- |
关联维保计划ID |
| equipmentId |
UUID |
是 |
- |
关联设备ID |
| projectId |
UUID |
否 |
- |
所属项目 |
| taskType |
ENUM |
否 |
- |
任务类型(3种) |
| triggerType |
ENUM |
否 |
- |
触发类型(4种) |
| priority |
ENUM |
否 |
自动判定 |
优先级(4级) |
| status |
ENUM |
否 |
PENDING |
任务状态(6种) |
| title |
VARCHAR(200) |
否 |
- |
任务标题 |
| description |
VARCHAR(2000) |
否 |
- |
任务描述 |
| assignedTo |
VARCHAR(200) |
否 |
- |
负责人 |
| assignedVendor |
VARCHAR(200) |
否 |
- |
服务商 |
| assignedDate |
DATE |
否 |
- |
派单日期 |
| actualStart |
TIMESTAMP |
否 |
- |
实际开始时间 |
| actualEnd |
TIMESTAMP |
否 |
- |
实际结束时间 |
| actualHours |
DECIMAL(6,2) |
否 |
- |
实际工时 |
| faultCause |
VARCHAR(2000) |
否 |
- |
故障原因 |
| solution |
VARCHAR(5000) |
否 |
- |
解决方案 |
| result |
VARCHAR(2000) |
否 |
- |
处理结果 |
| partsUsed |
JSONB |
否 |
- |
使用备件列表(List<Map>) |
| laborCost |
DECIMAL(12,2) |
否 |
- |
人工费 |
| partsCost |
DECIMAL(12,2) |
否 |
- |
备件费 |
| totalCost |
DECIMAL(12,2) |
否 |
- |
总费用 |
| completedBy |
VARCHAR(200) |
否 |
- |
完成人 |
| completedDate |
DATE |
否 |
- |
完成日期 |
| verifiedBy |
VARCHAR(200) |
否 |
- |
验收人 |
| verifiedDate |
DATE |
否 |
- |
验收日期 |
| rating |
INTEGER |
否 |
- |
评分(1-5) |
| remark |
VARCHAR(1000) |
否 |
- |
备注 |
| photos |
JSONB |
否 |
- |
照片URL列表 |
| signature |
VARCHAR(2000) |
否 |
- |
签名 |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
| updatedAt |
TIMESTAMP |
否 |
now() |
更新时间 |
| createdBy |
VARCHAR(200) |
否 |
- |
创建人 |
枚举定义:
| 枚举 |
值 |
中文 |
| TaskType |
PREVENTIVE |
预防性维护 |
|
CORRECTIVE |
纠正性维护 |
|
EMERGENCY |
紧急维修 |
| TriggerType |
PLAN |
计划触发 |
|
INSPECTION |
巡检触发 |
|
FAULT |
故障触发 |
|
MANUAL |
手动创建 |
| Priority |
LOW / MEDIUM / HIGH / URGENT |
低/中/高/紧急 |
| Status |
PENDING / ASSIGNED / IN_PROGRESS / COMPLETED / VERIFIED / CANCELLED |
见状态机 |
partsUsed JSONB 结构:
[
{
"partsId": "uuid",
"partsName": "备件名称",
"quantity": 2,
"unitPrice": 100.00,
"totalPrice": 200.00
}
]
索引:
| 索引名 |
字段 |
用途 |
| idx_mt_equipment_status |
equipment_id, status |
设备+状态查询 |
| idx_mt_project_status |
project_id, status |
项目+状态查询 |
| idx_mt_plan_createdat |
plan_id, created_at |
计划关联查询 |
| idx_mt_status_assigneddate |
status, assigned_date |
状态+派单日期查询 |
2.5 InspectionTemplate(巡检模板)
表名: ops_inspection_template
实体类: com.ether.pms.mdm.entity.InspectionTemplate(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| templateCode |
VARCHAR(50) |
是 |
自动生成 |
模板编码(UNIQUE) |
| templateName |
VARCHAR(200) |
是 |
- |
模板名称 |
| description |
VARCHAR(2000) |
否 |
- |
描述 |
| projectId |
UUID |
否 |
- |
所属项目 |
| spaceId |
UUID |
否 |
- |
关联空间 |
| category |
VARCHAR(50) |
否 |
- |
分类 |
| status |
ENUM |
否 |
ACTIVE |
模板状态(2种) |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
| updatedAt |
TIMESTAMP |
否 |
now() |
更新时间 |
| createdBy |
VARCHAR(200) |
否 |
- |
创建人 |
| items |
List<InspectionItem> |
- |
- |
检查项列表(@Transient,非持久化) |
枚举定义:
| 枚举 |
值 |
中文 |
| TemplateStatus |
ACTIVE |
启用 |
|
INACTIVE |
停用 |
2.6 InspectionItem(巡检项)
表名: ops_inspection_item
实体类: com.ether.pms.mdm.entity.InspectionItem(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| templateId |
UUID |
是 |
- |
所属模板ID |
| itemName |
VARCHAR(200) |
是 |
- |
检查项名称 |
| description |
VARCHAR(2000) |
否 |
- |
描述 |
| checkMethod |
VARCHAR(2000) |
否 |
- |
检查方法 |
| standard |
VARCHAR(2000) |
否 |
- |
检查标准 |
| isMandatory |
BOOLEAN |
否 |
true |
是否必检 |
| isNormalRequired |
BOOLEAN |
否 |
true |
是否需要正常判定 |
| sortOrder |
INTEGER |
否 |
0 |
排序序号 |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
2.7 SparePart(备件)
表名: ops_spare_part
实体类: com.ether.pms.mdm.entity.SparePart(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| projectId |
UUID |
是 |
- |
所属项目 |
| sparePartCode |
VARCHAR |
是 |
自动生成 |
备件编码(UNIQUE),格式: SP+yyyyMMddHHmmss |
| sparePartName |
VARCHAR |
是 |
- |
备件名称 |
| categoryId |
UUID |
否 |
- |
分类ID |
| specification |
VARCHAR(500) |
否 |
- |
规格 |
| unit |
VARCHAR(50) |
是 |
- |
单位 |
| safeStock |
INTEGER |
否 |
0 |
安全库存 |
| currentStock |
INTEGER |
否 |
0 |
当前库存 |
| unitPrice |
DECIMAL(10,2) |
否 |
- |
单价 |
| supplier |
VARCHAR(200) |
否 |
- |
供应商 |
| supplierContact |
VARCHAR |
否 |
- |
供应商联系方式 |
| location |
VARCHAR(200) |
否 |
- |
存放位置 |
| remarks |
VARCHAR(1000) |
否 |
- |
备注 |
| status |
ENUM |
是 |
ACTIVE |
状态(2种) |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
| updatedAt |
TIMESTAMP |
否 |
now() |
更新时间 |
枚举定义:
| 枚举 |
值 |
中文 |
| Status |
ACTIVE |
启用 |
|
INACTIVE |
停用 |
索引:
| 索引名 |
字段 |
用途 |
| idx_sp_project_status |
project_id, status |
项目+状态查询 |
2.8 SparePartCategory(备件分类)
表名: ops_spare_part_category
实体类: com.ether.pms.mdm.entity.SparePartCategory(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| parentId |
UUID |
否 |
- |
父分类ID(支持树形结构) |
| categoryCode |
VARCHAR |
是 |
自动生成 |
分类编码(UNIQUE),格式: CC+yyyyMMddHHmmss |
| categoryName |
VARCHAR |
是 |
- |
分类名称 |
| description |
VARCHAR(500) |
否 |
- |
描述 |
| sortOrder |
INTEGER |
否 |
- |
排序序号 |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
2.9 SparePartRecord(备件出入库记录)
表名: ops_spare_part_record
实体类: com.ether.pms.mdm.entity.SparePartRecord(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| recordCode |
VARCHAR |
是 |
自动生成 |
记录编码(UNIQUE),格式: REC+类型+yyyyMMddHHmmss |
| recordType |
ENUM |
是 |
- |
记录类型(4种) |
| sparePartId |
UUID |
是 |
- |
备件ID |
| quantity |
INTEGER |
是 |
- |
数量 |
| balance |
INTEGER |
是 |
- |
操作后余额 |
| relatedOrderId |
UUID |
否 |
- |
关联工单ID |
| recordedBy |
UUID |
否 |
- |
操作人 |
| recordDate |
TIMESTAMP |
否 |
now() |
操作时间 |
| remarks |
VARCHAR(1000) |
否 |
- |
备注 |
枚举定义:
| 枚举 |
值 |
中文 |
说明 |
| RecordType |
IN |
入库 |
库存增加 |
|
OUT |
出库 |
库存减少,可关联工单 |
|
CHECK |
盘点 |
库存校准 |
|
ADJUST |
调整 |
库存调整 |
2.10 EnergyMeter(能源计量点)
表名: ops_energy_meter
实体类: com.ether.pms.mdm.entity.EnergyMeter(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| projectId |
UUID |
是 |
- |
所属项目 |
| meterCode |
VARCHAR |
是 |
自动生成 |
计量点编码(UNIQUE) |
| meterName |
VARCHAR |
是 |
- |
计量点名称 |
| energyType |
ENUM |
是 |
- |
能源类型(6种) |
| spaceNodeId |
UUID |
否 |
- |
关联空间节点 |
| installationLocation |
VARCHAR |
否 |
- |
安装位置 |
| ratedCapacity |
DECIMAL(10,2) |
否 |
- |
额定容量 |
| unitPrice |
DECIMAL(10,4) |
否 |
- |
单价 |
| status |
ENUM |
是 |
ACTIVE |
状态(2种) |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
| updatedAt |
TIMESTAMP |
否 |
now() |
更新时间 |
枚举定义:
| 枚举 |
值 |
中文 |
说明 |
| EnergyType |
LIGHTING |
照明插座用电 |
建筑分项能耗分类 |
|
HVAC |
空调用电 |
建筑分项能耗分类 |
|
POWER |
动力用电 |
建筑分项能耗分类 |
|
SPECIAL |
特殊用电 |
建筑分项能耗分类 |
|
WATER |
给排水 |
水耗计量 |
|
GAS |
燃气 |
气耗计量 |
| Status |
ACTIVE |
启用 |
可正常抄表 |
|
INACTIVE |
停用 |
不可抄表 |
2.11 EnergyConsumption(能耗记录)
表名: ops_energy_consumption
实体类: com.ether.pms.mdm.entity.EnergyConsumption(位于module-mdm)
| 字段 |
类型 |
必填 |
默认值 |
说明 |
| id |
UUID |
是 |
自动生成 |
主键 |
| projectId |
UUID |
是 |
- |
项目ID |
| meterId |
UUID |
是 |
- |
计量点ID |
| consumptionDate |
DATE |
是 |
- |
消耗日期 |
| previousReading |
DECIMAL(12,2) |
否 |
- |
上次读数 |
| currentReading |
DECIMAL(12,2) |
否 |
- |
当前读数 |
| consumption |
DECIMAL(12,2) |
是 |
- |
消耗量 = currentReading - previousReading |
| amount |
DECIMAL(10,2) |
否 |
- |
费用 = consumption x unitPrice |
| recordedBy |
UUID |
否 |
- |
抄表人 |
| recordMethod |
ENUM |
否 |
MANUAL |
抄表方式(2种) |
| remarks |
VARCHAR(1000) |
否 |
- |
备注 |
| createdAt |
TIMESTAMP |
否 |
now() |
创建时间 |
枚举定义:
| 枚举 |
值 |
中文 |
说明 |
| RecordMethod |
MANUAL |
手动录入 |
人工抄表 |
|
IOT |
IoT自动采集 |
自动采集(预留) |
索引:
| 索引名 |
字段 |
用途 |
| idx_ec_meter_date |
meter_id, consumption_date |
计量点+日期查询 |
| idx_ec_project_date |
project_id, consumption_date |
项目+日期查询 |
2.12 ER关系图
erDiagram
MaintenancePlan ||--o{ MaintenanceTask : "1:N planId"
MaintenancePlan }o--|| Equipment : "equipmentId"
WorkOrder ||--o{ WorkOrderItem : "1:N workOrderId"
WorkOrder }o--o| MaintenancePlan : "planId"
WorkOrder }o--o| Equipment : "equipmentId"
InspectionTemplate ||--o{ InspectionItem : "1:N templateId"
SparePartCategory ||--o{ SparePart : "1:N categoryId"
SparePart ||--o{ SparePartRecord : "1:N sparePartId"
EnergyMeter ||--o{ EnergyConsumption : "1:N meterId"
WorkOrder {
UUID id PK
String workNo UK
Source source
Type type
Priority priority
Status status
Status previousStatus
UUID projectId FK
UUID equipmentId FK
UUID planId FK
}
WorkOrderItem {
UUID id PK
UUID workOrderId FK
ItemType itemType
String itemName
BigDecimal quantity
BigDecimal totalPrice
}
MaintenancePlan {
UUID id PK
String planCode UK
String planName
PlanType planType
Integer cycleDays
PlanStatus status
UUID equipmentId FK
}
MaintenanceTask {
UUID id PK
String taskNo UK
UUID planId FK
UUID equipmentId FK
TaskType taskType
TriggerType triggerType
Priority priority
Status status
}
InspectionTemplate {
UUID id PK
String templateCode UK
String templateName
String category
TemplateStatus status
}
InspectionItem {
UUID id PK
UUID templateId FK
String itemName
String checkMethod
String standard
Boolean isMandatory
}
SparePart {
UUID id PK
String sparePartCode UK
String sparePartName
UUID categoryId FK
Integer currentStock
Integer safeStock
}
SparePartRecord {
UUID id PK
String recordCode UK
RecordType recordType
UUID sparePartId FK
Integer quantity
Integer balance
UUID relatedOrderId FK
}
EnergyMeter {
UUID id PK
String meterCode UK
String meterName
EnergyType energyType
BigDecimal unitPrice
}
EnergyConsumption {
UUID id PK
UUID meterId FK
LocalDate consumptionDate
BigDecimal consumption
BigDecimal amount
}
三、API设计
3.1 WorkOrderController
基础路径: /api/wo/work-orders
控制器类: com.ether.pms.ops.controller.WorkOrderController
工单 CRUD
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/ |
创建工单 |
WorkOrder |
ApiResponse<WorkOrder> |
OPS-001 |
| GET |
/ |
查询工单列表(分页) |
QueryParams |
ApiResponse<PageResponse<WorkOrder>> |
OPS-002 |
| GET |
/{id} |
获取工单详情 |
- |
ApiResponse<WorkOrder> |
OPS-003 |
| PUT |
/{id} |
更新工单 |
WorkOrder |
ApiResponse<WorkOrder> |
OPS-004 |
| DELETE |
/{id} |
删除工单(逻辑删除) |
- |
ApiResponse<Void> |
OPS-005 |
查询参数(GET /):
| 参数 |
类型 |
必填 |
默认值 |
说明 |
| projectId |
UUID |
否 |
- |
按项目筛选 |
| equipmentId |
UUID |
否 |
- |
按设备筛选 |
| source |
Source |
否 |
- |
按来源筛选 |
| type |
Type |
否 |
- |
按类型筛选 |
| status |
Status |
否 |
- |
按状态筛选 |
| priority |
Priority |
否 |
- |
按优先级筛选 |
| assignedTo |
String |
否 |
- |
按负责人筛选 |
| keyword |
String |
否 |
- |
关键词搜索 |
| page |
int |
否 |
0 |
页码 |
| size |
int |
否 |
20 |
每页大小 |
工单状态流转
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/{id}/assign |
派单 |
AssignRequest |
ApiResponse<WorkOrder> |
OPS-006 |
| POST |
/{id}/start |
开始执行 |
- |
ApiResponse<WorkOrder> |
OPS-007 |
| POST |
/{id}/complete |
完成工单 |
WorkOrder |
ApiResponse<WorkOrder> |
OPS-008 |
| POST |
/{id}/verify |
验收工单 |
VerifyRequest |
ApiResponse<WorkOrder> |
OPS-009 |
| POST |
/{id}/cancel |
取消工单 |
- |
ApiResponse<WorkOrder> |
OPS-010 |
| POST |
/{id}/suspend |
挂起工单 |
- |
ApiResponse<WorkOrder> |
OPS-011 |
| POST |
/{id}/resume |
恢复工单 |
- |
ApiResponse<WorkOrder> |
OPS-012 |
| POST |
/{id}/return |
退回工单 |
- |
ApiResponse<WorkOrder> |
OPS-013 |
请求体定义:
// 派单请求
AssignRequest {
String assignedTo; // 负责人
String assignedVendor; // 服务商
LocalDate assignedDate; // 派单日期
}
// 验收请求
VerifyRequest {
String verifiedBy; // 验收人
String remark; // 备注
Integer rating; // 评分(1-5)
}
工单统计与明细
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| GET |
/stats |
工单统计 |
- |
ApiResponse<WorkOrderStatsDTO> |
OPS-014 |
| GET |
/{id}/items |
获取工单明细 |
- |
ApiResponse<List<WorkOrderItem>> |
OPS-015 |
| POST |
/{id}/items |
批量添加工单明细 |
List<WorkOrderItem> |
ApiResponse<WorkOrder> |
OPS-016 |
WorkOrderStatsDTO:
| 字段 |
类型 |
说明 |
| total |
long |
工单总数 |
| pending |
long |
待分配数 |
| assigned |
long |
已派单数 |
| inProgress |
long |
执行中数 |
| completed |
long |
已完成数 |
| verified |
long |
已验收数 |
| cancelled |
long |
已取消数 |
| suspended |
long |
已挂起数 |
| returned |
long |
已退回数 |
| completedToday |
long |
今日完成数 |
| createdToday |
long |
今日创建数 |
| overdue |
long |
逾期数 |
| bySource |
Map<String, Long> |
按来源分布 |
| byType |
Map<String, Long> |
按类型分布 |
| byPriority |
Map<String, Long> |
按优先级分布 |
3.2 MaintenanceTaskController
基础路径: /api/ops/maintenance-tasks
控制器类: com.ether.pms.ops.controller.MaintenanceTaskController
维保任务 CRUD
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/ |
创建维保任务 |
MaintenanceTask |
ApiResponse<MaintenanceTask> |
OPS-017 |
| GET |
/ |
查询维保任务列表 |
QueryParams |
ApiResponse<List<MaintenanceTask>> |
OPS-018 |
| GET |
/{id} |
获取任务详情 |
- |
ApiResponse<MaintenanceTask> |
OPS-018 |
| PUT |
/{id} |
更新任务 |
MaintenanceTask |
ApiResponse<MaintenanceTask> |
OPS-018 |
| DELETE |
/{id} |
删除任务(逻辑删除,设为CANCELLED) |
- |
ApiResponse<Void> |
OPS-024 |
查询参数(GET /):
| 参数 |
类型 |
必填 |
说明 |
| equipmentId |
UUID |
否 |
按设备筛选 |
| planId |
UUID |
否 |
按计划筛选 |
| status |
Status |
否 |
按状态筛选 |
| priority |
Priority |
否 |
按优先级筛选 |
| assignedTo |
String |
否 |
按负责人筛选 |
| overdueDate |
LocalDate |
否 |
逾期截止日期 |
查询参数互斥优先级:equipmentId > planId > status > priority > assignedTo > overdueDate
维保任务状态流转
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/{id}/assign |
分配任务 |
AssignRequest |
ApiResponse<MaintenanceTask> |
OPS-019 |
| POST |
/{id}/start |
开始执行 |
- |
ApiResponse<MaintenanceTask> |
OPS-020 |
| POST |
/{id}/complete |
完成任务(简版) |
CompleteRequest |
ApiResponse<MaintenanceTask> |
OPS-021 |
| POST |
/{id}/complete-details |
完成任务(详版) |
MaintenanceTask |
ApiResponse<MaintenanceTask> |
OPS-022 |
| POST |
/{id}/verify |
验收任务 |
VerifyRequest |
ApiResponse<MaintenanceTask> |
OPS-023 |
| POST |
/{id}/cancel |
取消任务 |
- |
ApiResponse<MaintenanceTask> |
OPS-024 |
| POST |
/{id}/rate |
评分 |
rating (query param) |
ApiResponse<MaintenanceTask> |
OPS-025 |
请求体定义:
// 分配请求
AssignRequest {
String assignedTo;
LocalDate assignedDate;
}
// 完成请求(简版)
CompleteRequest {
String result;
BigDecimal actualHours;
BigDecimal cost;
String completedBy;
}
// 验收请求
VerifyRequest {
String verifiedBy;
String remark;
Integer rating;
}
维保任务统计
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| GET |
/stats |
任务统计 |
- |
ApiResponse<MaintenanceTaskStatsDTO> |
OPS-026 |
MaintenanceTaskStatsDTO:
| 字段 |
类型 |
说明 |
| total |
long |
任务总数 |
| pending |
long |
待分配数 |
| assigned |
long |
已派单数 |
| inProgress |
long |
执行中数 |
| completed |
long |
已完成数 |
| verified |
long |
已验收数 |
| cancelled |
long |
已取消数 |
| completedToday |
long |
今日完成数 |
| createdToday |
long |
今日创建数 |
| overdue |
long |
逾期数 |
| avgCompleteHours |
BigDecimal |
平均完成工时 |
| avgRating |
BigDecimal |
平均评分 |
| byPriority |
Map<String, Long> |
按优先级分布 |
| byTriggerType |
Map<String, Long> |
按触发类型分布 |
3.3 InspectionTemplateController
基础路径: /api/ops/inspection-templates
控制器类: com.ether.pms.mdm.controller.InspectionTemplateController(位于module-mdm)
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| GET |
/ |
按项目查询模板 |
projectId |
ApiResponse<List<InspectionTemplate>> |
OPS-028 |
| POST |
/ |
创建模板 |
InspectionTemplate |
ApiResponse<InspectionTemplate> |
OPS-028 |
| GET |
/{id} |
获取模板详情 |
- |
ApiResponse<InspectionTemplate> |
OPS-028 |
| PUT |
/{id} |
更新模板 |
InspectionTemplate |
ApiResponse<InspectionTemplate> |
OPS-028 |
| POST |
/{id}/copy |
复制模板 |
newName (query param) |
ApiResponse<InspectionTemplate> |
OPS-029 |
| GET |
/by-type/{equipmentType} |
按设备类型查询 |
- |
ApiResponse<List<InspectionTemplate>> |
OPS-028 |
3.4 SparePartController
基础路径: /api/ops/spare-parts
控制器类: com.ether.pms.mdm.controller.SparePartController(位于module-mdm)
分类管理
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| GET |
/categories |
获取所有分类 |
- |
ApiResponse<List<SparePartCategory>> |
OPS-031 |
| POST |
/categories |
创建分类 |
SparePartCategory |
ApiResponse<SparePartCategory> |
OPS-031 |
备件管理
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| GET |
/ |
查询备件列表 |
projectId, categoryId(可选) |
ApiResponse<List<SparePart>> |
OPS-030 |
| GET |
/{id} |
获取备件详情 |
- |
ApiResponse<SparePart> |
OPS-030 |
| POST |
/ |
创建备件 |
SparePart |
ApiResponse<SparePart> |
OPS-030 |
| PUT |
/{id} |
更新备件 |
SparePart |
ApiResponse<SparePart> |
OPS-030 |
| DELETE |
/{id} |
删除备件(设为INACTIVE) |
- |
ApiResponse<Void> |
OPS-030 |
| GET |
/low-stock |
低库存预警 |
projectId |
ApiResponse<List<SparePart>> |
OPS-034 |
库存操作
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/in-stock |
入库 |
StockRequest |
ApiResponse<SparePartRecord> |
OPS-032 |
| POST |
/out-stock |
出库 |
OutStockRequest |
ApiResponse<SparePartRecord> |
OPS-033 |
| GET |
/{id}/records |
获取出入库记录 |
- |
ApiResponse<List<SparePartRecord>> |
OPS-035 |
请求体定义:
// 入库请求
StockRequest {
@NotNull UUID sparePartId;
@NotNull Integer quantity;
UUID recordedBy;
String remarks;
}
// 出库请求
OutStockRequest {
@NotNull UUID sparePartId;
@NotNull Integer quantity;
UUID relatedOrderId; // 关联工单ID
UUID recordedBy;
String remarks;
}
3.5 EnergyController
基础路径: /api/ops/energy
控制器类: com.ether.pms.mdm.controller.EnergyController(位于module-mdm)
计量点管理
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/meters |
创建计量点 |
EnergyMeter |
ApiResponse<EnergyMeter> |
OPS-036 |
| GET |
/meters |
查询计量点 |
projectId, energyType(可选) |
ApiResponse<List<EnergyMeter>> |
OPS-036 |
| GET |
/meters/{id} |
获取计量点详情 |
- |
ApiResponse<EnergyMeter> |
OPS-036 |
| PUT |
/meters/{id} |
更新计量点 |
EnergyMeter |
ApiResponse<EnergyMeter> |
OPS-036 |
| DELETE |
/meters/{id} |
删除计量点 |
- |
ApiResponse<Void> |
OPS-036 |
能耗记录
| 方法 |
路径 |
说明 |
请求体 |
响应 |
功能ID |
| POST |
/consumption |
抄表记录 |
RecordConsumptionRequest |
ApiResponse<EnergyConsumption> |
OPS-037 |
| GET |
/consumption/{meterId} |
获取能耗记录 |
startDate, endDate(可选) |
ApiResponse<List<EnergyConsumption>> |
OPS-037 |
能耗统计
| 方法 |
路径 |
说明 |
参数 |
响应 |
功能ID |
| GET |
/statistics/by-type |
按类型统计 |
projectId, month |
ApiResponse<Map<EnergyType, BigDecimal>> |
OPS-038 |
| GET |
/statistics/unit-consumption |
单位面积能耗 |
projectId, month |
ApiResponse<BigDecimal> |
OPS-039 |
请求体定义:
// 抄表请求
RecordConsumptionRequest {
@NotNull UUID meterId;
@NotNull BigDecimal currentReading;
UUID recordedBy;
}
四、业务规则
4.1 工单状态机
完整状态流转图
stateDiagram-v2
[*] --> PENDING : 创建工单
PENDING --> ASSIGNED : 派单(assign)
PENDING --> CANCELLED : 取消(cancel)
ASSIGNED --> IN_PROGRESS : 开始执行(start)
ASSIGNED --> SUSPENDED : 挂起(suspend)
ASSIGNED --> RETURNED : 退回(return)
ASSIGNED --> CANCELLED : 取消(cancel)
IN_PROGRESS --> COMPLETED : 完成(complete)
IN_PROGRESS --> SUSPENDED : 挂起(suspend)
IN_PROGRESS --> CANCELLED : 取消(cancel)
SUSPENDED --> IN_PROGRESS : 恢复(resume)\n[默认回到IN_PROGRESS]
SUSPENDED --> ASSIGNED : 恢复(resume)\n[previousStatus=ASSIGNED时]
RETURNED --> PENDING : 退回后重新变为待分配
COMPLETED --> VERIFIED : 验收(verify)
VERIFIED --> [*]
CANCELLED --> [*]
8种状态说明
| 状态 |
中文 |
说明 |
可转入状态 |
| PENDING |
待分配 |
工单创建后的初始状态 |
ASSIGNED, CANCELLED |
| ASSIGNED |
已派单 |
已分配负责人/服务商 |
IN_PROGRESS, SUSPENDED, RETURNED, CANCELLED |
| IN_PROGRESS |
执行中 |
正在处理 |
COMPLETED, SUSPENDED, CANCELLED |
| SUSPENDED |
已挂起 |
暂停处理,记录前一状态 |
IN_PROGRESS, ASSIGNED(取决于previousStatus) |
| RETURNED |
已退回 |
被退回重新分配 |
PENDING |
| COMPLETED |
已完成 |
处理完毕 |
VERIFIED |
| VERIFIED |
已验收 |
验收通过 |
终态 |
| CANCELLED |
已取消 |
工单取消 |
终态 |
每个流转的前置条件与自动处理
| 当前状态 |
操作 |
目标状态 |
前置条件 |
自动处理 |
| PENDING |
assign |
ASSIGNED |
- |
设置 assignedTo/assignedVendor/assignedDate |
| ASSIGNED |
start |
IN_PROGRESS |
- |
设置 actualStart = now() |
| IN_PROGRESS |
complete |
COMPLETED |
- |
设置 actualEnd = now(),自动计算 actualHours |
| COMPLETED |
verify |
VERIFIED |
- |
设置 verifiedBy/verifiedDate/rating |
| PENDING/ASSIGNED/IN_PROGRESS |
cancel |
CANCELLED |
status 非 COMPLETED/VERIFIED |
- |
| ASSIGNED/IN_PROGRESS |
suspend |
SUSPENDED |
- |
保存 previousStatus = 当前状态 |
| SUSPENDED |
resume |
previousStatus |
status == SUSPENDED |
恢复到 previousStatus,默认 IN_PROGRESS |
| ASSIGNED |
return |
PENDING |
status == ASSIGNED |
清空 assignedTo/assignedVendor/assignedDate |
previousStatus回溯机制
挂起时:
workOrder.previousStatus = workOrder.status // 保存当前状态
workOrder.status = SUSPENDED
恢复时:
targetStatus = workOrder.previousStatus != null
? workOrder.previousStatus
: IN_PROGRESS // 默认恢复到IN_PROGRESS
workOrder.previousStatus = null // 清空
workOrder.status = targetStatus
工单编号生成规则
格式: WO-YYYYMMDD-XXXX
示例: WO-20260518-0001
生成算法:
1. 日期前缀: "WO-" + LocalDate.now().format("yyyyMMdd") + "-"
2. 查询当天最大编号: findMaxWorkNoByPrefix(prefix + "%")
3. 序号递增: maxWorkNo存在时取后4位+1,否则从1开始
4. 格式化: String.format("%04d", sequence)
完成工单自动计算工时
actualHours = Duration.between(actualStart, actualEnd).toMinutes() / 60.0
// 精度:保留2位小数,四舍五入
4.2 维保任务触发机制
4种触发类型
| 触发类型 |
枚举值 |
说明 |
优先级自动判定 |
| 计划触发 |
PLAN |
由维保计划周期性触发 |
MEDIUM |
| 巡检触发 |
INSPECTION |
巡检发现异常时触发 |
HIGH |
| 故障触发 |
FAULT |
设备故障时触发 |
HIGH(含紧急关键词时URGENT) |
| 手动创建 |
MANUAL |
人工手动创建 |
MEDIUM |
自动优先级判定算法
输入: MaintenanceTask (taskType, triggerType, title, description)
1. IF taskType == EMERGENCY
→ return URGENT
2. IF triggerType == FAULT
→ 检查 title + description 是否包含紧急关键词
→ 紧急关键词: [困人, 漏水, 停电, 火灾, 爆炸, 漏电, 冒烟, 故障停机]
→ IF 包含任一关键词 → return URGENT
→ ELSE → return HIGH
3. IF triggerType == INSPECTION
→ return HIGH
4. IF triggerType == PLAN
→ return MEDIUM
5. 默认 → return MEDIUM
维保任务状态机
stateDiagram-v2
[*] --> PENDING : 创建任务
PENDING --> ASSIGNED : 分配(assign)
PENDING --> CANCELLED : 取消(cancel)
ASSIGNED --> IN_PROGRESS : 开始执行(start)
ASSIGNED --> CANCELLED : 取消(cancel)
IN_PROGRESS --> COMPLETED : 完成(complete/complete-details)
IN_PROGRESS --> CANCELLED : 取消(cancel)
COMPLETED --> VERIFIED : 验收(verify)
VERIFIED --> [*]
CANCELLED --> [*]
维保任务与工单状态机差异
| 差异点 |
WorkOrder |
MaintenanceTask |
| 状态数量 |
8种(含SUSPENDED/RETURNED) |
6种(无SUSPENDED/RETURNED) |
| 完成方式 |
仅一种 complete |
两种:complete(简版)+ complete-details(详版) |
| 评分方式 |
验收时评分 |
验收时评分 + 独立 rate 接口 |
| 删除行为 |
逻辑删除(isDeleted=true) |
逻辑删除(设为CANCELLED) |
| 完成后联动 |
无 |
自动更新设备维保记录 |
| 挂起/恢复 |
支持 |
不支持 |
| 退回 |
支持(ASSIGNED→PENDING) |
不支持 |
维保任务完成后联动
维保任务完成(completeTaskWithDetails)时自动更新设备信息:
1. 更新设备维保商:
equipment.maintenanceVendor = task.assignedVendor
2. 更新下次巡检日期(仅预防性维护):
IF task.taskType == PREVENTIVE
THEN equipment.nextInspectionDate = now() + equipment.inspectionCycle
默认周期: 30天
3. 异常容错:
更新设备失败不影响工单完成(catch异常仅打印日志)
任务编号生成规则
格式: EQ-YYYYMMDD-XXXX
示例: EQ-20260518-0001
生成算法: 与工单编号类似,前缀为 EQ-
4.3 巡检模板管理
模板创建规则:
- templateCode 自动生成(UNIQUE约束)
- 模板创建时可同时配置检查项列表(@Transient字段,需单独持久化)
- 模板状态默认为 ACTIVE
模板复制规则:
- 复制模板时创建新模板,名称使用传入的 newName
- 检查项同时复制到新模板
- 新模板的 templateCode 自动生成
检查项配置:
- InspectionItem 通过 templateId 关联模板
- isMandatory = true 的检查项为必检项,不可跳过
- isNormalRequired = true 的检查项需要判定正常/异常
- sortOrder 控制检查项的展示顺序
4.4 备件库存管理
入库流程
1. 校验: quantity > 0
2. 查询备件: getSparePartById(sparePartId)
3. 更新库存: currentStock += quantity
4. 创建入库记录: RecordType=IN, balance=更新后库存
5. 生成记录编码: REC + IN + yyyyMMddHHmmss
出库流程
1. 校验: quantity > 0
2. 查询备件: getSparePartById(sparePartId)
3. 校验库存: currentStock >= quantity
→ 不满足: 抛出 BusinessException("库存不足,当前库存:X,需要出库:Y")
4. 更新库存: currentStock -= quantity
5. 创建出库记录: RecordType=OUT, balance=更新后库存, relatedOrderId=关联工单
6. 生成记录编码: REC + OUT + yyyyMMddHHmmss
低库存预警
预警条件: currentStock < safeStock
查询方式: sparePartRepository.findLowStockParts(projectId)
库存余额计算
每次出入库操作后:
record.balance = sparePart.currentStock (操作后的库存余额)
关联工单
- 出库时可指定 relatedOrderId 关联工单
- 用于追踪工单的备件消耗
并发场景考虑
当前实现未使用乐观锁或悲观锁,存在并发安全问题:
| 并发场景 |
风险 |
当前处理 |
建议方案 |
| 同时出库 |
库存超卖 |
无保护 |
添加 @Version 乐观锁或 SELECT FOR UPDATE 悲观锁 |
| 同时入库 |
库存计算错误 |
无保护 |
添加 @Version 乐观锁 |
| 并发读写 |
读到中间状态 |
无保护 |
使用数据库事务隔离级别 |
建议并发控制方案:
// 方案1: 乐观锁(推荐)
@Version
private Long version;
// 出库时:
SparePart sp = repository.findById(id);
if (sp.getCurrentStock() < quantity) throw ...;
sp.setCurrentStock(sp.getCurrentStock() - quantity);
repository.save(sp); // version不匹配时抛出OptimisticLockException
// 方案2: 悲观锁
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT sp FROM SparePart sp WHERE sp.id = :id")
SparePart findByIdForUpdate(@Param("id") UUID id);
4.5 能耗计量管理
抄表流程
1. 校验计量点: meterId 存在且 status == ACTIVE
2. 获取上次抄表记录: findTopByMeterIdOrderByConsumptionDateDesc
3. 计算消耗量: consumption = currentReading - previousReading
4. 校验读数: consumption >= 0(当前读数不能小于上次读数)
5. 计算费用: amount = consumption x meter.unitPrice
6. 创建能耗记录: recordMethod=MANUAL
消耗量计算
consumption = currentReading - previousReading
特殊情况:
- 首次抄表: previousReading = 0
- 读数倒退: 抛出 BusinessException("当前读数不能小于上次读数")
费用计算
amount = consumption x meter.unitPrice
条件:
- meter.unitPrice 不为空
- consumption > 0
按类型统计
输入: projectId, month (LocalDate)
1. 计算月份范围: startDate = month.withDayOfMonth(1), endDate = month.withDayOfMonth(length)
2. 初始化所有EnergyType为0
3. 查询项目总消耗: sumConsumptionByProjectAndDateRange
4. 当前实现: 将总消耗分配给LIGHTING类型(TODO: 应按meter.energyType汇总)
当前按类型统计实现不完整,所有消耗归入LIGHTING类型,需改为按meter.energyType分别汇总。
单位面积能耗
输入: projectId, month
1. 获取项目所有ACTIVE状态的计量点
2. 遍历每个计量点,查询当月消耗量
3. 累加总消耗量
注意: 当前实现未除以建筑面积,仅返回总消耗量
TODO: 需要获取项目建筑面积,计算 单位面积能耗 = 总消耗量 / 建筑面积
4.6 工单明细管理
3种明细类型
| 类型 |
枚举值 |
中文 |
用途 |
| PART |
备件 |
记录工单使用的备件及费用 |
|
| INSPECTION_ITEM |
巡检项 |
记录工单的巡检检查项 |
|
| CHECKPOINT |
检查点 |
记录工单的关键检查点 |
|
费用计算
前端计算:
item.totalPrice = item.quantity x item.unitPrice
partsCost = SUM(item.totalPrice) WHERE item.itemType == PART
totalCost = laborCost + partsCost
批量添加
- 支持批量添加工单明细
- 受 BatchOperationValidator 限制(校验批量操作大小)
- 每条明细的 workOrderId 自动设置为当前工单ID
- sortOrder 控制明细展示顺序
五、执行约束
5.1 工单编号唯一性
- workNo 字段在数据库层设置 UNIQUE 约束
- 生成算法基于日期前缀+4位序号,按日期递增
- 编号格式:
WO-YYYYMMDD-XXXX
- 同一天内序号从0001开始递增
5.2 任务编号唯一性
- taskNo 字段在数据库层设置 UNIQUE 约束
- 编号格式:
EQ-YYYYMMDD-XXXX
- 生成算法与工单编号类似
5.3 备件库存非负约束
- 出库时校验:
currentStock >= quantity
- 不满足时抛出 BusinessException(6104, "库存不足,当前库存:X,需要出库:Y")
- 入库数量必须 > 0,否则抛出 BusinessException(6102, "入库数量必须大于0")
- 出库数量必须 > 0,否则抛出 BusinessException(6103, "出库数量必须大于0")
5.4 能耗读数递增约束
- 抄表时校验:
currentReading >= previousReading
- 不满足时抛出 BusinessException(6103, "当前读数不能小于上次读数")
- 首次抄表时 previousReading = 0
5.5 工单状态流转约束
- 只能按合法路径流转,非法流转抛出 RuntimeException
- COMPLETED/VERIFIED 状态不可取消
- SUSPENDED 状态只能恢复(resume),不能直接转为其他状态
- RETURNED 状态自动转为 PENDING
合法流转矩阵:
| 当前\目标 |
PENDING |
ASSIGNED |
IN_PROGRESS |
SUSPENDED |
RETURNED |
COMPLETED |
VERIFIED |
CANCELLED |
| PENDING |
- |
V |
- |
- |
- |
- |
- |
V |
| ASSIGNED |
- |
- |
V |
V |
V |
- |
- |
V |
| IN_PROGRESS |
- |
- |
- |
V |
- |
V |
- |
V |
| SUSPENDED |
- |
V* |
V* |
- |
- |
- |
- |
- |
| RETURNED |
V |
- |
- |
- |
- |
- |
- |
- |
| COMPLETED |
- |
- |
- |
- |
- |
- |
V |
- |
| VERIFIED |
- |
- |
- |
- |
- |
- |
- |
- |
| CANCELLED |
- |
- |
- |
- |
- |
- |
- |
- |
V* 表示 SUSPENDED 恢复时回到 previousStatus
5.6 维保计划周期约束
- cycleDays 必须 > 0(周期天数必须为正数)
- nextDate = lastDate + cycleDays(下次执行日期由周期推算)
- 计划状态变更:ACTIVE 可转为 INACTIVE 或 SUSPENDED
六、权限控制
| API路径 |
操作 |
所需权限 |
说明 |
| POST /api/wo/work-orders |
创建工单 |
wo:work-order:create |
需项目级权限 |
| GET /api/wo/work-orders |
查询工单 |
wo:work-order:read |
需项目级权限 |
| PUT /api/wo/work-orders/{id} |
更新工单 |
wo:work-order:update |
需项目级权限 |
| DELETE /api/wo/work-orders/{id} |
删除工单 |
wo:work-order:delete |
需项目级权限 |
| POST /api/wo/work-orders/{id}/assign |
派单 |
wo:work-order:assign |
需项目级权限 |
| POST /api/wo/work-orders/{id}/start |
开始执行 |
wo:work-order:execute |
需项目级权限 |
| POST /api/wo/work-orders/{id}/complete |
完成工单 |
wo:work-order:execute |
需项目级权限 |
| POST /api/wo/work-orders/{id}/verify |
验收工单 |
wo:work-order:verify |
需项目级权限 |
| POST /api/wo/work-orders/{id}/cancel |
取消工单 |
wo:work-order:cancel |
需项目级权限 |
| POST /api/wo/work-orders/{id}/suspend |
挂起工单 |
wo:work-order:suspend |
需项目级权限 |
| POST /api/wo/work-orders/{id}/resume |
恢复工单 |
wo:work-order:resume |
需项目级权限 |
| POST /api/wo/work-orders/{id}/return |
退回工单 |
wo:work-order:return |
需项目级权限 |
| POST /api/ops/maintenance-tasks |
创建维保任务 |
ops:maintenance-task:create |
需项目级权限 |
| POST /api/ops/maintenance-tasks/{id}/assign |
分配任务 |
ops:maintenance-task:assign |
需项目级权限 |
| POST /api/ops/maintenance-tasks/{id}/start |
开始执行 |
ops:maintenance-task:execute |
需项目级权限 |
| POST /api/ops/maintenance-tasks/{id}/complete |
完成任务 |
ops:maintenance-task:execute |
需项目级权限 |
| POST /api/ops/maintenance-tasks/{id}/verify |
验收任务 |
ops:maintenance-task:verify |
需项目级权限 |
| POST /api/ops/spare-parts/in-stock |
入库 |
ops:spare-part:stock-in |
需项目级权限 |
| POST /api/ops/spare-parts/out-stock |
出库 |
ops:spare-part:stock-out |
需项目级权限 |
| POST /api/ops/energy/consumption |
抄表 |
ops:energy:record |
需项目级权限 |
当前实现中权限控制尚未集成Spring Security,以上为设计预期权限矩阵。
七、例外情况处理
| 例外场景 |
触发条件 |
处理方式 |
错误码/信息 |
| 工单状态流转非法 |
从PENDING直接complete |
抛出RuntimeException |
"只能完成进行中的工单" |
| 工单已完成后取消 |
status=COMPLETED/VERIFIED时cancel |
抛出RuntimeException |
"无法取消已完成的工单" |
| 挂起非ASSIGNED/IN_PROGRESS工单 |
status不合法时suspend |
抛出RuntimeException |
"只能挂起已派单或执行中的工单" |
| 恢复非SUSPENDED工单 |
status!=SUSPENDED时resume |
抛出RuntimeException |
"只能恢复已挂起的工单" |
| 退回非ASSIGNED工单 |
status!=ASSIGNED时return |
抛出RuntimeException |
"只能退回已派单的工单" |
| 备件库存不足 |
出库数量>当前库存 |
抛出BusinessException |
6104 "库存不足,当前库存:X,需要出库:Y" |
| 入库数量非法 |
quantity <= 0 |
抛出BusinessException |
6102 "入库数量必须大于0" |
| 出库数量非法 |
quantity <= 0 |
抛出BusinessException |
6103 "出库数量必须大于0" |
| 能耗读数倒退 |
currentReading < previousReading |
抛出BusinessException |
6103 "当前读数不能小于上次读数" |
| 能源仪表不存在 |
meterId无效 |
抛出BusinessException |
6101 "能源仪表不存在" |
| 仪表非ACTIVE状态 |
抄表时meter.status!=ACTIVE |
抛出BusinessException |
6102 "只能对ACTIVE状态的仪表进行抄表" |
| 维保计划关联设备不存在 |
equipmentId无效 |
数据库外键约束 |
需添加业务校验 |
| 维保任务不存在 |
taskId无效 |
抛出BusinessException |
ErrorCode.NOT_FOUND "维保工单不存在" |
| 工单不存在 |
workOrderId无效或已删除 |
抛出RuntimeException |
"工单不存在: {id}" |
| 维保任务评分越界 |
rating < 1 或 rating > 5 |
抛出BusinessException |
6006 "评分必须在1-5之间" |
| 维保任务评分状态非法 |
非COMPLETED/VERIFIED状态评分 |
抛出BusinessException |
6005 "只有已完成或已验收的工单才能评分" |
| 巡检模板被引用不能删除 |
模板被巡检记录引用 |
- |
当前无校验,建议增加 |
| 备件不存在 |
sparePartId无效 |
抛出BusinessException |
6101 "备件不存在" |
附录:已知代码问题与改进建议
| 问题 |
位置 |
影响 |
建议 |
| 备件出入库无并发控制 |
SparePartServiceImpl |
高并发下可能库存超卖 |
添加 @Version 乐观锁或 SELECT FOR UPDATE |
| 能耗按类型统计实现不完整 |
EnergyConsumptionServiceImpl |
所有消耗归入LIGHTING类型 |
按 meter.energyType 分别汇总 |
| 单位面积能耗未除以建筑面积 |
EnergyConsumptionServiceImpl |
返回的是总消耗量而非单位面积能耗 |
获取项目建筑面积做除法 |
| 工单查询参数互斥 |
WorkOrderController/MaintenanceTaskController |
多条件同时传入只生效一个 |
改为 Specification 动态查询 |
| 维保任务查询无分页 |
MaintenanceTaskController |
数据量大时可能OOM |
改为分页查询 |
| 工单删除/退回使用RuntimeException |
WorkOrderServiceImpl |
异常信息不够结构化 |
改为 BusinessException + ErrorCode |
| 维保任务删除实际是取消 |
MaintenanceTaskServiceImpl |
deleteTask 实际设置 status=CANCELLED |
命名与行为不一致,建议重命名 |
| 前后端状态枚举不一致 |
maintenance.ts vs MaintenanceTask |
前端含ACCEPTED,后端含ASSIGNED/VERIFIED |
统一前后端状态定义 |
| 前端维保计划两套API |
maintenance.ts + maintenance-plan.ts |
接口重复,维护困难 |
统一为一套API |
| 工单无流转记录 |
WorkOrderServiceImpl |
无法追溯工单状态变更历史 |
新增 WorkOrderFlow 实体 |