# 运营与服务域 - 详细设计 **文档编号**: 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\) | | 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\) | | 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 结构**: ```json [ { "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\ | - | - | 检查项列表(@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关系图 ```mermaid 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\ | OPS-001 | | GET | `/` | 查询工单列表(分页) | QueryParams | ApiResponse\\> | OPS-002 | | GET | `/{id}` | 获取工单详情 | - | ApiResponse\ | OPS-003 | | PUT | `/{id}` | 更新工单 | WorkOrder | ApiResponse\ | OPS-004 | | DELETE | `/{id}` | 删除工单(逻辑删除) | - | ApiResponse\ | 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\ | OPS-006 | | POST | `/{id}/start` | 开始执行 | - | ApiResponse\ | OPS-007 | | POST | `/{id}/complete` | 完成工单 | WorkOrder | ApiResponse\ | OPS-008 | | POST | `/{id}/verify` | 验收工单 | VerifyRequest | ApiResponse\ | OPS-009 | | POST | `/{id}/cancel` | 取消工单 | - | ApiResponse\ | OPS-010 | | POST | `/{id}/suspend` | 挂起工单 | - | ApiResponse\ | OPS-011 | | POST | `/{id}/resume` | 恢复工单 | - | ApiResponse\ | OPS-012 | | POST | `/{id}/return` | 退回工单 | - | ApiResponse\ | OPS-013 | **请求体定义**: ```java // 派单请求 AssignRequest { String assignedTo; // 负责人 String assignedVendor; // 服务商 LocalDate assignedDate; // 派单日期 } // 验收请求 VerifyRequest { String verifiedBy; // 验收人 String remark; // 备注 Integer rating; // 评分(1-5) } ``` #### 工单统计与明细 | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | GET | `/stats` | 工单统计 | - | ApiResponse\ | OPS-014 | | GET | `/{id}/items` | 获取工单明细 | - | ApiResponse\\> | OPS-015 | | POST | `/{id}/items` | 批量添加工单明细 | List\ | ApiResponse\ | 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\ | 按来源分布 | | byType | Map\ | 按类型分布 | | byPriority | Map\ | 按优先级分布 | ### 3.2 MaintenanceTaskController **基础路径**: `/api/ops/maintenance-tasks` **控制器类**: `com.ether.pms.ops.controller.MaintenanceTaskController` #### 维保任务 CRUD | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | POST | `/` | 创建维保任务 | MaintenanceTask | ApiResponse\ | OPS-017 | | GET | `/` | 查询维保任务列表 | QueryParams | ApiResponse\\> | OPS-018 | | GET | `/{id}` | 获取任务详情 | - | ApiResponse\ | OPS-018 | | PUT | `/{id}` | 更新任务 | MaintenanceTask | ApiResponse\ | OPS-018 | | DELETE | `/{id}` | 删除任务(逻辑删除,设为CANCELLED) | - | ApiResponse\ | 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\ | OPS-019 | | POST | `/{id}/start` | 开始执行 | - | ApiResponse\ | OPS-020 | | POST | `/{id}/complete` | 完成任务(简版) | CompleteRequest | ApiResponse\ | OPS-021 | | POST | `/{id}/complete-details` | 完成任务(详版) | MaintenanceTask | ApiResponse\ | OPS-022 | | POST | `/{id}/verify` | 验收任务 | VerifyRequest | ApiResponse\ | OPS-023 | | POST | `/{id}/cancel` | 取消任务 | - | ApiResponse\ | OPS-024 | | POST | `/{id}/rate` | 评分 | rating (query param) | ApiResponse\ | OPS-025 | **请求体定义**: ```java // 分配请求 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\ | 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\ | 按优先级分布 | | byTriggerType | Map\ | 按触发类型分布 | ### 3.3 InspectionTemplateController **基础路径**: `/api/ops/inspection-templates` **控制器类**: `com.ether.pms.mdm.controller.InspectionTemplateController`(位于module-mdm) | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | GET | `/` | 按项目查询模板 | projectId | ApiResponse\\> | OPS-028 | | POST | `/` | 创建模板 | InspectionTemplate | ApiResponse\ | OPS-028 | | GET | `/{id}` | 获取模板详情 | - | ApiResponse\ | OPS-028 | | PUT | `/{id}` | 更新模板 | InspectionTemplate | ApiResponse\ | OPS-028 | | POST | `/{id}/copy` | 复制模板 | newName (query param) | ApiResponse\ | OPS-029 | | GET | `/by-type/{equipmentType}` | 按设备类型查询 | - | ApiResponse\\> | OPS-028 | ### 3.4 SparePartController **基础路径**: `/api/ops/spare-parts` **控制器类**: `com.ether.pms.mdm.controller.SparePartController`(位于module-mdm) #### 分类管理 | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | GET | `/categories` | 获取所有分类 | - | ApiResponse\\> | OPS-031 | | POST | `/categories` | 创建分类 | SparePartCategory | ApiResponse\ | OPS-031 | #### 备件管理 | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | GET | `/` | 查询备件列表 | projectId, categoryId(可选) | ApiResponse\\> | OPS-030 | | GET | `/{id}` | 获取备件详情 | - | ApiResponse\ | OPS-030 | | POST | `/` | 创建备件 | SparePart | ApiResponse\ | OPS-030 | | PUT | `/{id}` | 更新备件 | SparePart | ApiResponse\ | OPS-030 | | DELETE | `/{id}` | 删除备件(设为INACTIVE) | - | ApiResponse\ | OPS-030 | | GET | `/low-stock` | 低库存预警 | projectId | ApiResponse\\> | OPS-034 | #### 库存操作 | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | POST | `/in-stock` | 入库 | StockRequest | ApiResponse\ | OPS-032 | | POST | `/out-stock` | 出库 | OutStockRequest | ApiResponse\ | OPS-033 | | GET | `/{id}/records` | 获取出入库记录 | - | ApiResponse\\> | OPS-035 | **请求体定义**: ```java // 入库请求 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\ | OPS-036 | | GET | `/meters` | 查询计量点 | projectId, energyType(可选) | ApiResponse\\> | OPS-036 | | GET | `/meters/{id}` | 获取计量点详情 | - | ApiResponse\ | OPS-036 | | PUT | `/meters/{id}` | 更新计量点 | EnergyMeter | ApiResponse\ | OPS-036 | | DELETE | `/meters/{id}` | 删除计量点 | - | ApiResponse\ | OPS-036 | #### 能耗记录 | 方法 | 路径 | 说明 | 请求体 | 响应 | 功能ID | |------|------|------|--------|------|--------| | POST | `/consumption` | 抄表记录 | RecordConsumptionRequest | ApiResponse\ | OPS-037 | | GET | `/consumption/{meterId}` | 获取能耗记录 | startDate, endDate(可选) | ApiResponse\\> | OPS-037 | #### 能耗统计 | 方法 | 路径 | 说明 | 参数 | 响应 | 功能ID | |------|------|------|------|------|--------| | GET | `/statistics/by-type` | 按类型统计 | projectId, month | ApiResponse\\> | OPS-038 | | GET | `/statistics/unit-consumption` | 单位面积能耗 | projectId, month | ApiResponse\ | OPS-039 | **请求体定义**: ```java // 抄表请求 RecordConsumptionRequest { @NotNull UUID meterId; @NotNull BigDecimal currentReading; UUID recordedBy; } ``` --- ## 四、业务规则 ### 4.1 工单状态机 #### 完整状态流转图 ```mermaid 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) ``` #### 完成工单自动计算工时 ```java 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 ``` #### 维保任务状态机 ```mermaid 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 乐观锁 | | 并发读写 | 读到中间状态 | 无保护 | 使用数据库事务隔离级别 | **建议并发控制方案**: ```java // 方案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 实体 |