1009 lines
50 KiB
Markdown
1009 lines
50 KiB
Markdown
# 空间与项目域 - 详细设计
|
||
|
||
**文档版本**: v1.0
|
||
**生成日期**: 2026-05-18
|
||
**数据来源**: module-mdm 实际代码 + REVERSE-MDM.md
|
||
**对照需求**: 02-SPACE_NODE_DESIGN.md
|
||
|
||
---
|
||
|
||
## 一、功能点清单
|
||
|
||
| 功能ID | 功能名称 | 优先级 | 实现状态 | 对应需求ID |
|
||
|--------|----------|--------|----------|------------|
|
||
| SPACE-001 | 项目CRUD | P0 | 已实现 | 02-SPACE-001 |
|
||
| SPACE-002 | 项目编码自动生成 | P0 | 已实现 | 02-SPACE-002 |
|
||
| SPACE-003 | 项目状态流转 | P0 | 已实现 | 02-SPACE-003 |
|
||
| SPACE-004 | 项目成员管理 | P0 | 已实现 | 02-SPACE-004 |
|
||
| SPACE-005 | 项目统计数据 | P1 | 已实现 | 02-SPACE-005 |
|
||
| SPACE-006 | 项目配置管理 | P1 | 已实现 | 02-SPACE-006 |
|
||
| SPACE-007 | 项目删除前检查 | P0 | 已实现 | 02-SPACE-007 |
|
||
| SPACE-008 | 项目选择器列表 | P1 | 已实现 | 02-SPACE-008 |
|
||
| SPACE-009 | 空间节点CRUD | P0 | 已实现 | 02-SPACE-010 |
|
||
| SPACE-010 | 空间节点树形查询 | P0 | 已实现 | 02-SPACE-011 |
|
||
| SPACE-011 | 空间节点批量创建 | P1 | 已实现 | 02-SPACE-012 |
|
||
| SPACE-012 | 空间节点删除检查 | P0 | 已实现 | 02-SPACE-013 |
|
||
| SPACE-013 | 空间节点级联删除 | P0 | 已实现 | 02-SPACE-014 |
|
||
| SPACE-014 | 设备管理(CRUD) | P1 | 已实现(@Deprecated) | 02-SPACE-020 |
|
||
| SPACE-015 | 设备批量创建 | P2 | 已实现(@Deprecated) | 02-SPACE-021 |
|
||
| SPACE-016 | 设备Excel导入 | P2 | 已实现(@Deprecated) | 02-SPACE-022 |
|
||
| SPACE-017 | 特种设备查询 | P2 | 已实现(@Deprecated) | 02-SPACE-023 |
|
||
| SPACE-018 | 即将年检设备查询 | P2 | 已实现(@Deprecated) | 02-SPACE-024 |
|
||
| SPACE-019 | 楼栋楼层信息 | P1 | 已实现 | 02-SPACE-030 |
|
||
| SPACE-020 | 巡检标准项CRUD | P0 | 已实现 | 02-SPACE-040 |
|
||
| SPACE-021 | 巡检模板管理 | P1 | 已实现 | 02-SPACE-041 |
|
||
| SPACE-022 | 巡检记录CRUD | P0 | 已实现 | 02-SPACE-042 |
|
||
| SPACE-023 | 巡检完成确认 | P1 | 已实现 | 02-SPACE-043 |
|
||
| SPACE-024 | 备件CRUD | P1 | 已实现 | 02-SPACE-050 |
|
||
| SPACE-025 | 备件出入库 | P0 | 已实现 | 02-SPACE-051 |
|
||
| SPACE-026 | 备件低库存预警 | P1 | 已实现 | 02-SPACE-052 |
|
||
| SPACE-027 | 备件分类管理 | P1 | 已实现 | 02-SPACE-053 |
|
||
| SPACE-028 | 计量点CRUD | P1 | 已实现 | 02-SPACE-060 |
|
||
| SPACE-029 | 能耗记录录入 | P1 | 已实现 | 02-SPACE-061 |
|
||
| SPACE-030 | 能耗统计 | P2 | 已实现 | 02-SPACE-062 |
|
||
|
||
---
|
||
|
||
## 二、数据结构设计
|
||
|
||
### 2.1 实体定义
|
||
|
||
#### 2.1.1 Project(项目)
|
||
|
||
**表名**: `mdm_project`
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 项目唯一标识符 |
|
||
| code | VARCHAR(50) | NOT NULL, UNIQUE, 正则`^[a-zA-Z0-9_-]+$`, 2-50位 | 项目编码 |
|
||
| name | VARCHAR(100) | NOT NULL, 2-100位 | 项目名称 |
|
||
| description | VARCHAR(500) | | 项目描述 |
|
||
| address | VARCHAR(100) | | 项目地址 |
|
||
| projectType | VARCHAR(20) | @Enumerated(STRING), 默认RESIDENTIAL | 项目类型枚举 |
|
||
| province | VARCHAR(50) | | 省 |
|
||
| city | VARCHAR(50) | | 市 |
|
||
| district | VARCHAR(50) | | 区 |
|
||
| longitude | Double | | 经度 |
|
||
| latitude | Double | | 纬度 |
|
||
| status | VARCHAR(20) | NOT NULL, 默认ACTIVE | 项目状态 |
|
||
| buildingCount | Integer | | 楼栋数 |
|
||
| unitCount | Integer | | 单元数 |
|
||
| roomCount | Integer | | 房间数 |
|
||
| floorCount | Integer | | 楼层数 |
|
||
| logo | VARCHAR(200) | | 项目Logo |
|
||
| contact | VARCHAR(200) | | 联系人 |
|
||
| contactPhone | VARCHAR(20) | 正则`^1[3-9]\d{9}$` | 联系电话 |
|
||
| createdAt | LocalDateTime | NOT NULL, @PrePersist自动填充 | 创建时间 |
|
||
| updatedAt | LocalDateTime | NOT NULL, @PreUpdate自动填充 | 更新时间 |
|
||
|
||
**枚举: ProjectType**
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| RESIDENTIAL | 住宅 |
|
||
| OFFICE | 办公 |
|
||
| INDUSTRIAL_PARK | 产业园区 |
|
||
|
||
**项目状态值**(String类型,非枚举):
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| ACTIVE | 正常 |
|
||
| DISABLED | 禁用 |
|
||
| PENDING | 待审核 |
|
||
| ARCHIVED | 已归档 |
|
||
|
||
**索引**:
|
||
- `mdm_project_code_key` → code (UNIQUE)
|
||
|
||
---
|
||
|
||
#### 2.1.2 SpaceNode(空间节点)-- 核心实体
|
||
|
||
**表名**: `mdm_space_node`
|
||
|
||
##### 基础字段
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 节点唯一标识符 |
|
||
| projectId | UUID | NOT NULL, column: project_code | 项目ID(注意列名为project_code) |
|
||
| code | VARCHAR(50) | @JsonIgnore | 空间编码(保留字段,暂未使用) |
|
||
| name | VARCHAR(100) | NOT NULL, 1-100位 | 节点名称 |
|
||
| fullName | VARCHAR(500) | | 全路径名称 |
|
||
| shortName | VARCHAR(50) | | 简称 |
|
||
| nodeCategory | VARCHAR(20) | NOT NULL, @Enumerated(STRING), column: node_category | 节点大类枚举 |
|
||
| nodeType | VARCHAR(30) | NOT NULL, @Enumerated(STRING), column: node_type | 节点类型枚举 |
|
||
| usageType | VARCHAR(30) | | 用途类型 |
|
||
| parentId | UUID | column: parent_id | 父节点ID |
|
||
| treePath | VARCHAR(1000) | column: tree_path | 物理路径 id.id.id |
|
||
| treePathName | VARCHAR(1000) | column: tree_path_name | 名称路径 项目/楼栋/单元/房间 |
|
||
| level | Integer | 默认0 | 层级深度 |
|
||
| sortOrder | Integer | column: sort_order, 默认0 | 排序号 |
|
||
| status | VARCHAR(20) | 默认ACTIVE | 状态 |
|
||
| deliveryStatus | VARCHAR(20) | column: delivery_status | 交付状态 |
|
||
| decorationStatus | VARCHAR(20) | column: decoration_status | 装修状态 |
|
||
|
||
##### 面积信息
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| buildingArea | BigDecimal(10,2) | column: building_area | 建筑面积(㎡) |
|
||
| usableArea | BigDecimal(10,2) | column: usable_area | 使用面积(㎡) |
|
||
| sharedArea | BigDecimal(10,2) | column: shared_area | 公摊面积(㎡) |
|
||
| landArea | BigDecimal(10,2) | column: land_area | 占地面积(㎡) |
|
||
|
||
##### 地理信息
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| longitude | BigDecimal(10) | | 经度 |
|
||
| latitude | BigDecimal(10) | | 纬度 |
|
||
| altitude | BigDecimal(8,2) | | 海拔 |
|
||
| floorNumber | Integer | column: floor_number | 楼层号(正数地上,负数地下) |
|
||
|
||
##### 地址信息
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| province | VARCHAR(50) | | 省 |
|
||
| city | VARCHAR(50) | | 市 |
|
||
| district | VARCHAR(50) | | 区 |
|
||
| street | VARCHAR(100) | | 街道 |
|
||
| address | VARCHAR(255) | | 详细地址 |
|
||
|
||
##### 扩展属性
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| attributes | VARCHAR(2000) | | 类型特定属性(JSON格式) |
|
||
|
||
##### 系统字段
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| createdAt | LocalDateTime | column: created_at | 创建时间 |
|
||
| updatedAt | LocalDateTime | column: updated_at | 更新时间 |
|
||
| createdBy | UUID | column: created_by | 创建人 |
|
||
| updatedBy | UUID | column: updated_by | 更新人 |
|
||
| isDeleted | Boolean | column: is_deleted, 默认false | 软删除标记 |
|
||
|
||
##### 设备扩展字段(@Deprecated -- 请使用 module-asset 的 Equipment 实体)
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| isEquipment | Boolean | @Deprecated, column: is_equipment, 默认false | 是否为设备节点 |
|
||
| designLifeYears | Integer | @Deprecated, column: design_life_years | 设计寿命(年) |
|
||
| ratedPower | BigDecimal(10,2) | @Deprecated, column: rated_power | 额定功率 |
|
||
| ratedVoltage | BigDecimal(10,2) | @Deprecated, column: rated_voltage | 额定电压 |
|
||
| ratedCurrent | BigDecimal(10,2) | @Deprecated, column: rated_current | 额定电流 |
|
||
| maintenanceVendor | VARCHAR(100) | @Deprecated, column: maintenance_vendor | 维保厂商 |
|
||
| maintenanceVendorContact | VARCHAR(50) | @Deprecated, column: maintenance_vendor_contact | 维保联系人 |
|
||
| maintenanceVendorPhone | VARCHAR(20) | @Deprecated, column: maintenance_vendor_phone | 维保电话 |
|
||
| maintenanceContractNo | VARCHAR(50) | @Deprecated, column: maintenance_contract_no | 维保合同号 |
|
||
| maintenanceContractStart | LocalDate | @Deprecated, column: maintenance_contract_start | 合同开始日期 |
|
||
| maintenanceContractEnd | LocalDate | @Deprecated, column: maintenance_contract_end | 合同结束日期 |
|
||
| specialEquipmentType | VARCHAR(50) | @Deprecated, column: special_equipment_type | 特种设备类型 |
|
||
| specialEquipmentCert | VARCHAR(100) | @Deprecated, column: special_equipment_cert | 特种设备证书 |
|
||
| inspectionCycle | Integer | @Deprecated, column: inspection_cycle | 巡检周期(天) |
|
||
| nextInspectionDate | LocalDate | @Deprecated, column: next_inspection_date | 下次年检日期 |
|
||
| lastInspectionDate | LocalDate | @Deprecated, column: last_inspection_date | 上次年检日期 |
|
||
| lastInspectionResult | VARCHAR(20) | @Deprecated, column: last_inspection_result | 上次年检结果 |
|
||
| commonSpareParts | VARCHAR(2000) | @Deprecated, column: common_spare_parts | 常用备件(JSON) |
|
||
| energyConsumptionStandard | BigDecimal(12,2) | @Deprecated, column: energy_consumption_standard | 能耗标准 |
|
||
| installationEnvironment | VARCHAR(50) | @Deprecated, column: installation_environment | 安装环境 |
|
||
| protectionLevel | VARCHAR(20) | @Deprecated, column: protection_level | 防护等级 |
|
||
|
||
**索引**:
|
||
|
||
| 索引名 | 列 | 说明 |
|
||
|--------|-----|------|
|
||
| idx_space_node_project | project_id | 按项目查询 |
|
||
| idx_space_node_parent | parent_id | 查子节点 |
|
||
| idx_space_node_type | node_type | 按类型查询 |
|
||
| idx_space_node_tree_path | tree_path | 路径查询 |
|
||
| idx_sn_project_parent | project_id, parent_id | 项目+父节点 |
|
||
| idx_sn_project_type | project_id, node_type | 项目+类型 |
|
||
| idx_sn_project_isequipment | project_id, is_equipment | 项目+设备 |
|
||
| idx_sn_project_nextinspection | project_id, next_inspection_date | 年检预警 |
|
||
|
||
---
|
||
|
||
#### 2.1.3 ProjectConfig(项目配置)
|
||
|
||
**表名**: `mdm_project_config`
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 主键 |
|
||
| projectId | UUID | NOT NULL, UNIQUE, column: project_id | 项目ID |
|
||
| enableReservation | Boolean | column: enable_reservation, 默认false | 预约功能 |
|
||
| enableVisitor | Boolean | column: enable_visitor, 默认false | 访客功能 |
|
||
| enableComplaint | Boolean | column: enable_complaint, 默认true | 投诉功能 |
|
||
| enablePayment | Boolean | column: enable_payment, 默认false | 缴费功能 |
|
||
| enableAnnouncement | Boolean | column: enable_announcement, 默认true | 公告功能 |
|
||
| enableSurvey | Boolean | column: enable_survey, 默认false | 问卷功能 |
|
||
| enableVote | Boolean | column: enable_vote, 默认false | 投票功能 |
|
||
| enableMaintenance | Boolean | column: enable_maintenance, 默认true | 报修功能 |
|
||
| enableAsset | Boolean | column: enable_asset, 默认false | 资产功能 |
|
||
| customConfig | VARCHAR(5000) | column: custom_config | 自定义配置JSON |
|
||
| createdAt | LocalDateTime | column: created_at | 创建时间 |
|
||
| updatedAt | LocalDateTime | column: updated_at | 更新时间 |
|
||
|
||
**索引**:
|
||
- `mdm_project_config_project_id_key` → projectId (UNIQUE)
|
||
|
||
---
|
||
|
||
#### 2.1.4 ProjectStatistics(项目统计)
|
||
|
||
**表名**: `mdm_project_statistics`
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 主键 |
|
||
| projectId | UUID | NOT NULL, UNIQUE, column: project_id | 项目ID |
|
||
| memberCount | Integer | column: member_count, 默认0 | 成员数 |
|
||
| buildingCount | Integer | column: building_count, 默认0 | 楼栋数 |
|
||
| unitCount | Integer | column: unit_count, 默认0 | 单元数 |
|
||
| roomCount | Integer | column: room_count, 默认0 | 房间数 |
|
||
| ownerCount | Integer | column: owner_count, 默认0 | 业主数 |
|
||
| tenantCount | Integer | column: tenant_count, 默认0 | 租户数 |
|
||
| lastSyncedAt | LocalDateTime | column: last_synced_at | 最后同步时间 |
|
||
| createdAt | LocalDateTime | column: created_at | 创建时间 |
|
||
| updatedAt | LocalDateTime | column: updated_at | 更新时间 |
|
||
|
||
**索引**:
|
||
- `mdm_project_statistics_project_id_key` → projectId (UNIQUE)
|
||
|
||
---
|
||
|
||
#### 2.1.5 ProjectStatusHistory(状态变更历史)
|
||
|
||
**表名**: `mdm_project_status_history`
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 主键 |
|
||
| projectId | UUID | NOT NULL, column: project_id | 项目ID |
|
||
| fromStatus | VARCHAR(20) | column: from_status | 原状态 |
|
||
| toStatus | VARCHAR(20) | NOT NULL, column: to_status | 新状态 |
|
||
| reason | VARCHAR(500) | | 变更原因 |
|
||
| operatorId | UUID | column: operator_id | 操作人ID |
|
||
| operatorName | VARCHAR(50) | column: operator_name | 操作人姓名 |
|
||
| createdAt | LocalDateTime | column: created_at, @PrePersist自动填充 | 创建时间 |
|
||
|
||
---
|
||
|
||
#### 2.1.6 InspectionItem(巡检标准项)
|
||
|
||
**表名**: `mdm_inspection_item`
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 主键 |
|
||
| equipmentType | VARCHAR(50) | column: equipment_type | 设备类型 |
|
||
| systemType | VARCHAR(50) | column: system_type | 系统类型 |
|
||
| itemName | VARCHAR(200) | NOT NULL, column: item_name | 检查项名称 |
|
||
| checkMethod | VARCHAR(200) | column: check_method | 检查方法 |
|
||
| standardValue | VARCHAR(100) | column: standard_value | 标准值 |
|
||
| isRequired | Boolean | column: is_required, 默认true | 是否必检 |
|
||
| remark | VARCHAR(500) | | 备注 |
|
||
| sortOrder | Integer | column: sort_order | 排序号 |
|
||
| status | VARCHAR(20) | NOT NULL, @Enumerated(STRING), 默认ACTIVE | 状态枚举 |
|
||
| createdAt | LocalDateTime | column: created_at, @PrePersist自动填充 | 创建时间 |
|
||
| updatedAt | LocalDateTime | column: updated_at, @PreUpdate自动填充 | 更新时间 |
|
||
|
||
**枚举: Status**
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| ACTIVE | 启用 |
|
||
| INACTIVE | 停用 |
|
||
|
||
---
|
||
|
||
#### 2.1.7 InspectionRecord(巡检记录)
|
||
|
||
**表名**: `mdm_inspection_record`
|
||
|
||
| 字段名 | 类型 | 约束 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | UUID | PK, 自动生成 | 主键 |
|
||
| planId | UUID | column: plan_id | 巡检计划ID |
|
||
| equipmentId | UUID | NOT NULL, column: equipment_id | 设备ID |
|
||
| inspectionDate | LocalDate | NOT NULL, column: inspection_date | 巡检日期 |
|
||
| inspector | VARCHAR(200) | NOT NULL | 巡检人 |
|
||
| status | VARCHAR(20) | @Enumerated(STRING), 默认NORMAL | 检查状态枚举 |
|
||
| checkInTime | LocalDateTime | column: check_in_time | 签到时间 |
|
||
| checkInLocation | VARCHAR(100) | column: check_in_location | 签到位置 |
|
||
| checkInPhoto | VARCHAR(200) | column: check_in_photo | 签到照片 |
|
||
| items | JSONB | @JdbcTypeCode(JSON), columnDefinition: jsonb | 检查项结果列表 |
|
||
| problems | JSONB | @JdbcTypeCode(JSON), columnDefinition: jsonb | 异常问题列表 |
|
||
| completed | Boolean | 默认false | 是否完成 |
|
||
| completedTime | LocalDateTime | column: completed_time | 完成时间 |
|
||
| createdAt | LocalDateTime | column: created_at, @PrePersist自动填充 | 创建时间 |
|
||
|
||
**枚举: CheckStatus**
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| NORMAL | 正常 |
|
||
| WARNING | 预警 |
|
||
| ABNORMAL | 异常 |
|
||
|
||
**items JSONB结构**:
|
||
```json
|
||
[
|
||
{"itemId": "uuid", "itemName": "检查项", "value": "实测值", "result": "PASS/FAIL", "remark": "备注"}
|
||
]
|
||
```
|
||
|
||
**problems JSONB结构**:
|
||
```json
|
||
[
|
||
{"desc": "问题描述", "photo": "照片URL", "severity": "LOW/MEDIUM/HIGH"}
|
||
]
|
||
```
|
||
|
||
**索引**:
|
||
|
||
| 索引名 | 列 |
|
||
|--------|-----|
|
||
| idx_ir_equipment_date | equipment_id, inspection_date |
|
||
| idx_ir_inspectiondate | inspection_date |
|
||
|
||
---
|
||
|
||
### 2.2 枚举定义
|
||
|
||
#### NodeCategory(节点大类)
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| BUILDING | 建筑空间 |
|
||
| PARKING | 停车空间 |
|
||
| FACILITY | 设施空间 |
|
||
| AREA | 区域空间 |
|
||
|
||
#### NodeType(节点类型)-- 含分类和层级
|
||
|
||
| 值 | 描述 | 所属大类 | 层级序号 |
|
||
|----|------|----------|----------|
|
||
| BUILDING | 楼栋 | BUILDING | 1 |
|
||
| UNIT | 单元 | BUILDING | 2 |
|
||
| FLOOR | 楼层 | BUILDING | 3 |
|
||
| ROOM | 房间 | BUILDING | 4 |
|
||
| SHOP | 商铺 | BUILDING | 2 |
|
||
| GARAGE | 车库 | PARKING | 1 |
|
||
| PARKING_AREA | 停车区域 | PARKING | 2 |
|
||
| PARKING_SPACE | 车位 | PARKING | 3 |
|
||
| EQUIPMENT_ROOM | 设备房 | FACILITY | 1 |
|
||
| PROPERTY_OFFICE | 物业用房 | FACILITY | 1 |
|
||
| SECURITY_ROOM | 门岗 | FACILITY | 1 |
|
||
| PUBLIC_ROOM | 公共用房 | FACILITY | 1 |
|
||
| PUBLIC_AREA | 公共区域 | AREA | 1 |
|
||
| GREEN_AREA | 绿化区域 | AREA | 1 |
|
||
| ROAD | 道路 | AREA | 1 |
|
||
|
||
#### ProjectType(项目类型)
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| RESIDENTIAL | 住宅 |
|
||
| OFFICE | 办公 |
|
||
| INDUSTRIAL_PARK | 产业园区 |
|
||
|
||
#### InspectionItem.Status(巡检标准项状态)
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| ACTIVE | 启用 |
|
||
| INACTIVE | 停用 |
|
||
|
||
#### CheckStatus(巡检检查状态)
|
||
|
||
| 值 | 描述 |
|
||
|----|------|
|
||
| NORMAL | 正常 |
|
||
| WARNING | 预警 |
|
||
| ABNORMAL | 异常 |
|
||
|
||
---
|
||
|
||
### 2.3 ER关系图(Mermaid)
|
||
|
||
```mermaid
|
||
erDiagram
|
||
Project ||--o{ SpaceNode : "1:N projectId"
|
||
Project ||--|| ProjectConfig : "1:1 projectId UNIQUE"
|
||
Project ||--|| ProjectStatistics : "1:1 projectId UNIQUE"
|
||
Project ||--o{ ProjectStatusHistory : "1:N projectId"
|
||
|
||
SpaceNode ||--o{ SpaceNode : "自引用树形 parentId"
|
||
SpaceNode ||--o| SpaceNode : "设备扩展 isEquipment=true"
|
||
|
||
InspectionItem {
|
||
UUID id PK
|
||
String equipmentType
|
||
String systemType
|
||
String itemName
|
||
String status
|
||
}
|
||
|
||
InspectionTemplate {
|
||
UUID id PK
|
||
UUID projectId
|
||
String templateCode
|
||
String equipmentType
|
||
String inspectionItems
|
||
}
|
||
|
||
InspectionRecord {
|
||
UUID id PK
|
||
UUID planId
|
||
UUID equipmentId
|
||
LocalDate inspectionDate
|
||
String inspector
|
||
String status
|
||
}
|
||
|
||
InspectionRecord }o--|| SpaceNode : "equipmentId"
|
||
|
||
SparePartCategory ||--o{ SparePartCategory : "自引用树形 parentId"
|
||
SparePartCategory ||--o{ SparePart : "1:N categoryId"
|
||
SparePart ||--o{ SparePartRecord : "1:N sparePartId"
|
||
|
||
EnergyMeter ||--o{ EnergyConsumption : "1:N meterId"
|
||
EnergyMeter }o--o| SpaceNode : "spaceNodeId"
|
||
```
|
||
|
||
---
|
||
|
||
## 三、API设计
|
||
|
||
### 3.1 ProjectController -- `/api/mdm/projects`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| PROJ-API-001 | GET | `/` | 分页查询项目列表 | keyword(String, 可选), status(String, 可选), page(int, 默认0), size(int, 默认10), sortBy(String, 可选), sortDirection(String, 可选) | `{code, data: PageResponse<Project>}` | 已登录 | 分页参数越界 |
|
||
| PROJ-API-002 | GET | `/selector` | 获取项目选择器列表 | 无 | `{code, data: [ProjectSelectorItem]}` | 已登录 | 无 |
|
||
| PROJ-API-003 | GET | `/generate-code` | 生成项目编码 | 无 | `{code, data: "PRJ-xxx"}` | 管理员 | 无 |
|
||
| PROJ-API-004 | GET | `/{id}` | 按ID查询项目 | id(UUID, Path) | `{code, data: Project}` | 已登录 | 项目不存在 |
|
||
| PROJ-API-005 | GET | `/code/{code}` | 按编码查询项目 | code(String, Path) | `{code, data: Project}` | 已登录 | 编码不存在 |
|
||
| PROJ-API-006 | POST | `/` | 创建项目 | Body: Project实体 | `{code, data: Project}` | 管理员 | 项目编码已存在; 字段校验失败 |
|
||
| PROJ-API-007 | PUT | `/{id}` | 更新项目 | id(UUID, Path), Body: Project实体 | `{code, data: Project}` | 管理员 | 项目不存在; 编码冲突 |
|
||
| PROJ-API-008 | DELETE | `/{id}` | 删除项目 | id(UUID, Path) | `{code, data: null}` | 管理员 | 项目不存在; 有关联数据 |
|
||
| PROJ-API-009 | GET | `/{id}/members` | 获取项目成员列表 | id(UUID, Path), page(int, 默认0), size(int, 默认20) | `{code, data: PageResponse<ProjectMemberDTO>}` | 项目成员 | 项目不存在 |
|
||
| PROJ-API-010 | POST | `/{id}/members` | 添加项目成员 | id(UUID, Path), Body: AddMemberRequest `{userIds: [UUID], roleInProject: String}` | `{code, data: null}` | 项目管理员 | 项目不存在; 用户不存在; 已是成员 |
|
||
| PROJ-API-011 | DELETE | `/{id}/members/{memberId}` | 移除项目成员 | id(UUID, Path), memberId(UUID, Path) | `{code, data: null}` | 项目管理员 | 成员不存在 |
|
||
| PROJ-API-012 | GET | `/{id}/statistics` | 获取项目统计数据 | id(UUID, Path) | `{code, data: ProjectStatistics}` | 项目成员 | 项目不存在 |
|
||
| PROJ-API-013 | PUT | `/{id}/status` | 变更项目状态 | id(UUID, Path), Body: ChangeStatusRequest `{status: String, reason?: String}` | `{code, data: null}` | 管理员 | 项目不存在; 状态流转非法 |
|
||
| PROJ-API-014 | GET | `/{id}/config` | 获取项目配置 | id(UUID, Path) | `{code, data: ProjectConfigDTO}` | 项目成员 | 项目不存在 |
|
||
| PROJ-API-015 | PUT | `/{id}/config` | 更新项目配置 | id(UUID, Path), Body: ProjectConfigDTO | `{code, data: ProjectConfigDTO}` | 项目管理员 | 项目不存在 |
|
||
| PROJ-API-016 | GET | `/{projectId}/delete-check` | 项目删除前检查 | projectId(UUID, Path) | `{code, data: ProjectDeleteCheckVO}` | 管理员 | 项目不存在 |
|
||
|
||
**关键DTO**:
|
||
|
||
```typescript
|
||
// ProjectQueryRequest
|
||
{ keyword?: string; status?: string; page?: number; size?: number; sortBy?: string; sortDirection?: string }
|
||
|
||
// ChangeStatusRequest
|
||
{ status: string; reason?: string }
|
||
|
||
// AddMemberRequest
|
||
{ userIds: string[]; roleInProject: string }
|
||
|
||
// ProjectDeleteCheckVO
|
||
{ canDelete: boolean; reason?: string; statistics: ProjectDeleteStatistics }
|
||
|
||
// ProjectSelectorItem
|
||
{ id: UUID; code: string; name: string; status: string }
|
||
```
|
||
|
||
---
|
||
|
||
### 3.2 SpaceNodeController -- `/api/mdm/space-nodes`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| SN-API-001 | GET | `/` | 分页查询空间节点 | page(int, 默认0), size(int, 默认10) | `{code, data: Page<SpaceNode>}` | 已登录 | 分页参数越界 |
|
||
| SN-API-002 | GET | `/{id}` | 查询节点详情 | id(UUID, Path) | `{code, data: SpaceNode}` | 已登录 | 节点不存在 |
|
||
| SN-API-003 | GET | `/project/{projectId}` | 按项目查询节点列表 | projectId(UUID, Path) | `{code, data: [SpaceNode]}` | 项目成员 | 项目不存在 |
|
||
| SN-API-004 | GET | `/project/{projectId}/tree` | 获取项目空间树 | projectId(UUID, Path) | `{code, data: [SpaceNodeTreeDTO]}` | 项目成员 | 项目不存在 |
|
||
| SN-API-005 | GET | `/project/{projectId}/roots` | 获取项目根节点 | projectId(UUID, Path) | `{code, data: [SpaceNode]}` | 项目成员 | 项目不存在 |
|
||
| SN-API-006 | GET | `/project/{projectId}/type/{nodeType}` | 按项目+类型查询 | projectId(UUID, Path), nodeType(NodeType, Path) | `{code, data: [SpaceNode]}` | 项目成员 | 无效NodeType |
|
||
| SN-API-007 | GET | `/parent/{parentId}/children` | 获取子节点列表 | parentId(UUID, Path) | `{code, data: [SpaceNode]}` | 已登录 | 父节点不存在 |
|
||
| SN-API-008 | POST | `/` | 创建空间节点 | Body: SpaceNodeCreateDTO | `{code, data: SpaceNode}` | 项目管理员 | 项目不存在; 父节点不存在; 字段校验失败 |
|
||
| SN-API-009 | POST | `/batch` | 批量创建节点 | Body: [SpaceNodeCreateDTO] | `{code, data: [SpaceNode]}` | 项目管理员 | 批量数量超限; 父节点不存在 |
|
||
| SN-API-010 | PUT | `/{id}` | 更新节点 | id(UUID, Path), Body: SpaceNodeUpdateDTO | `{code, data: SpaceNode}` | 项目管理员 | 节点不存在 |
|
||
| SN-API-011 | DELETE | `/{id}` | 删除节点 | id(UUID, Path) | `{code, data: null}` | 项目管理员 | 节点不存在; 有子节点 |
|
||
| SN-API-012 | GET | `/{id}/delete-check` | 删除前检查 | id(UUID, Path) | `{code, data: SpaceNodeDeleteCheckDTO}` | 项目管理员 | 节点不存在 |
|
||
| SN-API-013 | DELETE | `/{id}/cascade` | 级联删除(含子节点) | id(UUID, Path) | `{code, data: null}` | 项目管理员 | 节点不存在 |
|
||
| SN-API-014 | GET | `/{id}/equipment` | 获取设备详情 | id(UUID, Path) | `{code, data: SpaceNodeEquipmentDTO}` | 项目成员(@Deprecated) | 节点不存在 |
|
||
| SN-API-015 | GET | `/equipment` | 获取设备列表 | projectId(UUID, Param) | `{code, data: [SpaceNodeEquipmentDTO]}` | 项目成员(@Deprecated) | 项目不存在 |
|
||
| SN-API-016 | GET | `/special-equipment` | 获取特种设备列表 | projectId(UUID, Param) | `{code, data: [SpaceNodeEquipmentDTO]}` | 项目成员(@Deprecated) | 项目不存在 |
|
||
| SN-API-017 | GET | `/expiring-inspection` | 获取即将年检设备 | projectId(UUID, Param), daysAhead(Integer, 默认90) | `{code, data: [SpaceNodeEquipmentDTO]}` | 项目成员(@Deprecated) | 项目不存在 |
|
||
| SN-API-018 | POST | `/equipment` | 创建设备 | Body: EquipmentCreateDTO | `{code, data: SpaceNode}` | 项目管理员(@Deprecated) | 项目不存在; 字段校验失败 |
|
||
| SN-API-019 | POST | `/equipment/batch` | 批量创建设备 | Body: [EquipmentCreateDTO] | `{code, data: [SpaceNode]}` | 项目管理员(@Deprecated) | 批量数量超限 |
|
||
| SN-API-020 | POST | `/equipment/import` | Excel导入设备 | file(Multipart), projectId(UUID, Param) | `{code, data: Object}` | 项目管理员(@Deprecated) | 文件为空; 文件类型不支持; 文件超10MB; 行数超1000 |
|
||
| SN-API-021 | GET | `/{buildingId}/floor-info` | 获取楼栋楼层信息 | buildingId(String, Path) | `{code, data: FloorInfoVO}` | 项目成员 | 楼栋不存在 |
|
||
| SN-API-022 | GET | `/debug/floor-numbers` | 调试:检查房间楼层号 | projectId(UUID, Param) | `{code, data: Map}` | 管理员 | 项目不存在 |
|
||
|
||
**关键DTO**:
|
||
|
||
```typescript
|
||
// SpaceNodeCreateDTO
|
||
{ projectId: UUID; name: String; nodeCategory: NodeCategory; nodeType: NodeType; parentId?: UUID; buildingArea?: BigDecimal; ... }
|
||
|
||
// SpaceNodeTreeDTO extends SpaceNode
|
||
{ ...SpaceNode; children: SpaceNodeTreeDTO[] }
|
||
|
||
// SpaceNodeDeleteCheckDTO
|
||
{ nodeId: UUID; nodeName: String; childCount: Integer; childTypeCount: Map; totalDescendantCount: Integer }
|
||
|
||
// EquipmentCreateDTO
|
||
{ projectId: UUID; name: String; nodeType: NodeType; parentId?: UUID; ...设备扩展字段 }
|
||
|
||
// FloorInfoVO
|
||
{ buildingId: UUID; buildingName: String; totalFloors: Integer; undergroundFloors: Integer; floors: FloorDetailVO[] }
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 InspectionItemController -- `/api/mdm/inspection-items`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| II-API-001 | POST | `/` | 创建巡检标准项 | Body: InspectionItem | `{code, data: InspectionItem}` | 管理员 | 字段校验失败 |
|
||
| II-API-002 | GET | `/` | 查询巡检标准项列表 | equipmentType?(String), systemType?(String), activeOnly?(Boolean) | `{code, data: [InspectionItem]}` | 已登录 | 无 |
|
||
| II-API-003 | GET | `/{id}` | 获取标准项详情 | id(UUID, Path) | `{code, data: InspectionItem}` | 已登录 | 标准项不存在 |
|
||
| II-API-004 | PUT | `/{id}` | 更新标准项 | id(UUID, Path), Body: InspectionItem | `{code, data: InspectionItem}` | 管理员 | 标准项不存在 |
|
||
| II-API-005 | DELETE | `/{id}` | 删除标准项 | id(UUID, Path) | `{code, data: null}` | 管理员 | 标准项不存在; 已被模板引用 |
|
||
|
||
**查询逻辑优先级**:
|
||
1. activeOnly=true → 仅返回ACTIVE状态项
|
||
2. equipmentType + systemType → 双条件过滤
|
||
3. equipmentType → 单条件过滤
|
||
4. systemType → 系统类型过滤
|
||
5. 无参数 → 返回全部
|
||
|
||
---
|
||
|
||
### 3.4 InspectionTemplateController -- `/api/ops/inspection-templates`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| IT-API-001 | GET | `/` | 获取项目模板列表 | projectId(UUID, Param) | `{code, data: [InspectionTemplate]}` | 项目成员 | 项目不存在 |
|
||
| IT-API-002 | POST | `/` | 创建模板 | Body: InspectionTemplate | `{code, data: InspectionTemplate}` | 管理员 | 模板编码已存在 |
|
||
| IT-API-003 | GET | `/{id}` | 获取模板详情 | id(UUID, Path) | `{code, data: InspectionTemplate}` | 已登录 | 模板不存在 |
|
||
| IT-API-004 | PUT | `/{id}` | 更新模板 | id(UUID, Path), Body: InspectionTemplate | `{code, data: InspectionTemplate}` | 管理员 | 模板不存在 |
|
||
| IT-API-005 | POST | `/{id}/copy` | 复制模板 | id(UUID, Path), newName(String, Param) | `{code, data: InspectionTemplate}` | 管理员 | 模板不存在 |
|
||
| IT-API-006 | GET | `/by-type/{equipmentType}` | 按设备类型查模板 | equipmentType(String, Path) | `{code, data: [InspectionTemplate]}` | 已登录 | 无 |
|
||
|
||
---
|
||
|
||
### 3.5 InspectionRecordController -- `/api/mdm/inspection-records`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| IR-API-001 | POST | `/` | 创建巡检记录 | Body: InspectionRecord | `{code, data: InspectionRecord}` | 项目成员 | 设备不存在; 字段校验失败 |
|
||
| IR-API-002 | GET | `/` | 查询巡检记录列表 | equipmentId?(UUID), planId?(UUID), inspector?(String), status?(CheckStatus), startDate?(LocalDate), endDate?(LocalDate) | `{code, data: [InspectionRecord]}` | 项目成员 | 无 |
|
||
| IR-API-003 | GET | `/{id}` | 获取记录详情 | id(UUID, Path) | `{code, data: InspectionRecord}` | 已登录 | 记录不存在 |
|
||
| IR-API-004 | PUT | `/{id}` | 更新记录 | id(UUID, Path), Body: InspectionRecord | `{code, data: InspectionRecord}` | 项目成员 | 记录不存在 |
|
||
| IR-API-005 | DELETE | `/{id}` | 删除记录 | id(UUID, Path) | `{code, data: null}` | 管理员 | 记录不存在 |
|
||
| IR-API-006 | POST | `/{id}/complete` | 完成巡检记录 | id(UUID, Path) | `{code, data: null}` | 项目成员 | 记录不存在; 已完成 |
|
||
|
||
**查询逻辑优先级**:
|
||
1. equipmentId + 日期范围 → 设备+日期
|
||
2. equipmentId → 设备
|
||
3. planId → 计划
|
||
4. inspector → 巡检人
|
||
5. status → 状态
|
||
6. 日期范围 → 时间段
|
||
7. 无参数 → 全部
|
||
|
||
---
|
||
|
||
### 3.6 SparePartController -- `/api/ops/spare-parts`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| SP-API-001 | GET | `/categories` | 获取分类列表 | 无 | `{code, data: [SparePartCategory]}` | 已登录 | 无 |
|
||
| SP-API-002 | POST | `/categories` | 创建分类 | Body: SparePartCategory | `{code, data: SparePartCategory}` | 管理员 | 分类编码已存在 |
|
||
| SP-API-003 | GET | `/` | 获取备件列表 | projectId(UUID, Param), categoryId?(UUID) | `{code, data: [SparePart]}` | 项目成员 | 项目不存在 |
|
||
| SP-API-004 | GET | `/{id}` | 获取备件详情 | id(UUID, Path) | `{code, data: SparePart}` | 已登录 | 备件不存在 |
|
||
| SP-API-005 | POST | `/` | 创建备件 | Body: SparePart | `{code, data: SparePart}` | 管理员 | 备件编码已存在 |
|
||
| SP-API-006 | PUT | `/{id}` | 更新备件 | id(UUID, Path), Body: SparePart | `{code, data: SparePart}` | 管理员 | 备件不存在 |
|
||
| SP-API-007 | DELETE | `/{id}` | 删除备件 | id(UUID, Path) | `{code, data: null}` | 管理员 | 备件不存在; 有出入库记录 |
|
||
| SP-API-008 | GET | `/low-stock` | 获取低库存备件 | projectId(UUID, Param) | `{code, data: [SparePart]}` | 项目成员 | 项目不存在 |
|
||
| SP-API-009 | POST | `/in-stock` | 入库操作 | Body: StockRequest `{sparePartId, quantity, recordedBy?, remarks?}` | `{code, data: null}` | 项目成员 | 备件不存在; 数量<=0 |
|
||
| SP-API-010 | POST | `/out-stock` | 出库操作 | Body: OutStockRequest `{sparePartId, quantity, relatedOrderId?, recordedBy?, remarks?}` | `{code, data: null}` | 项目成员 | 备件不存在; 库存不足; 数量<=0 |
|
||
| SP-API-011 | GET | `/{id}/records` | 获取备件出入库记录 | id(UUID, Path) | `{code, data: [SparePartRecord]}` | 已登录 | 备件不存在 |
|
||
|
||
---
|
||
|
||
### 3.7 EnergyController -- `/api/ops/energy`
|
||
|
||
| 编号 | 方法 | 路径 | 说明 | 请求参数 | 响应格式 | 权限要求 | 例外情况 |
|
||
|------|------|------|------|----------|----------|----------|----------|
|
||
| EN-API-001 | POST | `/meters` | 创建计量点 | Body: EnergyMeter | `{code, data: EnergyMeter}` | 管理员 | 计量点编码已存在 |
|
||
| EN-API-002 | GET | `/meters` | 获取计量点列表 | projectId(UUID, Param), energyType?(String) | `{code, data: [EnergyMeter]}` | 项目成员 | 项目不存在 |
|
||
| EN-API-003 | GET | `/meters/{id}` | 获取计量点详情 | id(UUID, Path) | `{code, data: EnergyMeter}` | 已登录 | 计量点不存在 |
|
||
| EN-API-004 | PUT | `/meters/{id}` | 更新计量点 | id(UUID, Path), Body: EnergyMeter | `{code, data: EnergyMeter}` | 管理员 | 计量点不存在 |
|
||
| EN-API-005 | DELETE | `/meters/{id}` | 删除计量点 | id(UUID, Path) | `{code, data: null}` | 管理员 | 计量点不存在; 有能耗记录 |
|
||
| EN-API-006 | POST | `/consumption` | 录入能耗记录 | Body: RecordConsumptionRequest `{meterId, currentReading, recordedBy?}` | `{code, data: null}` | 项目成员 | 计量点不存在; 读数小于上次 |
|
||
| EN-API-007 | GET | `/consumption/{meterId}` | 获取能耗记录 | meterId(UUID, Path), startDate?(LocalDate), endDate?(LocalDate) | `{code, data: [EnergyConsumption]}` | 项目成员 | 计量点不存在 |
|
||
| EN-API-008 | GET | `/statistics/by-type` | 按类型统计能耗 | projectId(UUID, Param), month(String, Param) | `{code, data: Map<EnergyType, BigDecimal>}` | 项目成员 | 项目不存在 |
|
||
| EN-API-009 | GET | `/statistics/unit-consumption` | 单位面积能耗 | projectId(UUID, Param), month(String, Param) | `{code, data: BigDecimal}` | 项目成员 | 项目不存在 |
|
||
|
||
---
|
||
|
||
## 四、业务规则
|
||
|
||
### 4.1 空间节点树形管理(创建时自动生成treePath/删除前检查/级联删除)
|
||
|
||
#### 4.1.1 树形结构规则
|
||
|
||
```
|
||
项目 (Project)
|
||
├── 楼栋 (BUILDING) ← NodeCategory.BUILDING, order=1
|
||
│ ├── 单元 (UNIT) ← NodeCategory.BUILDING, order=2
|
||
│ │ ├── 楼层 (FLOOR) ← NodeCategory.BUILDING, order=3
|
||
│ │ │ └── 房间 (ROOM) ← NodeCategory.BUILDING, order=4
|
||
│ │ └── 房间 (ROOM) ← 可跳过楼层直接挂房间
|
||
│ └── 公共用房 (PUBLIC_ROOM)
|
||
├── 商铺 (SHOP) ← NodeCategory.BUILDING, order=2(直接挂在项目下)
|
||
├── 车库 (GARAGE) ← NodeCategory.PARKING, order=1
|
||
│ ├── 停车区域 (PARKING_AREA) ← NodeCategory.PARKING, order=2
|
||
│ │ └── 车位 (PARKING_SPACE) ← NodeCategory.PARKING, order=3
|
||
├── 设备房 (EQUIPMENT_ROOM) ← NodeCategory.FACILITY, order=1
|
||
├── 物业用房 (PROPERTY_OFFICE)
|
||
├── 门岗 (SECURITY_ROOM)
|
||
├── 公共区域 (PUBLIC_AREA) ← NodeCategory.AREA, order=1
|
||
├── 绿化区域 (GREEN_AREA)
|
||
└── 道路 (ROAD)
|
||
```
|
||
|
||
#### 4.1.2 树形路径维护
|
||
|
||
- **treePath**: 物理路径,格式 `id1.id2.id3`,用于快速查询所有子孙节点
|
||
- 创建节点时: `父节点.treePath + "." + 当前节点.id`(根节点为自身id)
|
||
- 支持通过 `LIKE 'parentId.%'` 快速查询所有子孙
|
||
|
||
- **treePathName**: 名称路径,格式 `项目/楼栋/单元/房间`,用于展示
|
||
- 创建节点时: `父节点.treePathName + "/" + 当前节点.name`
|
||
|
||
- **level**: 层级深度,根节点为0,每层+1
|
||
- 创建节点时: `父节点.level + 1`
|
||
|
||
- **sortOrder**: 同级排序,默认0
|
||
|
||
#### 4.1.3 删除规则
|
||
|
||
1. **删除前检查**: 调用 `GET /{id}/delete-check` 获取子节点信息
|
||
- 返回: `{nodeId, nodeName, childCount, childTypeCount, totalDescendantCount}`
|
||
|
||
2. **普通删除**: `DELETE /{id}`
|
||
- 有子节点时拒绝删除
|
||
- 软删除: 设置 `isDeleted=true`
|
||
|
||
3. **级联删除**: `DELETE /{id}/cascade`
|
||
- 删除当前节点及所有子孙节点
|
||
- 软删除: 所有节点设置 `isDeleted=true`
|
||
|
||
#### 4.1.4 批量操作
|
||
|
||
- **批量创建**: `POST /batch`,受 `BatchOperationValidator.validateUpdateSize()` 限制数量
|
||
- **Excel导入设备**: `POST /equipment/import`
|
||
- 支持格式: .xlsx, .xls
|
||
- 文件大小限制: 10MB
|
||
- 行数限制: 1000行
|
||
- 文件类型校验: contentType + 扩展名双重校验
|
||
|
||
---
|
||
|
||
### 4.2 项目状态流转(ACTIVE/DISABLED/PENDING/ARCHIVED的转换规则)
|
||
|
||
#### 4.2.1 状态值与转换规则
|
||
|
||
| 当前状态 | 可流转到 | 说明 |
|
||
|---------|---------|------|
|
||
| PENDING | ACTIVE | 审核通过 |
|
||
| PENDING | DISABLED | 审核拒绝 |
|
||
| ACTIVE | DISABLED | 禁用项目 |
|
||
| ACTIVE | ARCHIVED | 归档项目 |
|
||
| DISABLED | ACTIVE | 重新启用 |
|
||
| DISABLED | ARCHIVED | 归档禁用项目 |
|
||
| ARCHIVED | - | 终态,不可再变更 |
|
||
|
||
#### 4.2.2 状态变更规则
|
||
|
||
- 每次状态变更必须记录 `ProjectStatusHistory`(fromStatus, toStatus, reason, operatorId, operatorName)
|
||
- 变更接口: `PUT /{id}/status`,请求体: `{status, reason}`
|
||
- 非法状态转换抛出 BusinessException
|
||
|
||
#### 4.2.3 项目成员角色
|
||
|
||
| 角色 | 说明 |
|
||
|------|------|
|
||
| PROJECT_MANAGER | 项目经理 |
|
||
| PROJECT_ADMIN | 项目管理员 |
|
||
| OPERATION_STAFF | 运营人员 |
|
||
| FINANCE_STAFF | 财务人员 |
|
||
| VIEWER | 查看者 |
|
||
|
||
---
|
||
|
||
### 4.3 空间编码自动生成(BLD-{projSuffix}-{seq}格式)
|
||
|
||
> **当前状态**: SpaceNode实体有 `code` 字段(VARCHAR(50), @JsonIgnore),但自动编码规则尚未完整实现。
|
||
|
||
**设计规则**(参考原需求 02-SPACE_NODE_DESIGN.md):
|
||
|
||
| NodeType | 编码格式 | 示例 |
|
||
|----------|---------|------|
|
||
| BUILDING | `BLD-{projSuffix}-{seq}` | BLD-XY-001 |
|
||
| UNIT | `BLD-{projSuffix}-{bldSeq}-{seq}` | BLD-XY-001-01 |
|
||
| FLOOR | `BLD-{projSuffix}-{bldSeq}-F{floorNo}` | BLD-XY-001-F3 |
|
||
| ROOM | `BLD-{projSuffix}-{bldSeq}-{unitSeq}-{seq}` | BLD-XY-001-01-0301 |
|
||
| GARAGE | `GRG-{projSuffix}-{seq}` | GRG-XY-001 |
|
||
| PARKING_SPACE | `GRG-{projSuffix}-{grgSeq}-{seq}` | GRG-XY-001-A001 |
|
||
| EQUIPMENT_ROOM | `EQP-{projSuffix}-{seq}` | EQP-XY-001 |
|
||
|
||
**唯一性约束**: 同项目同类型下编码唯一(原设计 `UNIQUE(project_id, code)`,当前代码未实现此约束)
|
||
|
||
---
|
||
|
||
### 4.4 巡检标准项管理(按设备类型/系统类型分类)
|
||
|
||
#### 4.4.1 标准项库
|
||
|
||
- 巡检标准项按 `equipmentType`(设备类型)和 `systemType`(系统类型)分类
|
||
- 每个标准项包含: 检查项名称、检查方法、标准值、是否必检
|
||
- 标准项状态: ACTIVE(启用) / INACTIVE(停用)
|
||
- 支持按 `activeOnly=true` 仅查询启用项
|
||
|
||
#### 4.4.2 巡检模板
|
||
|
||
- 模板按项目+设备类型组织
|
||
- 模板包含检查项列表(JSON格式存储,字段 `inspectionItems`)
|
||
- 支持模板版本管理(`version` 字段)
|
||
- 支持模板复制(`POST /{id}/copy`)
|
||
- 模板与标准项通过 `equipmentType` 逻辑关联
|
||
|
||
#### 4.4.3 巡检记录
|
||
|
||
- 记录关联设备(`equipmentId`)和计划(`planId`)
|
||
- 检查结果状态: NORMAL(正常) / WARNING(预警) / ABNORMAL(异常)
|
||
- 支持签到信息: 时间(`checkInTime`)、位置(`checkInLocation`)、照片(`checkInPhoto`)
|
||
- 检查项结果和异常问题以JSONB存储
|
||
- 完成操作: 调用 `POST /{id}/complete` 标记完成
|
||
|
||
---
|
||
|
||
### 4.5 巡检记录管理(签到/问题上报/完成确认)
|
||
|
||
#### 4.5.1 创建巡检记录流程
|
||
|
||
1. 选择设备(`equipmentId`)
|
||
2. 填写巡检日期(`inspectionDate`)和巡检人(`inspector`)
|
||
3. 逐项填写检查结果(`items` JSONB)
|
||
4. 上报异常问题(`problems` JSONB)
|
||
5. 签到: 记录时间、位置、照片
|
||
|
||
#### 4.5.2 完成确认
|
||
|
||
- 调用 `POST /{id}/complete`
|
||
- 设置 `completed=true`, `completedTime=当前时间`
|
||
- 已完成的记录不可再修改
|
||
|
||
#### 4.5.3 状态判定
|
||
|
||
- NORMAL: 所有检查项通过,无异常问题
|
||
- WARNING: 存在轻微异常
|
||
- ABNORMAL: 存在严重异常
|
||
|
||
---
|
||
|
||
## 五、执行约束
|
||
|
||
### 5.1 项目编码唯一性
|
||
|
||
- **数据库约束**: `mdm_project.code` 列 UNIQUE
|
||
- **校验规则**: 正则 `^[a-zA-Z0-9_-]+$`,长度2-50位
|
||
- **违反后果**: 创建/更新项目时抛出 DataIntegrityViolationException
|
||
- **自动生成**: `GET /generate-code` 接口生成唯一编码
|
||
|
||
### 5.2 空间节点树完整性(不能形成环/父节点必须存在)
|
||
|
||
- **约束说明**: 空间节点通过 parentId 自引用形成树形结构
|
||
- **父节点校验**: 创建/更新节点时,parentId 指向的节点必须存在且属于同一项目
|
||
- **环路检测**: 更新 parentId 时,沿 parentId 链向上遍历,检查是否会回到当前节点
|
||
- **违反后果**: 抛出 BusinessException,提示"不能将节点设置为其子节点的下级"
|
||
- **项目一致性**: 子节点的 projectId 必须与父节点一致
|
||
|
||
### 5.3 项目删除前检查(有空间节点/设备/工单时不能删除)
|
||
|
||
- **检查接口**: `GET /{projectId}/delete-check`
|
||
- **返回结果**: `ProjectDeleteCheckVO { canDelete, reason, statistics }`
|
||
- **阻止删除条件**:
|
||
- 存在空间节点(SpaceNode)
|
||
- 存在设备(isEquipment=true的SpaceNode)
|
||
- 存在应收未收费用
|
||
- 存在关联工单
|
||
- **违反后果**: canDelete=false,返回具体原因
|
||
|
||
### 5.4 空间编码唯一性(同项目同类型下唯一)
|
||
|
||
- **约束说明**: 同一项目下,相同 NodeType 的节点 code 应唯一
|
||
- **当前状态**: 数据库层面未建立 `UNIQUE(project_id, code)` 约束
|
||
- **业务层校验**: 创建/更新节点时检查同项目同类型下编码是否重复
|
||
- **违反后果**: 抛出 BusinessException
|
||
|
||
### 5.5 备件出库库存约束
|
||
|
||
- **约束说明**: 出库数量不能超过当前库存
|
||
- **校验方式**: `currentStock - quantity >= 0`
|
||
- **违反后果**: 抛出 BusinessException,提示"库存不足"
|
||
|
||
### 5.6 能耗读数递增约束
|
||
|
||
- **约束说明**: 本次读数必须大于上次读数
|
||
- **校验方式**: `currentReading > previousReading`
|
||
- **违反后果**: 抛出 BusinessException,提示"本次读数不能小于上次读数"
|
||
|
||
---
|
||
|
||
## 六、权限控制
|
||
|
||
### 6.1 API端点权限矩阵
|
||
|
||
| 控制器 | 端点 | 所需角色 | 数据范围过滤 |
|
||
|--------|------|----------|-------------|
|
||
| ProjectController | GET / | 已登录用户 | ALL/PROJECT(按用户项目过滤) |
|
||
| ProjectController | GET /selector | 已登录用户 | PROJECT(仅用户参与的项目) |
|
||
| ProjectController | GET /generate-code | 管理员 | 无 |
|
||
| ProjectController | GET /{id} | 项目成员 | PROJECT |
|
||
| ProjectController | GET /code/{code} | 项目成员 | PROJECT |
|
||
| ProjectController | POST / | 管理员(SYSTEM级) | 无 |
|
||
| ProjectController | PUT /{id} | 项目管理员 | PROJECT |
|
||
| ProjectController | DELETE /{id} | 管理员(SYSTEM级) | 无 |
|
||
| ProjectController | GET /{id}/members | 项目成员 | PROJECT |
|
||
| ProjectController | POST /{id}/members | 项目管理员 | PROJECT |
|
||
| ProjectController | DELETE /{id}/members/{memberId} | 项目管理员 | PROJECT |
|
||
| ProjectController | GET /{id}/statistics | 项目成员 | PROJECT |
|
||
| ProjectController | PUT /{id}/status | 管理员(SYSTEM级) | 无 |
|
||
| ProjectController | GET /{id}/config | 项目成员 | PROJECT |
|
||
| ProjectController | PUT /{id}/config | 项目管理员 | PROJECT |
|
||
| ProjectController | GET /{projectId}/delete-check | 管理员(SYSTEM级) | 无 |
|
||
| SpaceNodeController | GET / | 已登录用户 | ALL/PROJECT |
|
||
| SpaceNodeController | GET /{id} | 已登录用户 | PROJECT |
|
||
| SpaceNodeController | GET /project/{projectId}/* | 项目成员 | PROJECT |
|
||
| SpaceNodeController | POST / | 项目管理员 | PROJECT |
|
||
| SpaceNodeController | POST /batch | 项目管理员 | PROJECT |
|
||
| SpaceNodeController | PUT /{id} | 项目管理员 | PROJECT |
|
||
| SpaceNodeController | DELETE /{id} | 项目管理员 | PROJECT |
|
||
| SpaceNodeController | DELETE /{id}/cascade | 项目管理员 | PROJECT |
|
||
| SpaceNodeController | POST /equipment* | 项目管理员(@Deprecated) | PROJECT |
|
||
| SpaceNodeController | GET /{buildingId}/floor-info | 项目成员 | PROJECT |
|
||
| InspectionItemController | GET / | 已登录用户 | ALL |
|
||
| InspectionItemController | GET /{id} | 已登录用户 | ALL |
|
||
| InspectionItemController | POST / | 管理员 | 无 |
|
||
| InspectionItemController | PUT /{id} | 管理员 | 无 |
|
||
| InspectionItemController | DELETE /{id} | 管理员 | 无 |
|
||
| InspectionTemplateController | GET / | 项目成员 | PROJECT |
|
||
| InspectionTemplateController | POST / | 管理员 | 无 |
|
||
| InspectionTemplateController | GET /{id} | 已登录用户 | PROJECT |
|
||
| InspectionTemplateController | PUT /{id} | 管理员 | 无 |
|
||
| InspectionTemplateController | POST /{id}/copy | 管理员 | 无 |
|
||
| InspectionRecordController | POST / | 项目成员 | PROJECT |
|
||
| InspectionRecordController | GET / | 项目成员 | PROJECT |
|
||
| InspectionRecordController | GET /{id} | 已登录用户 | PROJECT |
|
||
| InspectionRecordController | PUT /{id} | 项目成员 | PROJECT |
|
||
| InspectionRecordController | DELETE /{id} | 管理员 | 无 |
|
||
| InspectionRecordController | POST /{id}/complete | 项目成员 | PROJECT |
|
||
| SparePartController | GET /categories | 已登录用户 | ALL |
|
||
| SparePartController | POST /categories | 管理员 | 无 |
|
||
| SparePartController | GET / | 项目成员 | PROJECT |
|
||
| SparePartController | GET /{id} | 已登录用户 | ALL |
|
||
| SparePartController | POST / | 管理员 | PROJECT |
|
||
| SparePartController | PUT /{id} | 管理员 | PROJECT |
|
||
| SparePartController | DELETE /{id} | 管理员 | PROJECT |
|
||
| SparePartController | GET /low-stock | 项目成员 | PROJECT |
|
||
| SparePartController | POST /in-stock | 项目成员 | PROJECT |
|
||
| SparePartController | POST /out-stock | 项目成员 | PROJECT |
|
||
| SparePartController | GET /{id}/records | 已登录用户 | ALL |
|
||
| EnergyController | POST /meters | 管理员 | PROJECT |
|
||
| EnergyController | GET /meters | 项目成员 | PROJECT |
|
||
| EnergyController | GET /meters/{id} | 已登录用户 | ALL |
|
||
| EnergyController | PUT /meters/{id} | 管理员 | PROJECT |
|
||
| EnergyController | DELETE /meters/{id} | 管理员 | PROJECT |
|
||
| EnergyController | POST /consumption | 项目成员 | PROJECT |
|
||
| EnergyController | GET /consumption/{meterId} | 项目成员 | PROJECT |
|
||
| EnergyController | GET /statistics/* | 项目成员 | PROJECT |
|
||
|
||
---
|
||
|
||
## 七、例外情况处理
|
||
|
||
### 7.1 项目相关例外
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 项目不存在 | 404 | "项目不存在" | findById返回空时抛出 |
|
||
| 项目编码已存在 | 409 | "项目编码{code}已存在" | 创建/更新前查询 |
|
||
| 项目编码格式错误 | 400 | "项目代码只能包含字母、数字、连字符和下划线" | 正则校验 |
|
||
| 项目名称为空 | 400 | "项目名称不能为空" | @NotNull校验 |
|
||
| 联系电话格式错误 | 400 | "联系电话格式不正确" | 正则校验 |
|
||
| 非法状态转换 | 400 | "不允许从{from}变更为{to}" | 状态机校验 |
|
||
| 删除项目有空间节点 | 400 | "项目下存在空间节点,无法删除" | delete-check检查 |
|
||
| 删除项目有应收费用 | 400 | "项目存在应收未收费用,无法删除" | delete-check检查 |
|
||
| 项目已归档不可变更 | 400 | "已归档项目不可操作" | ARCHIVED终态检查 |
|
||
|
||
### 7.2 空间节点相关例外
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 节点不存在 | 404 | "空间节点不存在" | findById返回空时抛出 |
|
||
| 父节点不存在 | 400 | "父节点不存在" | 创建/更新时校验parentId |
|
||
| 父节点不属于同一项目 | 400 | "父节点不属于当前项目" | projectId一致性校验 |
|
||
| 有子节点不能普通删除 | 400 | "该节点存在{count}个子节点,请使用级联删除" | delete-check检查 |
|
||
| 树形结构形成环 | 400 | "不能将节点设置为其子节点的下级" | parentId链遍历检查 |
|
||
| 节点名称为空 | 400 | "空间节点名称不能为空" | @NotNull校验 |
|
||
| nodeType与nodeCategory不匹配 | 400 | "节点类型与节点大类不匹配" | 枚举关联校验 |
|
||
| 批量创建数量超限 | 400 | "批量操作数量超过限制" | BatchOperationValidator校验 |
|
||
|
||
### 7.3 设备相关例外(@Deprecated)
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 设备不存在 | 404 | "设备不存在" | findById返回空时抛出 |
|
||
| Excel文件为空 | 400 | "文件不能为空" | file.isEmpty()检查 |
|
||
| Excel文件类型不支持 | 400 | "不支持的文件类型,仅支持 Excel 文件(.xlsx, .xls)" | contentType+扩展名校验 |
|
||
| Excel文件过大 | 400 | "文件大小不能超过 10MB" | file.getSize()检查 |
|
||
| Excel行数超限 | 400 | "Excel 行数不能超过 1000 行,当前 {n} 行" | POI解析行数检查 |
|
||
| Excel解析失败 | 400 | "文件解析失败: {message}" | IOException捕获 |
|
||
|
||
### 7.4 巡检相关例外
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 巡检标准项不存在 | 404 | "巡检标准项不存在" | findById返回空时抛出 |
|
||
| 标准项已被模板引用 | 400 | "该标准项已被巡检模板引用,无法删除" | 检查模板引用 |
|
||
| 巡检记录不存在 | 404 | "巡检记录不存在" | findById返回空时抛出 |
|
||
| 巡检记录已完成 | 400 | "巡检记录已完成,不可修改" | completed=true检查 |
|
||
| 设备ID不存在 | 400 | "设备不存在" | equipmentId校验 |
|
||
| 巡检模板编码已存在 | 409 | "模板编码{code}已存在" | 创建前查询 |
|
||
| 巡检模板不存在 | 404 | "巡检模板不存在" | findById返回空时抛出 |
|
||
|
||
### 7.5 备件相关例外
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 备件不存在 | 404 | "备件不存在" | findById返回空时抛出 |
|
||
| 备件编码已存在 | 409 | "备件编码{code}已存在" | 创建前查询 |
|
||
| 出库数量超过库存 | 400 | "库存不足,当前库存{stock},出库数量{qty}" | currentStock校验 |
|
||
| 出入库数量为0或负数 | 400 | "数量必须大于0" | quantity > 0校验 |
|
||
| 分类编码已存在 | 409 | "分类编码{code}已存在" | 创建前查询 |
|
||
| 备件有出入库记录不能删除 | 400 | "该备件存在出入库记录,无法删除" | 检查SparePartRecord |
|
||
|
||
### 7.6 能耗相关例外
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 计量点不存在 | 404 | "计量点不存在" | findById返回空时抛出 |
|
||
| 计量点编码已存在 | 409 | "计量点编码{code}已存在" | 创建前查询 |
|
||
| 本次读数小于上次读数 | 400 | "本次读数不能小于上次读数" | currentReading > previousReading |
|
||
| 计量点有能耗记录不能删除 | 400 | "该计量点存在能耗记录,无法删除" | 检查EnergyConsumption |
|
||
| 项目无建筑面积 | 400 | "项目无建筑面积数据,无法计算单位面积能耗" | 统计时校验 |
|
||
|
||
### 7.7 通用例外
|
||
|
||
| 例外场景 | 错误码 | 错误信息 | 处理方式 |
|
||
|---------|--------|---------|---------|
|
||
| 分页参数越界 | 200(自动修正) | 无(使用getSafeSize修正) | PaginationValidator校验 |
|
||
| 项目ID为空 | 400 | "项目ID不能为空" | @NotNull校验 |
|
||
| 无权限访问项目数据 | 403 | "无权访问该项目数据" | DataScopeService校验 |
|