docs: 文档体系重构
- 合并需求文档为SYSTEM-REQUIREMENTS.md(214项需求) - 新增概要设计OVERVIEW-DESIGN.md - 新增7份详细设计(AUTH/SPACE/ASSET/OPS/FINANCE/FRONTEND/CROSS-DOMAIN) - 新增进度追踪体系progress.yml - 新增测试计划TEST-PLAN.md - 新增API测试脚本骨架(auth/asset/ops) - 新增E2E测试项目骨架(Playwright) - 旧文档归档至_archive目录
This commit is contained in:
parent
5619e6943b
commit
cd86a97007
|
|
@ -0,0 +1,857 @@
|
||||||
|
# Ether 物业管理系统 - 系统需求规格说明书
|
||||||
|
|
||||||
|
**文档版本**: v1.0
|
||||||
|
**创建日期**: 2026-05-18
|
||||||
|
**数据来源**: FEATURE_LIST.md / REVERSE-AUTH.md / REVERSE-MDM.md / REVERSE-ASSET.md / REVERSE-OPS.md / REVERSE-FINANCE.md / 00-项目总览.md
|
||||||
|
**维护原则**: 每个需求项标注唯一ID与实现状态,变更时同步更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、系统概述
|
||||||
|
|
||||||
|
### 1.1 系统定位与目标
|
||||||
|
|
||||||
|
Ether 物业管理平台是一个面向住宅物业和商业物业的全场景智慧管理解决方案,旨在通过数字化手段实现物业运营全流程闭环管理。
|
||||||
|
|
||||||
|
**核心目标**:
|
||||||
|
- 统一物业管理数据基座,消除信息孤岛
|
||||||
|
- 实现设备全生命周期管理,从采购安装到报废退役
|
||||||
|
- 建立运维工单闭环,覆盖报修、派单、执行、验收全流程
|
||||||
|
- 构建预防性维保体系,降低设备故障率
|
||||||
|
- 实现财务收费自动化,支持多种计费方式与支付渠道
|
||||||
|
- 提供多端协同能力(管理后台、员工APP、业主小程序、商办小程序)
|
||||||
|
|
||||||
|
**系统组成**:
|
||||||
|
|
||||||
|
| 应用 | 目录 | 端口 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 后端服务 | ether-pms | 8080 | Spring Boot 单体应用 |
|
||||||
|
| Web 管理后台 | ether-admin | 5175 | Vue3 + TypeScript + Ant Design Vue |
|
||||||
|
| 员工 APP | ether-app-employee | 5174 | 移动端应用 |
|
||||||
|
| 业主小程序 | ether-app-owner | 5176 | 微信/支付宝小程序 |
|
||||||
|
| 商办小程序 | ether-app-commercial | 5177 | 微信/支付宝小程序 |
|
||||||
|
|
||||||
|
**后端模块划分**:
|
||||||
|
|
||||||
|
| 模块 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| module-auth | 认证授权(用户/角色/权限/部门/审计日志) |
|
||||||
|
| module-mdm | 主数据管理(项目/空间节点/巡检标准/备件/能耗) |
|
||||||
|
| module-asset | 资产设备管理(设备台账/扩展表/健康评分/故障历史/归属主体) |
|
||||||
|
| module-wo | 运维工单(工单/维保计划/维保任务/巡检模板) |
|
||||||
|
| module-finance | 财务管理(待建设) |
|
||||||
|
| module-common | 公共组件与工具 |
|
||||||
|
|
||||||
|
### 1.2 用户角色定义
|
||||||
|
|
||||||
|
| 角色 | 说明 | 用户类型 | 典型操作 |
|
||||||
|
|------|------|----------|----------|
|
||||||
|
| 系统管理员 | 平台运维人员 | ENTERPRISE | 用户管理、角色配置、系统设置 |
|
||||||
|
| 项目经理 | 物业项目负责人 | ENTERPRISE | 项目管理、成员分配、工单审批 |
|
||||||
|
| 维保人员 | 设备维修保养人员 | PROJECT_STAFF | 接单、执行维保、填写报告 |
|
||||||
|
| 巡检人员 | 设备巡检执行人员 | PROJECT_STAFF | 执行巡检、签到、异常上报 |
|
||||||
|
| 业主 | 房屋产权人 | RESIDENT | 报修、缴费、查看公告 |
|
||||||
|
| 租户 | 房屋承租人 | RESIDENT | 报修、缴费 |
|
||||||
|
|
||||||
|
### 1.3 系统边界与约束
|
||||||
|
|
||||||
|
**系统边界**:
|
||||||
|
- 本系统管理物业项目从入驻到退场的全周期运营
|
||||||
|
- 涵盖设备资产从采购安装到报废退役的全生命周期
|
||||||
|
- 不包含房地产开发阶段的销售管理
|
||||||
|
- 不包含物业管理公司内部的人力资源管理(仅管理项目人员配置)
|
||||||
|
|
||||||
|
**技术约束**:
|
||||||
|
- 后端:Spring Boot 3.x 单体架构,PostgreSQL 数据库
|
||||||
|
- 前端:Vue3 + TypeScript + Ant Design Vue
|
||||||
|
- 认证:JWT Token(HMAC-SHA256),BCrypt 密码加密
|
||||||
|
- 数据隔离:基于项目维度的数据隔离(X-Project-ID Header)
|
||||||
|
- 移动端:uni-app 跨端框架
|
||||||
|
|
||||||
|
**业务约束**:
|
||||||
|
- 所有业务数据以项目为基本归属维度
|
||||||
|
- 用户可参与多个项目,需切换项目上下文
|
||||||
|
- 角色权限支持项目级隔离(系统级/项目级/部门级)
|
||||||
|
|
||||||
|
### 1.4 术语定义
|
||||||
|
|
||||||
|
| 术语 | 定义 |
|
||||||
|
|------|------|
|
||||||
|
| MDM | Master Data Management,主数据管理 |
|
||||||
|
| RBAC | Role-Based Access Control,基于角色的访问控制 |
|
||||||
|
| MTBF | Mean Time Between Failures,平均故障间隔时间 |
|
||||||
|
| MTTR | Mean Time To Repair,平均修复时间 |
|
||||||
|
| JWT | JSON Web Token,用于身份认证的令牌 |
|
||||||
|
| SpaceNode | 空间节点,统一空间体系中的树形节点 |
|
||||||
|
| WorkOrder | 工单,运维服务的业务单据 |
|
||||||
|
| MaintenanceTask | 维保任务,设备维保的执行单元 |
|
||||||
|
| OwnershipEntity | 归属主体,设备的所有权/管理权归属方 |
|
||||||
|
| DataScope | 数据范围,控制用户可访问的数据边界 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、业务域需求
|
||||||
|
|
||||||
|
### 2.1 身份与权限域
|
||||||
|
|
||||||
|
#### 2.1.1 用户管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-001 | 用户创建 | 支持创建用户,用户名3-50位字母数字下划线(正则`^[a-zA-Z0-9_]{3,50}$`),密码BCrypt加密存储,手机号格式校验(正则`^1[3-9]\d{9}$`),邮箱格式校验,用户类型必填(ENTERPRISE/PROJECT_STAFF/RESIDENT/CUSTOMER),默认状态ACTIVE | 高 | 已实现 | module-auth: UserController |
|
||||||
|
| REQ-AUTH-002 | 用户查询 | 支持分页查询用户列表,可按用户名/姓名/手机号/状态/用户类型筛选 | 高 | 已实现 | module-auth: UserController |
|
||||||
|
| REQ-AUTH-003 | 用户更新 | 支持更新用户基本信息(真实姓名/手机号/邮箱/头像/状态/部门),状态枚举:ACTIVE/LOCKED/DISABLED | 高 | 已实现 | module-auth: UserController |
|
||||||
|
| REQ-AUTH-004 | 用户删除 | 支持删除用户,删除时记录审计日志 | 中 | 已实现 | module-auth: UserController |
|
||||||
|
| REQ-AUTH-005 | 密码修改 | 需验证原密码,新密码需通过强度校验(8-20位,大小写+数字+特殊字符),弱密码检测(黑名单:password/123456/admin/qwerty/letmein/welcome/monkey/dragon),旧密码兼容检测(非BCrypt格式强制重置) | 高 | 已实现 | module-auth: UserController |
|
||||||
|
| REQ-AUTH-006 | 多类型用户扩展 | 企业员工(EnterpriseUser)含员工工号/部门/职位/入职日期/员工类别;项目员工(ProjectStaff)含所属项目/员工类型/班次/班组长/在岗状态;住户(Resident)含身份证/住户类型/认证状态 | 高 | 已实现 | module-auth: EnterpriseUser/ProjectStaff/Resident |
|
||||||
|
| REQ-AUTH-007 | 用户角色分配 | 支持为用户分配多个角色(覆盖模式),通过auth_user_role中间表关联 | 高 | 已实现 | module-auth: UserController.assignRoles |
|
||||||
|
| REQ-AUTH-008 | 用户项目关联 | 支持查看用户参与的项目列表、添加用户到项目、从项目移除用户,关联信息含项目角色(leader/member/viewer)和加入时间 | 高 | 已实现 | module-auth: UserController |
|
||||||
|
|
||||||
|
#### 2.1.2 角色与权限
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-010 | 角色管理 | 支持创建/查询/更新/删除角色,角色代码2-50位字母数字下划线(唯一),角色类型三级:SYSTEM(系统级)/PROJECT(项目级)/DEPARTMENT(部门级),角色状态:ENABLED/DISABLED | 高 | 已实现 | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-011 | 权限管理 | 支持创建/查询/更新/删除权限,权限代码格式`module:resource:action`(仅字母数字冒号下划线),权限类型:MENU/BUTTON/API,支持资源路径和HTTP方法(API类型),支持父权限代码(parentCode)构建权限树 | 高 | 已实现 | module-auth: PermissionController |
|
||||||
|
| REQ-AUTH-012 | 角色权限分配 | 支持为角色分配多个权限(覆盖模式),通过auth_role_permission中间表关联,支持查询角色权限列表 | 高 | 已实现 | module-auth: RoleController.assignPermissions |
|
||||||
|
| REQ-AUTH-013 | 四级数据范围 | 角色数据范围枚举:ALL(全部数据,不过滤)/PROJECT(本项目数据,过滤project_id)/DEPARTMENT(本部门数据,过滤project_id+dept_id)/SELF(仅本人数据,过滤assigned_to/creator) | 高 | 已实现 | module-auth: DataScopeService |
|
||||||
|
| REQ-AUTH-014 | 双层角色分配 | 用户直接角色(auth_user_role表)+ 项目员工角色(ProjectStaffRole表),登录时收集全部角色 = 用户直接角色 U 项目员工角色 | 高 | 已实现 | module-auth: AuthService |
|
||||||
|
| REQ-AUTH-015 | 项目级角色查询 | 支持按项目ID查询该项目下的角色列表 | 中 | 已实现 | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-016 | 角色关联用户查询 | 支持查询拥有指定角色的用户列表 | 中 | 已实现 | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-017 | 权限树端点 | 提供权限树结构查询接口(GET /permissions/tree) | 中 | 未实现 | - |
|
||||||
|
| REQ-AUTH-018 | 权限校验端点 | 提供权限校验接口(POST /check),验证用户是否拥有指定权限 | 中 | 未实现 | - |
|
||||||
|
| REQ-AUTH-019 | 用户菜单端点 | 提供用户菜单查询接口(GET /users/{id}/menus),返回用户可见菜单树 | 中 | 未实现 | - |
|
||||||
|
| REQ-AUTH-020 | 用户权限查询端点 | 提供用户权限查询接口(GET /users/{id}/permissions),返回用户全部权限列表 | 中 | 未实现 | - |
|
||||||
|
| REQ-AUTH-021 | 角色业务属性 | 角色扩展字段:businessType(业务类型)/terminalType(终端类型)/isFrontline(是否一线岗位) | 低 | 未实现 | - |
|
||||||
|
| REQ-AUTH-022 | 权限菜单路由属性 | 权限扩展字段:path(路由路径)/component(组件路径)/icon(图标)/level(层级) | 低 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.1.3 组织架构
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-030 | 部门管理 | 支持创建/查询/更新/删除部门,部门名称1-100位,支持树形结构(parentId自引用),部门类型5种:ADMIN(行政管理)/ENGINEERING(工程部)/SECURITY(安保部)/CS(客服部)/CLEANING(保洁部),部门状态:ACTIVE/DISABLED | 高 | 已实现 | module-auth: DeptController |
|
||||||
|
| REQ-AUTH-031 | 部门树查询 | 后端返回扁平列表,Controller层递归构建树形结构(DeptVO含children字段) | 高 | 已实现 | module-auth: DeptController.getDeptTree |
|
||||||
|
| REQ-AUTH-032 | 部门成员查询 | 支持查询指定部门下的成员列表 | 中 | 已实现 | module-auth: DeptController.getDeptMembers |
|
||||||
|
| REQ-AUTH-033 | 按类型查询部门 | 支持按部门类型(deptType)筛选部门列表 | 中 | 已实现 | module-auth: DeptController.getDeptsByType |
|
||||||
|
| REQ-AUTH-034 | 部门删除约束 | 删除部门前需先删除子部门,否则拒绝操作 | 高 | 已实现 | module-auth: DeptService |
|
||||||
|
|
||||||
|
#### 2.1.4 项目成员管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-040 | 项目成员列表 | 支持分页查询指定项目的成员列表 | 高 | 已实现 | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-041 | 可用成员查询 | 获取可添加到项目的企业员工列表,支持search参数模糊搜索(用户名/姓名) | 高 | 已实现 | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-042 | 添加项目成员 | 创建/更新ProjectStaff记录(设定员工类型、分配状态),覆盖模式更新角色(先删后加),员工类型6种:SECURITY/CLEANING/GARDEN/MAINTENANCE/CUSTOMER_SERVICE/GENERAL | 高 | 已实现 | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-043 | 移除项目成员 | 删除ProjectStaff记录(级联删除ProjectStaffRole角色关联) | 高 | 已实现 | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-044 | 项目员工班次 | 项目员工班次类型3种:DAY(白班)/NIGHT(夜班)/ROTATION(轮班),在岗状态3种:ASSIGNED(在岗)/ON_LEAVE(休假)/TRANSFERRED(已调离) | 中 | 已实现 | module-auth: ProjectStaff |
|
||||||
|
| REQ-AUTH-045 | 项目员工角色 | 项目员工在项目中的角色分配(ProjectStaffRole),支持一个员工多个项目角色 | 中 | 已实现 | module-auth: ProjectStaffRole |
|
||||||
|
|
||||||
|
#### 2.1.5 住户管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-050 | 住户信息管理 | 住户(Resident)通过共享主键一对一关联User,含身份证号(18位,JSON忽略脱敏)、住户类型3种:OWNER(业主)/FAMILY(家属)/TENANT(租户) | 高 | 已实现 | module-auth: Resident |
|
||||||
|
| REQ-AUTH-051 | 住户认证流程 | 认证状态4种流转:UNVERIFIED(未认证)-> PENDING(待审核)-> VERIFIED(已认证)/REJECTED(已拒绝),记录认证时间和认证人 | 高 | 已实现 | module-auth: Resident |
|
||||||
|
| REQ-AUTH-052 | 住户房屋绑定 | 住户-房屋关联(ResidentSpace),关系类型3种:OWNER/FAMILY/TENANT,绑定状态4种:PENDING(待确认)/ACTIVE(生效中)/EXPIRED(已过期)/CANCELLED(已取消),支持生效日期和失效日期(NULL=永久) | 高 | 已实现 | module-auth: ResidentSpace |
|
||||||
|
| REQ-AUTH-053 | 房屋空间管理 | 房屋空间(Space)含楼栋/单元/房号/房屋类型(RESIDENTIAL/COMMERCIAL)/楼层/建筑面积/状态,归属项目 | 中 | 已实现 | module-auth: Space |
|
||||||
|
|
||||||
|
#### 2.1.6 认证与授权
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-060 | 用户登录 | POST /api/auth/login,请求体{username, password},流程:检查登录锁定 -> 查询用户(含角色) -> 验证密码(BCrypt) -> 检查用户状态(LOCKED/DISABLED拒绝) -> 收集全部角色 -> 生成JWT Token -> 返回{token, userId, username, realName, roles} | 高 | 已实现 | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-061 | 用户登出 | POST /api/auth/logout,需携带Authorization Header | 高 | 已实现 | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-062 | Token刷新 | POST /api/auth/refresh,需携带有效Token,生成新Token返回 | 高 | 已实现 | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-063 | 获取当前用户 | GET /api/auth/me,需携带Authorization Header,返回当前用户信息 | 高 | 已实现 | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-064 | JWT Token规范 | 算法HMAC-SHA256,Claims含userId(subject)/username/roles列表,过期时间可配置(默认86400000ms=24小时) | 高 | 已实现 | module-auth: JwtTokenProvider |
|
||||||
|
| REQ-AUTH-065 | 登录失败锁定 | 基于Redis实现,Key格式`login:attempt:{username}`,5次失败后锁定10分钟,每次失败递增计数并设置TTL,登录成功清除计数,支持手动解锁 | 高 | 已实现 | module-auth: LoginAttemptService |
|
||||||
|
| REQ-AUTH-066 | 项目上下文传递 | 请求头`X-Project-ID`携带当前项目ID,ProjectContextInterceptor拦截器设置ThreadLocal,业务层通过DataScopeService过滤数据 | 高 | 已实现 | module-auth: ProjectContextInterceptor |
|
||||||
|
|
||||||
|
#### 2.1.7 审计日志
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-070 | 操作日志记录 | 通过`@OperationLog`注解+AOP切面自动记录操作日志,记录内容:操作用户/操作描述/模块/操作类型/目标类型/目标ID/操作内容/请求参数/操作结果/IP地址/用户代理/请求URL/请求方法/执行耗时/状态/错误信息 | 高 | 已实现 | module-auth: OperationLogAspect |
|
||||||
|
| REQ-AUTH-071 | 异步持久化 | 通过`auditLogExecutor`线程池异步保存审计日志,避免影响业务性能 | 高 | 已实现 | module-auth: AuditLogService |
|
||||||
|
| REQ-AUTH-072 | 审计日志查询 | 支持分页查询,可按module/action/username/日期范围筛选,强制限制查询范围不超过30天(超过自动截断起始时间) | 高 | 已实现 | module-auth: AuditLogController |
|
||||||
|
| REQ-AUTH-073 | 模块与操作类型 | 记录模块8种:USER/ROLE/PERMISSION/PROJECT/AUTH/DEPT/PROJECT_MEMBER/SYSTEM;操作类型11种:CREATE/UPDATE/DELETE/QUERY/VIEW/LOGIN/LOGOUT/EXPORT/IMPORT/ASSIGN/REVOKE | 中 | 已实现 | module-auth: AuditLog |
|
||||||
|
| REQ-AUTH-074 | 日志统计 | 支持获取最近30天日志统计数据,提供模块列表和操作类型列表(用于筛选下拉) | 中 | 已实现 | module-auth: AuditLogController |
|
||||||
|
| REQ-AUTH-075 | 日志归档 | 90天以上日志归档(当前实现为直接删除,TODO: 导出至对象存储) | 低 | 部分实现 | module-auth: AuditLogService |
|
||||||
|
|
||||||
|
#### 2.1.8 系统配置
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-AUTH-080 | 键值配置管理 | 支持创建/查询/更新系统配置项,配置键(configKey)唯一,配置值最大5000字符,支持单个更新和批量更新 | 高 | 已实现 | module-auth: SysConfigController |
|
||||||
|
| REQ-AUTH-081 | 密码策略配置 | 密码强度规则可配置(前缀`password.*`):最小长度(默认8)/最大长度(默认20)/需大写(默认true)/需小写(默认true)/需数字(默认true)/需特殊字符(默认true) | 高 | 已实现 | module-auth: PasswordStrengthValidator |
|
||||||
|
| REQ-AUTH-082 | 数据访问授权 | 通用数据访问控制(DataAccess),支持按dataType/dataId/accessType/accessId/accessLevel进行细粒度授权,已存在相同记录则更新accessLevel | 中 | 已实现 | module-auth: DataAccessController |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 空间与项目域
|
||||||
|
|
||||||
|
#### 2.2.1 项目管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-MDM-001 | 项目创建 | 支持创建项目,项目编码唯一(正则`^[a-zA-Z0-9_-]+$`,2-50位),项目名称2-100位,项目类型3种:RESIDENTIAL(住宅)/OFFICE(办公)/INDUSTRIAL_PARK(产业园区),支持自动生成项目编码,创建后自动初始化ProjectConfig(默认配置) | 高 | 已实现 | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-002 | 项目查询 | 支持分页查询项目列表,可按keyword/status筛选,支持排序;支持按ID/编码查询项目详情;支持项目选择器列表(用于下拉选择) | 高 | 已实现 | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-003 | 项目更新与删除 | 支持更新项目基本信息;删除前必须调用delete-check检查关联数据(存在应收未收费用时无法删除) | 高 | 已实现 | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-004 | 项目状态流转 | 4种状态:PENDING(待审核)/ACTIVE(正常)/DISABLED(禁用)/ARCHIVED(已归档),流转规则:PENDING->ACTIVE/DISABLED, ACTIVE->DISABLED/ARCHIVED, DISABLED->ACTIVE/ARCHIVED, ARCHIVED为终态不可流转 | 高 | 已实现 | module-mdm: ProjectController.changeStatus |
|
||||||
|
| REQ-MDM-005 | 状态变更历史 | 每次状态变更记录ProjectStatusHistory(fromStatus/toStatus/reason/operatorId/operatorName) | 中 | 已实现 | module-mdm: ProjectStatusHistory |
|
||||||
|
| REQ-MDM-006 | 项目统计 | 支持查询项目统计数据(ProjectStatistics快照):成员数/楼栋数/单元数/房间数/业主数/租户数 | 中 | 已实现 | module-mdm: ProjectController.getStatistics |
|
||||||
|
| REQ-MDM-007 | 项目配置管理 | 项目功能开关10项:预约/访客/投诉(默认开)/缴费/公告(默认开)/问卷/投票/报修(默认开)/资产/自定义配置JSON | 高 | 已实现 | module-mdm: ProjectConfigController |
|
||||||
|
| REQ-MDM-008 | 项目成员管理(MDM侧) | 支持查看项目成员列表(分页)、添加成员(从企业员工中选择,支持批量)、移除成员;成员角色5种:PROJECT_MANAGER/PROJECT_ADMIN/OPERATION_STAFF/FINANCE_STAFF/VIEWER | 高 | 已实现 | module-mdm: ProjectMemberController |
|
||||||
|
| REQ-MDM-009 | 项目删除检查 | 删除前检查关联数据,返回{canDelete, reason, statistics},存在应收未收费用时canDelete=false | 高 | 已实现 | module-mdm: ProjectController |
|
||||||
|
|
||||||
|
#### 2.2.2 空间节点管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-MDM-010 | 空间节点创建 | 支持创建空间节点,节点名称1-100位,节点大类4种(NodeCategory):BUILDING(建筑空间)/PARKING(停车空间)/FACILITY(设施空间)/AREA(区域空间),节点类型15种(NodeType,含层级序号),支持批量创建 | 高 | 已实现 | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-MDM-011 | 15种节点类型 | BUILDING(楼栋,L1)/UNIT(单元,L2)/FLOOR(楼层,L3)/ROOM(房间,L4)/SHOP(商铺,L2)/GARAGE(车库,L1)/PARKING_AREA(停车区域,L2)/PARKING_SPACE(车位,L3)/EQUIPMENT_ROOM(设备房,L1)/PROPERTY_OFFICE(物业用房,L1)/SECURITY_ROOM(门岗,L1)/PUBLIC_ROOM(公共用房,L1)/PUBLIC_AREA(公共区域,L1)/GREEN_AREA(绿化区域,L1)/ROAD(道路,L1) | 高 | 已实现 | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-012 | 树形结构维护 | 支持树形路径维护:treePath(物理路径id.id.id,用于快速查询子孙)、treePathName(名称路径项目/楼栋/单元/房间,用于展示)、level(层级深度,根节点为0),创建/移动节点时自动维护 | 高 | 已实现 | module-mdm: SpaceNodeService |
|
||||||
|
| REQ-MDM-013 | 空间节点查询 | 支持按项目查询节点列表、获取项目空间树、获取项目根节点、按项目+类型查询、获取子节点列表、按ID查询详情 | 高 | 已实现 | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-MDM-014 | 空间节点更新与删除 | 支持更新节点信息;删除前必须调用delete-check检查子节点数量;普通删除(有子节点时拒绝);级联删除(DELETE /{id}/cascade,含子节点);软删除(isDeleted标记) | 高 | 已实现 | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-MDM-015 | 面积信息管理 | 支持记录建筑面积/使用面积/公摊面积/占地面积(BigDecimal(10,2)) | 中 | 已实现 | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-016 | 地理信息管理 | 支持记录经度/纬度/海拔/楼层号(正数地上,负数地下) | 中 | 已实现 | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-017 | 设备扩展模式 | SpaceNode通过isEquipment字段标记设备节点,设备节点额外使用设备扩展字段(维保信息/巡检周期/特种设备证书/常用备件/能耗标准/安装环境/防护等级等20+字段) | 高 | 已实现 | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-018 | 扩展属性JSON | 支持attributes字段(String(2000) JSON格式)存储类型特定属性 | 中 | 已实现 | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-019 | 空间编码自动生成 | 自动生成空间编码规则,唯一约束UNIQUE(project_id, code) | 中 | 未实现 | - |
|
||||||
|
| REQ-MDM-020 | 节点移动 | 支持移动节点到新的父节点(PUT /{id}/move),自动维护treePath | 中 | 未实现 | - |
|
||||||
|
| REQ-MDM-021 | 祖先/子孙查询 | 支持查询指定节点的祖先链(GET /{id}/ancestors)和子孙节点(GET /{id}/descendants) | 低 | 未实现 | - |
|
||||||
|
| REQ-MDM-022 | 空间统计分析 | 支持空间统计接口:按类型统计/按状态统计/空间总览 | 低 | 未实现 | - |
|
||||||
|
| REQ-MDM-023 | PostGIS空间查询 | 支持PostGIS空间查询(范围搜索/距离计算),GEOMETRY(Point)/GEOMETRY(Polygon)字段,GIST空间索引 | 低 | 未实现 | - |
|
||||||
|
| REQ-MDM-024 | 地图服务 | 高德地图集成,支持标注/绘制/路径,MapEditor组件,地图标记/边界查询API | 低 | 未实现 | - |
|
||||||
|
| REQ-MDM-025 | 空间节点导入导出 | 支持空间节点批量导入和导出,导入模板下载 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.2.3 巡检标准项
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-MDM-030 | 巡检标准项CRUD | 支持创建/查询/更新/删除巡检标准项,按设备类型(equipmentType)和系统类型(systemType)分类,每项含检查项名称/检查方法/标准值/是否必检(默认true)/排序号/状态(ACTIVE/INACTIVE) | 高 | 已实现 | module-mdm: InspectionItemController |
|
||||||
|
| REQ-MDM-031 | 标准项查询筛选 | 支持按activeOnly=true仅查询启用项;equipmentType+systemType双条件过滤;equipmentType单条件过滤;systemType过滤 | 中 | 已实现 | module-mdm: InspectionItemService |
|
||||||
|
| REQ-MDM-032 | 巡检模板管理 | 支持创建/查询/更新/复制巡检模板,模板按项目+设备类型组织,含检查项列表(JSON格式存储),支持版本管理(version字段),模板与标准项通过equipmentType逻辑关联 | 高 | 已实现 | module-mdm: InspectionTemplateController |
|
||||||
|
| REQ-MDM-033 | 巡检记录管理 | 支持创建/查询/更新/删除/完成巡检记录,记录关联设备(equipmentId)和计划(planId),检查结果状态3种:NORMAL/WARNING/ABNORMAL,支持签到信息(时间/位置/照片),检查项结果和异常问题以JSONB存储 | 高 | 已实现 | module-mdm: InspectionRecordController |
|
||||||
|
| REQ-MDM-034 | 巡检记录查询 | 支持按设备/计划/巡检人/状态/日期范围筛选,支持按设备+日期范围组合查询 | 中 | 已实现 | module-mdm: InspectionRecordService |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 设备与资产域
|
||||||
|
|
||||||
|
#### 2.3.1 设备台账
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-ASSET-001 | 设备创建 | 支持创建设备,设备编码全局唯一(equipmentCode),设备名称必填,设备类型10种:ELEVATOR/HVAC/FIRE_PROTECTION/PLUMBING/ELECTRICAL/ENERGY_METER/SECURITY/LANDSCAPE/KITCHEN/OTHER,归属类型4种(默认PROJECT):PROJECT(项目自有)/COMPANY(公司统筹)/OWNER(业主自置)/RENTAL(租赁设备),设备状态4种(默认ACTIVE):ACTIVE(在用)/INACTIVE(停用)/MAINTENANCE(维保中)/SCRAPPED(已报废) | 高 | 已实现 | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-002 | 设备查询 | 支持按项目查询/按空间查询/按类型查询/按归属查询设备列表,支持按ID查询设备详情 | 高 | 已实现 | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-003 | 设备更新与删除 | 支持更新设备信息;支持单个删除和批量删除(POST /batch-delete);逻辑删除(isDeleted标记) | 高 | 已实现 | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-004 | 设备与空间关联 | 设备通过spaceNodeId关联空间节点树,支持按空间节点查询设备列表 | 高 | 已实现 | module-asset: Equipment.spaceNodeId |
|
||||||
|
| REQ-ASSET-005 | 系统类型分类 | 设备系统类型9种(商业地产8大系统+其他):HVAC(暖通空调)/FIRE(消防)/ELEVATOR(电梯)/ELECTRICAL(电气)/PLUMBING(给排水)/BAS(弱电智能化)/KITCHEN(餐饮厨房)/LANDSCAPE(景观)/OTHER(其他) | 高 | 已实现 | module-asset: Equipment.systemType |
|
||||||
|
| REQ-ASSET-006 | 设备统计 | 支持按类型统计/按归属统计/项目设备总数查询 | 中 | 已实现 | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-007 | 设备导入 | 支持Excel批量导入设备(.xlsx/.xls),文件大小最大10MB,行数最大1000行,文件类型校验,返回{successCount, failCount, failDetails} | 高 | 已实现 | module-asset: EquipmentController.importEquipment |
|
||||||
|
| REQ-ASSET-008 | 设备导出 | 支持按项目导出设备列表为Excel文件(.xlsx格式) | 中 | 已实现 | module-asset: EquipmentController.exportEquipment |
|
||||||
|
| REQ-ASSET-009 | 设备照片管理 | 支持设备照片JSONB内嵌存储,照片类型4种:外观/铭牌/安装位置/环境,每张照片含type/url/remark | 中 | 已实现 | module-asset: Equipment.photos |
|
||||||
|
| REQ-ASSET-010 | 设备文档管理 | 支持电子文档JSONB内嵌存储,文档类型4种:manual/certificate/contract/other,每份文档含name/url/size/type/remark | 中 | 已实现 | module-asset: Equipment.documents |
|
||||||
|
| REQ-ASSET-011 | 设备二维码 | 支持设备二维码生成、扫码查看设备详情、扫码快速报修 | 中 | 未实现 | - |
|
||||||
|
| REQ-ASSET-012 | 设备规格字段 | 设备规格型号字段(specifications) | 低 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.3.2 设备扩展信息
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-ASSET-020 | 电梯扩展表 | EquipmentElevator(asset_equipment_elevator),通过equipment_id UNIQUE一对一关联主表,含:电梯类型/型号/载重(kg)/速度(m/s)/楼层数/井道尺寸/底坑深度/顶层高度/注册登记号/检验证书/下次检验日期/能耗/维保等级/应急救援预案 | 高 | 已实现 | module-asset: EquipmentElevatorService |
|
||||||
|
| REQ-ASSET-021 | 暖通扩展表 | EquipmentHvac(asset_equipment_hvac),含:暖通类型/制冷量/制热量/风量/制冷剂类型/充注量/能效比(EER)/性能系数(COP)/安装日期/保修到期/滤网更换周期/上次更换日期/风管类型/风管尺寸 | 高 | 已实现 | module-asset: EquipmentHvacService |
|
||||||
|
| REQ-ASSET-022 | 消防扩展表 | EquipmentFire(asset_equipment_fire),含:消防设备类型/安装面积/安装高度/探测范围/系统类型/分区编号/回路编号/是否启用联动/联动动作/巡检周期/上次/下次巡检日期/巡检结果/特殊要求 | 高 | 已实现 | module-asset: EquipmentFireService |
|
||||||
|
| REQ-ASSET-023 | 能源计量扩展表 | EquipmentEnergy(asset_equipment_energy),含:表计类型/能源类型/表计型号/规格/表常数/精度等级/抄表方式/上次读数/当前读数/单价/计费方式/通讯方式/通讯地址/检定周期/下次检定日期/检定证书 | 高 | 已实现 | module-asset: EquipmentEnergyService |
|
||||||
|
| REQ-ASSET-024 | 扩展表读写模式 | 扩展表通过主设备ID路径读写(GET/PUT /{id}/elevator),采用saveOrUpdate模式:存在则更新,不存在则新建 | 高 | 已实现 | module-asset: Equipment*Service |
|
||||||
|
| REQ-ASSET-025 | 无扩展表设备类型 | PLUMBING/ELECTRICAL/SECURITY/LANDSCAPE/KITCHEN/OTHER无扩展表,使用主表通用字段+attributes JSON扩展 | 中 | 已实现 | module-asset: Equipment.attributes |
|
||||||
|
|
||||||
|
#### 2.3.3 设备健康评分
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-ASSET-030 | 健康评分算法 | 健康度 = 基础分(100) - 故障扣分 - 维保扣分 - 年龄扣分,最低0分。故障扣分=故障次数(近30天)x5(无上限);维保扣分=(1-维保完成率)x20(上限20分);年龄扣分=设备年龄(年)x2(上限10分) | 高 | 已实现[Beta] | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-031 | 5级健康等级 | EXCELLENT(优秀,[90,100))/GOOD(良好,[75,90))/FAIR(一般,[60,75))/POOR(较差,[40,60))/CRITICAL(危急,[0,40)) | 高 | 已实现[Beta] | module-asset: HealthLevel |
|
||||||
|
| REQ-ASSET-032 | MTBF计算 | 平均故障间隔时间 = 运行时间(小时) / 故障次数,计算周期默认30天可自定义,仅1次故障时使用默认期间天数x24小时,无故障返回0 | 中 | 已实现[Beta] | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-033 | MTTR计算 | 平均修复时间 = 总修复时间(小时) / 修复次数,仅统计已完成修复的故障记录,无修复记录返回0 | 中 | 已实现[Beta] | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-034 | 健康评分查询 | 支持获取设备最新健康度、健康度历史、手动触发计算 | 中 | 已实现[Beta] | module-asset: EquipmentHealthController |
|
||||||
|
| REQ-ASSET-035 | 维保完成率计算 | 维保完成率用于健康评分扣分,当前TODO标记暂返回1.0 | 中 | 部分实现 | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
|
||||||
|
#### 2.3.4 故障历史
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-ASSET-040 | 故障记录 | 支持记录设备故障,故障类型4种:SUDDEN(突发)/SCHEDULED(计划停机)/WARNING(预警)/OTHER(其他),故障等级4级:LOW(轻微)/MEDIUM(一般)/HIGH(严重)/CRITICAL(危急),修复结果4种:FIXED(已修复)/PARTIAL_FIXED(部分修复)/REPLACED(更换新设备)/PENDING(待处理) | 高 | 已实现 | module-asset: EquipmentHealthController |
|
||||||
|
| REQ-ASSET-041 | 故障自动计算 | 记录故障时自动计算:修复时长=(repairEndTime-repairStartTime)/60分钟转小时;停机时长=(repairStartTime-failureTime)/60分钟转小时 | 中 | 已实现 | module-asset: EquipmentFailureHistory |
|
||||||
|
| REQ-ASSET-042 | 故障历史查询 | 支持按设备ID查询故障历史列表 | 中 | 已实现 | module-asset: EquipmentHealthController |
|
||||||
|
| REQ-ASSET-043 | 故障自动工单 | 故障记录时自动创建维修工单(事件驱动) | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.3.5 归属主体管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-ASSET-050 | 归属主体CRUD | 支持创建/查询/更新/删除归属主体,主体类型3种:COMPANY(公司)/OWNER(业主)/RENTAL_COMPANY(租赁公司),含主体名称/编码/联系人/联系电话/联系地址/营业执照/法人代表/合同编号/合同起止日期/租赁费用 | 高 | 已实现 | module-asset: OwnershipEntityController |
|
||||||
|
| REQ-ASSET-051 | 归属主体查询 | 支持按类型查询归属主体、获取全部未删除主体 | 中 | 已实现 | module-asset: OwnershipEntityController |
|
||||||
|
| REQ-ASSET-052 | 归属主体逻辑删除 | 删除操作为逻辑删除(isDeleted标记),查询时过滤已删除记录 | 高 | 已实现 | module-asset: OwnershipEntityController |
|
||||||
|
| REQ-ASSET-053 | 设备归属冗余 | Equipment表同时存储owningEntityId和owningEntityName,避免频繁关联查询 | 中 | 已实现 | module-asset: Equipment |
|
||||||
|
|
||||||
|
#### 2.3.6 特种设备管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-ASSET-060 | 特种设备标记 | 设备主表含specialEquipmentType(特种设备类型)和specialEquipmentCert(特种设备证书)字段 | 中 | 已实现 | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-061 | 年检周期管理 | 设备含inspectionCycle(年检周期/月)和nextInspectionDate(下次年检日期)字段 | 中 | 已实现 | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-062 | 特种设备列表查询 | 支持查询项目下的特种设备列表 | 中 | 已实现 | module-mdm: SpaceNodeController.getSpecialEquipmentList |
|
||||||
|
| REQ-ASSET-063 | 即将年检预警 | 支持查询即将年检设备(默认90天内),按项目筛选 | 中 | 已实现 | module-mdm: SpaceNodeController.getExpiringInspectionEquipment |
|
||||||
|
| REQ-ASSET-064 | 维保到期定时提醒 | 后端定时任务检查维保合同到期/年检到期,自动发送提醒通知 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 运营与服务域
|
||||||
|
|
||||||
|
#### 2.4.1 工单管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-OPS-001 | 工单创建 | 支持创建工单,工单编号自动生成(格式WO-YYYYMMDD-XXXX,4位序号按日递增),工单来源6种:OWNER(业主报修)/MAINTENANCE(维保计划)/INSPECTION(巡检触发)/FAULT(故障触发)/REGULATORY(法规巡检)/MANUAL(手动创建),工单类型6种:REPAIR/INSPECTION/SECURITY/CLEANING/PROPERTY/CONSULTATION | 高 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-002 | 工单查询 | 支持查询工单列表,可按projectId/equipmentId/source/type/status/assignedTo筛选(参数互斥,按优先级依次判断),支持按ID查询工单详情 | 高 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-003 | 工单状态流转 | 6种状态:PENDING(待分配)->ASSIGNED(已派单)->IN_PROGRESS(执行中)->COMPLETED(已完成)->VERIFIED(已验收),CANCELLED(已取消,PENDING/ASSIGNED/IN_PROGRESS可转入),COMPLETED/VERIFIED不可取消 | 高 | 已实现 | module-wo: WorkOrderService |
|
||||||
|
| REQ-OPS-004 | 工单派单 | POST /{id}/assign,指定负责人(assignedTo)/服务商(assignedVendor)/派单日期(assignedDate),状态PENDING->ASSIGNED | 高 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-005 | 工单执行 | POST /{id}/start,记录实际开始时间(actualStart=now()),状态ASSIGNED->IN_PROGRESS | 高 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-006 | 工单完成 | POST /{id}/complete,记录故障原因/解决方案/处理结果/费用(人工费+备件费=总费用),自动计算实际工时(actualHours=Duration.between(actualStart,actualEnd)),状态IN_PROGRESS->COMPLETED | 高 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-007 | 工单验收 | POST /{id}/verify,记录验收人(verifiedBy)/评分(rating,1-5星)/备注(remark),状态COMPLETED->VERIFIED | 高 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-008 | 工单取消 | POST /{id}/cancel,PENDING/ASSIGNED/IN_PROGRESS可取消,COMPLETED/VERIFIED不可取消(抛出异常"无法取消已完成的工单") | 中 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-009 | 工单优先级 | 4种优先级:LOW/MEDIUM(默认)/HIGH/URGENT | 高 | 已实现 | module-wo: WorkOrder |
|
||||||
|
| REQ-OPS-010 | 工单明细管理 | 支持查询/批量添加工单明细(WorkOrderItem),明细类型3种:PART(备件)/INSPECTION_ITEM(巡检项)/CHECKPOINT(检查点),含名称/数量/单位/单价/总价/是否正常/观察记录/建议 | 中 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-011 | 工单统计 | 支持工单多维度统计:总数/待分配/已派单/执行中/已完成/已验收/已取消/今日完成/今日创建/逾期/平均完成工时/平均评分/按来源分布/按类型分布/按优先级分布 | 中 | 已实现 | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-012 | 工单挂起与恢复 | 支持工单挂起(SUSPENDED状态)和恢复操作,任意执行中状态可挂起 | 中 | 未实现 | - |
|
||||||
|
| REQ-OPS-013 | 工单退回 | 支持已派单工单退回重分配(RETURNED状态),ASSIGNED状态可退回 | 中 | 未实现 | - |
|
||||||
|
| REQ-OPS-014 | 工单流转记录 | 记录工单每次状态变更的流转历史(WorkOrderFlow实体) | 中 | 未实现 | - |
|
||||||
|
| REQ-OPS-015 | 工单报修人信息 | 工单含报修人信息字段:reporterId/reporterName/reporterPhone/reporterAddress | 中 | 未实现 | - |
|
||||||
|
| REQ-OPS-016 | 工单分页查询 | 工单列表接口支持分页返回(当前为全量List返回) | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.4.2 维保计划
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-OPS-020 | 维保计划创建 | 支持创建维保计划,计划编码自动生成(唯一),计划名称必填,关联设备(equipmentId)必填,计划类型2种:PREVENTIVE(预防性)/CORRECTIVE(纠正性),周期天数(cycleDays),预估工时,指定服务商 | 高 | 已实现 | module-wo: MaintenancePlan |
|
||||||
|
| REQ-OPS-021 | 维保计划状态管理 | 计划状态3种:ACTIVE(启用)/INACTIVE(停用)/SUSPENDED(暂停),删除行为为逻辑删除(将状态设为INACTIVE) | 高 | 已实现 | module-wo: MaintenancePlan |
|
||||||
|
| REQ-OPS-022 | 维保计划周期调度 | 通过cycleDays计算下次执行日期(nextDate),上次执行日期(lastDate) | 中 | 已实现 | module-wo: MaintenancePlan |
|
||||||
|
| REQ-OPS-023 | 维保计划自动调度 | 定时任务根据维保计划周期自动生成维保任务 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.4.3 维保任务
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-OPS-030 | 维保任务创建 | 支持创建维保任务,任务编号自动生成(格式EQ-YYYYMMDD-XXXX),关联设备(equipmentId)必填,任务类型3种:PREVENTIVE(预防性)/CORRECTIVE(纠正性)/EMERGENCY(紧急维修) | 高 | 已实现 | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-031 | 维保任务状态流转 | 6种状态:PENDING->ASSIGNED->IN_PROGRESS->COMPLETED->VERIFIED,CANCELLED(PENDING/ASSIGNED/IN_PROGRESS可转入),与工单状态机一致 | 高 | 已实现 | module-wo: MaintenanceTaskService |
|
||||||
|
| REQ-OPS-032 | 4种触发方式 | 触发类型4种:PLAN(计划触发)/INSPECTION(巡检触发)/FAULT(故障触发)/MANUAL(手动创建) | 高 | 已实现 | module-wo: MaintenanceTask |
|
||||||
|
| REQ-OPS-033 | 自动优先级判定 | 紧急维修类型->URGENT;故障触发+紧急关键词(困人/漏水/停电/火灾/爆炸/漏电/冒烟/故障停机)->URGENT;故障触发无紧急关键词->HIGH;巡检触发->HIGH;计划触发->MEDIUM;其他->MEDIUM | 高 | 已实现 | module-wo: MaintenanceTaskServiceImpl.autoDeterminePriority |
|
||||||
|
| REQ-OPS-034 | 双完成接口 | 简版完成(POST /{id}/complete):处理结果/实际工时/总费用/完成人;详版完成(POST /{id}/complete-details):含故障原因/解决方案/使用备件(JSONB)/人工费/备件费 | 中 | 已实现 | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-035 | 维保任务验收 | POST /{id}/verify,记录验收人/评分(1-5)/备注;独立评分接口POST /{id}/rate | 中 | 已实现 | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-036 | 完成后设备联动 | 维保任务完成时自动更新设备信息:维保商=任务服务商;预防性维护更新下次巡检日期(now()+inspectionCycle,默认30天);异常容错(更新设备失败不影响工单完成) | 高 | 已实现 | module-wo: MaintenanceTaskServiceImpl |
|
||||||
|
| REQ-OPS-037 | 维保任务统计 | 支持多维度统计:总数/待分配/已派单/执行中/已完成/已验收/已取消/今日完成/今日创建/逾期/平均完成工时/平均评分/按优先级分布/按触发类型分布/人工费总计/备件费总计/费用总计 | 中 | 已实现 | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-038 | 使用备件记录 | 维保任务完成时可记录使用备件列表(partsUsed JSONB),含备件ID/名称/数量/单价/总价 | 中 | 已实现 | module-wo: MaintenanceTask |
|
||||||
|
|
||||||
|
#### 2.4.4 巡检管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-OPS-040 | 巡检模板管理 | 支持创建/查询/更新/复制巡检模板,模板编码自动生成(唯一),模板状态2种:ACTIVE/INACTIVE,模板含检查项列表(@Transient非持久化,通过templateId关联InspectionItem) | 高 | 已实现 | module-wo: InspectionTemplate |
|
||||||
|
| REQ-OPS-041 | 巡检项管理 | 检查项通过templateId关联模板,含检查项名称/描述/检查方法/检查标准/是否必检(默认true)/是否需要正常判定(默认true)/排序号 | 高 | 已实现 | module-wo: InspectionItem |
|
||||||
|
| REQ-OPS-042 | 巡检签到 | 巡检记录支持签到信息:签到时间(checkInTime)/签到位置(checkInLocation)/签到照片(checkInPhoto) | 中 | 已实现 | module-mdm: InspectionRecord |
|
||||||
|
| REQ-OPS-043 | 巡检异常上报 | 巡检记录支持异常问题列表(problems JSONB),含问题描述/照片/严重程度(LOW/MEDIUM/HIGH) | 中 | 已实现 | module-mdm: InspectionRecord |
|
||||||
|
| REQ-OPS-044 | 巡检扫码签到 | 支持扫描设备二维码进行巡检签到,记录位置信息 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.4.5 备件管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-OPS-050 | 备件分类管理 | 支持创建/查询备件分类,分类编码唯一,支持树形分类(parentId自关联),含分类名称/描述/排序号 | 高 | 已实现 | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-051 | 备件台账CRUD | 支持创建/查询/更新/删除备件,备件编码唯一,含名称/分类/规格型号/计量单位/安全库存(默认0)/当前库存(默认0)/单价/供应商/供应商联系方式/存放位置/状态(ACTIVE/INACTIVE) | 高 | 已实现 | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-052 | 入库操作 | POST /in-stock,增加库存,记录备件ID/数量/操作人/备注,更新currentStock,记录SparePartRecord(类型IN,含操作后余额balance) | 高 | 已实现 | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-053 | 出库操作 | POST /out-stock,减少库存,记录备件ID/数量/关联工单ID(可选)/操作人/备注,校验库存不能为负,更新currentStock,记录SparePartRecord(类型OUT) | 高 | 已实现 | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-054 | 低库存预警 | 当currentStock < safeStock时触发预警,GET /low-stock返回低于安全库存的备件列表 | 中 | 已实现 | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-055 | 出入库记录查询 | 支持查询指定备件的所有出入库记录流水 | 中 | 已实现 | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-056 | 盘点与调整 | 支持盘点(CHECK)和调整(ADJUST)操作,记录SparePartRecord | 中 | 已实现 | module-mdm: SparePartRecord(RecordType) |
|
||||||
|
|
||||||
|
#### 2.4.6 能耗管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-OPS-060 | 计量点管理 | 支持创建/查询/更新/删除计量点,计量点编码自动生成(EM+yyyyMMddHHmmss,冲突时追加后缀),能源类型6种:LIGHTING(照明插座用电)/HVAC(空调用电)/POWER(动力用电)/SPECIAL(特殊用电)/WATER(给排水)/GAS(燃气),关联空间节点,含额定容量/单价(4位小数精度)/状态(ACTIVE/INACTIVE) | 高 | 已实现 | module-mdm: EnergyController |
|
||||||
|
| REQ-OPS-061 | 能耗抄表录入 | POST /consumption,录入能耗数据{meterId, currentReading, recordedBy},校验:仪表状态必须ACTIVE(错误码6102)/当前读数不能小于上次读数(错误码6103)/仪表必须存在(错误码6101),自动获取上次读数(首次为0),自动计算消耗量(current-previous),自动计算费用(consumption x unitPrice,单价为空时为0),默认记录方式MANUAL | 高 | 已实现 | module-mdm: EnergyConsumptionService |
|
||||||
|
| REQ-OPS-062 | 能耗记录查询 | 支持按计量点查询能耗记录,可按日期范围(startDate/endDate)筛选 | 中 | 已实现 | module-mdm: EnergyController |
|
||||||
|
| REQ-OPS-063 | 按类型统计能耗 | GET /statistics/by-type,按项目+月份统计各能源类型消耗量(注意:当前实现将总消耗全部归入LIGHTING类型,存在已知缺陷) | 中 | 部分实现 | module-mdm: EnergyConsumptionService |
|
||||||
|
| REQ-OPS-064 | 单位面积能耗 | GET /statistics/unit-consumption,计算总能耗/总建筑面积 | 中 | 已实现 | module-mdm: EnergyConsumptionService |
|
||||||
|
| REQ-OPS-065 | IoT自动抄表 | 支持对接IoT平台自动采集能耗数据(RecordMethod.IOT) | 低 | 未实现 | - |
|
||||||
|
| REQ-OPS-066 | 前后端能源类型统一 | 后端LIGHTING/HVAC/POWER/SPECIAL/WATER/GAS与前端ELECTRICITY/WATER/GAS/CENTRAL_HEATING/CENTRAL_COOLING需对齐 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 财务与收费域
|
||||||
|
|
||||||
|
#### 2.5.1 收费项目管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-FIN-001 | 收费项目实体 | FeeItem实体,含code/name/type/billingMethod/unitPrice/unit/billDay/dueDay/overdueDay/enableLateFee/lateFeeRate/maxLateFee,收费类型8种:PROPERTY/PARKING/WATER/ELECTRICITY/GAS/HEATING/REPAIR_FUND/OTHER,计费方式4种:FIXED/AREA/USAGE/CUSTOM | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-002 | 收费项目CRUD | 支持创建/查询/更新/禁用收费项目 | 高 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.5.2 收费账单管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-FIN-010 | 账单实体 | FeeBill实体,含billNo/feeItemId/spaceNodeId/ownerId/billPeriod/billDate/dueDate/overdueDate/usageAmount/amount/lateFee/discount/payableAmount/paidAmount/status,账单状态5种:UNPAID/PARTIAL_PAID/PAID/OVERDUE/CANCELLED | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-011 | 账单自动生成 | 根据收费项目规则,按账期自动生成账单 | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-012 | 账单状态流转 | UNPAID->PARTIAL_PAID->PAID / OVERDUE->CANCELLED | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-013 | 批量账单生成 | 一次性为项目所有业主生成账单 | 中 | 未实现 | - |
|
||||||
|
| REQ-FIN-014 | 账单导出 | 导出Excel/PDF格式账单 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.5.3 支付记录管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-FIN-020 | 支付记录实体 | FeePayment实体,含paymentNo/amount/method/thirdPartyNo/status/failReason/paymentTime,支付方式5种:WECHAT/ALIPAY/CASH/BANK_TRANSFER/CARD,支付状态4种:PENDING/SUCCESS/FAILED/REFUNDED | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-021 | 线下收款登记 | 支持登记线下收款记录 | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-022 | 支付记录查询 | 支持查询支付记录列表 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.5.4 退款管理
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-FIN-030 | 退款实体 | FeeRefund实体,含refundNo/amount/reason/status/approverId/approveTime/thirdPartyRefundNo/refundTime,退款状态4种:PENDING/APPROVED/REJECTED/REFUNDED | 中 | 未实现 | - |
|
||||||
|
| REQ-FIN-031 | 退款流程 | 退款申请->审批->执行 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.5.5 费用催缴与滞纳金
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-FIN-040 | 费用催缴定时任务 | 到期提醒(3天内)、逾期催缴、周汇总 | 中 | 未实现 | - |
|
||||||
|
| REQ-FIN-041 | 滞纳金自动计算 | 逾期天数 x 日利率,不超过上限 | 中 | 未实现 | - |
|
||||||
|
|
||||||
|
#### 2.5.6 能耗计费
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-FIN-050 | 能耗数据与账单对接 | 将EnergyConsumption的amount作为按用量计费的账单数据来源,实现能耗抄表->费用计算->账单生成的闭环 | 高 | 未实现 | - |
|
||||||
|
| REQ-FIN-051 | 按面积计费 | 根据房产面积 x 单价计算物业费 | 中 | 未实现 | - |
|
||||||
|
| REQ-FIN-052 | 固定金额计费 | 每月固定金额的收费项目(如停车费) | 中 | 未实现 | - |
|
||||||
|
| REQ-FIN-053 | 支付网关对接 | 微信支付/支付宝SDK集成 | 中 | 未实现 | - |
|
||||||
|
| REQ-FIN-054 | 财务报表 | 收费统计/欠费分析/收入趋势 | 低 | 未实现 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.6 前端交互域
|
||||||
|
|
||||||
|
#### 2.6.1 登录与权限控制流程
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-UI-001 | 登录页面 | /auth/Login.vue,输入用户名+密码,调用POST /api/auth/login,存储Token到本地,跳转至主页 | 高 | 已实现 | ether-admin: Login.vue |
|
||||||
|
| REQ-UI-002 | 前端权限指令 | v-permission指令,根据用户角色/权限控制元素显示隐藏 | 高 | 已实现 | ether-admin: v-permission |
|
||||||
|
| REQ-UI-003 | 路由权限守卫 | 前端路由守卫,根据用户权限动态渲染菜单和路由 | 高 | 已实现 | ether-admin: router |
|
||||||
|
| REQ-UI-004 | Token管理优化 | 双Token机制(accessToken+refreshToken)、加密存储 | 高 | 未实现 | OPT-SEC-001 |
|
||||||
|
| REQ-UI-005 | XSS防护增强 | xss库过滤、白名单配置 | 高 | 未实现 | OPT-SEC-002 |
|
||||||
|
| REQ-UI-006 | CSRF防护 | CSRF Token、SameSite Cookie | 高 | 未实现 | OPT-SEC-003 |
|
||||||
|
|
||||||
|
#### 2.6.2 项目切换与数据隔离
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-UI-010 | 项目选择 | 登录后获取用户项目列表GET /api/auth/users/{id}/projects,用户选择项目,后续请求携带X-Project-ID Header | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-011 | 项目切换 | 支持在已参与的项目间切换,切换后刷新当前页面数据 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-012 | 菜单动态渲染 | 根据用户角色和项目配置(ProjectConfig功能开关)动态渲染菜单 | 高 | 已实现 | ether-admin |
|
||||||
|
|
||||||
|
#### 2.6.3 各模块页面交互规范
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 | 对应代码模块 |
|
||||||
|
|--------|----------|----------|--------|----------|-------------|
|
||||||
|
| REQ-UI-020 | 用户管理页面 | /system/Users.vue + /system/UserDetail.vue,用户列表(分页/搜索/状态筛选)/创建用户/编辑/删除/修改密码/分配角色/查看项目 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-021 | 角色管理页面 | /system/Roles.vue,角色列表(分页/搜索)/创建/编辑/删除/分配权限/查看关联用户 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-022 | 权限管理页面 | /system/Permissions.vue,权限列表(分页/搜索)/创建/编辑/删除/按类型筛选/查看菜单权限 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-023 | 部门管理页面 | /system/Depts.vue,部门树展示/创建/编辑/删除(需先删除子部门)/查看成员/按类型筛选 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-024 | 审计日志页面 | /system/Audit.vue,日志列表(分页/模块/操作类型/用户名/日期范围筛选)/查看统计/模块和操作类型下拉 | 中 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-025 | 系统设置页面 | /system/Settings.vue,查看所有配置项(键值对)/单个或批量更新配置值 | 中 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-026 | 设备列表页面 | EquipmentList.vue,设备台账主列表含筛选/新增/编辑/删除/导入导出/批量删除 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-027 | 设备详情页面 | EquipmentDetail.vue,设备完整信息展示(基本信息/技术参数/维保/财务/照片/文档) | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-028 | 设备健康页面 | EquipmentHealth.vue,健康度评分/趋势图/MTBF/MTTR/故障历史 | 中 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-029 | 工单管理页面 | WorkOrder.vue,工单列表/新建/详情/派单/执行/完成/验收/取消 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-030 | 维保计划页面 | PlanList.vue,维保计划列表/新建/编辑/停用 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-031 | 维保任务页面 | TaskList.vue,维保任务列表/接受/开始/完成/取消 | 高 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-032 | 能耗管理页面 | MeterList.vue + ConsumptionRecord.vue + EnergyStatistics.vue,计量点管理/能耗录入/能耗统计 | 中 | 已实现 | ether-admin |
|
||||||
|
| REQ-UI-033 | 列表虚拟滚动 | 后端分页、虚拟表格优化长列表性能 | 中 | 未实现 | OPT-PERF-001 |
|
||||||
|
| REQ-UI-034 | 请求优化 | 取消重复请求、请求缓存 | 中 | 未实现 | OPT-PERF-002 |
|
||||||
|
| REQ-UI-035 | WebSocket替代轮询 | 实时通信、断线重连 | 低 | 未实现 | OPT-PERF-003 |
|
||||||
|
| REQ-UI-036 | 响应式设计 | CSS媒体查询、移动端适配 | 低 | 未实现 | OPT-UX-002 |
|
||||||
|
| REQ-UI-037 | 国际化支持 | vue-i18n、中英文切换 | 低 | 未实现 | OPT-UX-005 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、跨域业务流程需求
|
||||||
|
|
||||||
|
### 3.1 业主报修全流程
|
||||||
|
|
||||||
|
**流程描述**:业主发起报修到工单验收评价的完整闭环。
|
||||||
|
|
||||||
|
| 步骤 | 操作 | 涉及域 | 涉及功能点 | 实现状态 |
|
||||||
|
|------|------|--------|-----------|----------|
|
||||||
|
| 1 | 业主认证身份 | 身份与权限域 | REQ-AUTH-051 住户认证流程 | 已实现 |
|
||||||
|
| 2 | 业主选择项目 | 前端交互域 | REQ-UI-010 项目选择 | 已实现 |
|
||||||
|
| 3 | 业主定位设备 | 空间与项目域 | REQ-MDM-013 空间节点查询 | 已实现 |
|
||||||
|
| 4 | 创建报修工单 | 运营与服务域 | REQ-OPS-001 工单创建(source=OWNER) | 已实现 |
|
||||||
|
| 5 | 工单派单 | 运营与服务域 | REQ-OPS-004 工单派单 | 已实现 |
|
||||||
|
| 6 | 维保人员执行 | 运营与服务域 | REQ-OPS-005 工单执行 -> REQ-OPS-006 工单完成 | 已实现 |
|
||||||
|
| 7 | 工单验收 | 运营与服务域 | REQ-OPS-007 工单验收 | 已实现 |
|
||||||
|
| 8 | 业主评价 | 运营与服务域 | REQ-OPS-007 工单验收(评分) | 已实现 |
|
||||||
|
| - | 业主端小程序 | 前端交互域 | 业主端23项特性(APP-O01~O23) | 未实现 |
|
||||||
|
|
||||||
|
### 3.2 设备全生命周期管理
|
||||||
|
|
||||||
|
**流程描述**:设备从采购安装到报废退役的全生命周期管理。
|
||||||
|
|
||||||
|
| 步骤 | 操作 | 涉及域 | 涉及功能点 | 实现状态 |
|
||||||
|
|------|------|--------|-----------|----------|
|
||||||
|
| 1 | 设备采购登记 | 设备与资产域 | REQ-ASSET-001 设备创建(purchaseDate/purchasePrice) | 已实现 |
|
||||||
|
| 2 | 设备安装就位 | 设备与资产域 | REQ-ASSET-004 设备与空间关联 + REQ-ASSET-020~023 扩展表录入 | 已实现 |
|
||||||
|
| 3 | 设备运行监控 | 设备与资产域 | REQ-ASSET-030~034 健康评分/MTBF/MTTR | 已实现[Beta] |
|
||||||
|
| 4 | 预防性维保 | 运营与服务域 | REQ-OPS-020~022 维保计划 + REQ-OPS-030~036 维保任务 | 已实现 |
|
||||||
|
| 5 | 定期巡检 | 运营与服务域 | REQ-OPS-040~043 巡检模板/签到/异常上报 | 已实现 |
|
||||||
|
| 6 | 故障处理 | 设备与资产域 | REQ-ASSET-040~042 故障记录 | 已实现 |
|
||||||
|
| 7 | 故障触发工单 | 运营与服务域 | REQ-OPS-001 工单创建(source=FAULT) | 已实现 |
|
||||||
|
| 8 | 设备报废 | 设备与资产域 | REQ-ASSET-001 设备状态变更为SCRAPPED | 已实现 |
|
||||||
|
|
||||||
|
### 3.3 预防性维保调度流程
|
||||||
|
|
||||||
|
**流程描述**:从维保计划制定到维保执行验收的完整调度闭环。
|
||||||
|
|
||||||
|
| 步骤 | 操作 | 涉及域 | 涉及功能点 | 实现状态 |
|
||||||
|
|------|------|--------|-----------|----------|
|
||||||
|
| 1 | 制定维保计划 | 运营与服务域 | REQ-OPS-020 维保计划创建(type=PREVENTIVE) | 已实现 |
|
||||||
|
| 2 | 设置周期规则 | 运营与服务域 | REQ-OPS-022 维保计划周期调度(cycleDays/nextDate) | 已实现 |
|
||||||
|
| 3 | 自动生成维保任务 | 运营与服务域 | REQ-OPS-023 维保计划自动调度 | 未实现 |
|
||||||
|
| 4 | 手动创建维保任务 | 运营与服务域 | REQ-OPS-030 维保任务创建(triggerType=PLAN) | 已实现 |
|
||||||
|
| 5 | 分配维保人员 | 运营与服务域 | REQ-OPS-031 维保任务状态流转(ASSIGNED) | 已实现 |
|
||||||
|
| 6 | 执行维保 | 运营与服务域 | REQ-OPS-034 维保任务双完成接口 | 已实现 |
|
||||||
|
| 7 | 维保验收 | 运营与服务域 | REQ-OPS-035 维保任务验收 | 已实现 |
|
||||||
|
| 8 | 更新设备记录 | 设备与资产域 | REQ-OPS-036 完成后设备联动 | 已实现 |
|
||||||
|
|
||||||
|
### 3.4 巡检异常处理流程
|
||||||
|
|
||||||
|
**流程描述**:巡检发现异常到问题解决的完整处理流程。
|
||||||
|
|
||||||
|
| 步骤 | 操作 | 涉及域 | 涉及功能点 | 实现状态 |
|
||||||
|
|------|------|--------|-----------|----------|
|
||||||
|
| 1 | 配置巡检模板 | 运营与服务域 | REQ-OPS-040 巡检模板管理 | 已实现 |
|
||||||
|
| 2 | 执行巡检签到 | 运营与服务域 | REQ-OPS-042 巡检签到 | 已实现 |
|
||||||
|
| 3 | 发现异常上报 | 运营与服务域 | REQ-OPS-043 巡检异常上报 | 已实现 |
|
||||||
|
| 4 | 创建维修工单 | 运营与服务域 | REQ-OPS-001 工单创建(source=INSPECTION) | 已实现 |
|
||||||
|
| 5 | 工单处理 | 运营与服务域 | REQ-OPS-004~007 工单派单/执行/完成/验收 | 已实现 |
|
||||||
|
| 6 | 复检确认 | 运营与服务域 | REQ-MDM-033 巡检记录管理(再次巡检) | 已实现 |
|
||||||
|
|
||||||
|
### 3.5 能耗计费流程
|
||||||
|
|
||||||
|
**流程描述**:从能耗抄表到费用收取的完整流程。
|
||||||
|
|
||||||
|
| 步骤 | 操作 | 涉及域 | 涉及功能点 | 实现状态 |
|
||||||
|
|------|------|--------|-----------|----------|
|
||||||
|
| 1 | 配置计量点 | 运营与服务域 | REQ-OPS-060 计量点管理(含单价) | 已实现 |
|
||||||
|
| 2 | 能耗抄表 | 运营与服务域 | REQ-OPS-061 能耗抄表录入 | 已实现 |
|
||||||
|
| 3 | 费用计算 | 运营与服务域 | REQ-OPS-061 自动计算费用(consumption x unitPrice) | 已实现 |
|
||||||
|
| 4 | 生成账单 | 财务与收费域 | REQ-FIN-050 能耗数据与账单对接 | 未实现 |
|
||||||
|
| 5 | 业主缴费 | 财务与收费域 | REQ-FIN-020~022 支付记录管理 | 未实现 |
|
||||||
|
| 6 | 能耗统计分析 | 运营与服务域 | REQ-OPS-063~064 能耗统计 | 部分实现 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、非功能需求
|
||||||
|
|
||||||
|
### 4.1 安全需求
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 |
|
||||||
|
|--------|----------|----------|--------|----------|
|
||||||
|
| REQ-NFR-001 | 密码加密 | 用户密码使用BCrypt算法加密存储,不存储明文 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-002 | JWT认证 | 使用HMAC-SHA256算法生成JWT Token,Token含userId/username/roles,过期时间可配置(默认24小时) | 高 | 已实现 |
|
||||||
|
| REQ-NFR-003 | 登录失败锁定 | 5次登录失败后锁定账户10分钟,基于Redis实现 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-004 | 密码强度校验 | 8-20位,必须包含大小写字母+数字+特殊字符,弱密码黑名单检测 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-005 | 项目数据隔离 | 基于X-Project-ID Header实现项目级数据隔离,DataScopeService四级数据范围过滤 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-006 | 操作审计 | AOP切面自动记录操作日志,异步持久化,30天查询窗口 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-007 | 敏感字段脱敏 | 审计日志中敏感字段自动脱敏(如密码、身份证号) | 中 | 已实现 |
|
||||||
|
| REQ-NFR-008 | XSS防护 | 前端输入过滤、白名单配置 | 高 | 未实现 |
|
||||||
|
| REQ-NFR-009 | CSRF防护 | CSRF Token、SameSite Cookie | 高 | 未实现 |
|
||||||
|
| REQ-NFR-010 | 敏感信息保护 | 移除敏感Header、错误信息脱敏 | 中 | 未实现 |
|
||||||
|
|
||||||
|
### 4.2 性能需求
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 |
|
||||||
|
|--------|----------|----------|--------|----------|
|
||||||
|
| REQ-NFR-020 | 分页查询 | 所有列表接口支持分页查询,避免全量数据返回 | 高 | 部分实现(工单列表为全量返回) |
|
||||||
|
| REQ-NFR-021 | 异步日志 | 审计日志通过线程池异步保存,不影响业务接口响应时间 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-022 | 数据库索引 | 关键查询字段建立索引(审计日志/空间节点/设备/工单/能耗记录等) | 高 | 已实现 |
|
||||||
|
| REQ-NFR-023 | 列表虚拟滚动 | 前端长列表使用虚拟滚动技术,减少DOM节点 | 中 | 未实现 |
|
||||||
|
| REQ-NFR-024 | 请求去重与缓存 | 前端取消重复请求、请求结果缓存 | 中 | 未实现 |
|
||||||
|
| REQ-NFR-025 | WebSocket实时通信 | 替代轮询,实现工单状态变更实时推送 | 低 | 未实现 |
|
||||||
|
|
||||||
|
### 4.3 可用性需求
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 |
|
||||||
|
|--------|----------|----------|--------|----------|
|
||||||
|
| REQ-NFR-030 | 多项目切换 | 用户可在参与的项目间自由切换,切换后数据自动刷新 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-031 | 角色权限动态菜单 | 根据用户角色和项目配置动态渲染菜单和路由 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-032 | 前端权限指令 | v-permission指令控制页面元素显示隐藏 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-033 | 表单分步优化 | 复杂表单使用Steps组件分步验证 | 中 | 未实现 |
|
||||||
|
| REQ-NFR-034 | 加载状态优化 | 骨架屏、防抖等加载状态优化 | 中 | 未实现 |
|
||||||
|
| REQ-NFR-035 | 列表交互增强 | 批量操作、视图切换 | 低 | 未实现 |
|
||||||
|
|
||||||
|
### 4.4 可扩展性需求
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 |
|
||||||
|
|--------|----------|----------|--------|----------|
|
||||||
|
| REQ-NFR-040 | JSONB扩展 | 设备attributes/巡检items&problems/工单photos&partsUsed等使用JSONB存储,支持灵活扩展 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-041 | 扩展表模式 | 设备主表+4张扩展表模式,新增设备类型只需添加扩展表 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-042 | 自定义配置 | 项目配置(ProjectConfig)支持功能开关和customConfig JSON | 高 | 已实现 |
|
||||||
|
| REQ-NFR-043 | 系统键值配置 | SysConfig支持运行时动态参数调整 | 中 | 已实现 |
|
||||||
|
| REQ-NFR-044 | 数据访问授权 | DataAccess通用数据访问控制,支持细粒度授权扩展 | 中 | 已实现 |
|
||||||
|
|
||||||
|
### 4.5 兼容性需求
|
||||||
|
|
||||||
|
| 需求ID | 需求名称 | 需求描述 | 优先级 | 实现状态 |
|
||||||
|
|--------|----------|----------|--------|----------|
|
||||||
|
| REQ-NFR-050 | 浏览器兼容 | 管理后台支持Chrome/Firefox/Edge/Safari最新2个主版本 | 高 | 已实现 |
|
||||||
|
| REQ-NFR-051 | 移动端适配 | 员工APP和业主小程序基于uni-app跨端框架,支持iOS/Android | 高 | 未实现(框架待搭建) |
|
||||||
|
| REQ-NFR-052 | 响应式设计 | 管理后台支持不同屏幕尺寸的响应式布局 | 中 | 未实现 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、需求追踪矩阵
|
||||||
|
|
||||||
|
### 5.1 身份与权限域
|
||||||
|
|
||||||
|
| 需求ID | 名称 | 优先级 | 实现状态 | 设计文档 | 代码模块 |
|
||||||
|
|--------|------|--------|----------|----------|----------|
|
||||||
|
| REQ-AUTH-001 | 用户创建 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-002 | 用户查询 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-003 | 用户更新 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-004 | 用户删除 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-005 | 密码修改 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-006 | 多类型用户扩展 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: EnterpriseUser/ProjectStaff/Resident |
|
||||||
|
| REQ-AUTH-007 | 用户角色分配 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-008 | 用户项目关联 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: UserController |
|
||||||
|
| REQ-AUTH-010 | 角色管理 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-011 | 权限管理 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: PermissionController |
|
||||||
|
| REQ-AUTH-012 | 角色权限分配 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-013 | 四级数据范围 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: DataScopeService |
|
||||||
|
| REQ-AUTH-014 | 双层角色分配 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuthService |
|
||||||
|
| REQ-AUTH-015 | 项目级角色查询 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-016 | 角色关联用户查询 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: RoleController |
|
||||||
|
| REQ-AUTH-017 | 权限树端点 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-AUTH-018 | 权限校验端点 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-AUTH-019 | 用户菜单端点 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-AUTH-020 | 用户权限查询端点 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-AUTH-021 | 角色业务属性 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-AUTH-022 | 权限菜单路由属性 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-AUTH-030 | 部门管理 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: DeptController |
|
||||||
|
| REQ-AUTH-031 | 部门树查询 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: DeptController |
|
||||||
|
| REQ-AUTH-032 | 部门成员查询 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: DeptController |
|
||||||
|
| REQ-AUTH-033 | 按类型查询部门 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: DeptController |
|
||||||
|
| REQ-AUTH-034 | 部门删除约束 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: DeptService |
|
||||||
|
| REQ-AUTH-040 | 项目成员列表 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-041 | 可用成员查询 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-042 | 添加项目成员 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-043 | 移除项目成员 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectMemberController |
|
||||||
|
| REQ-AUTH-044 | 项目员工班次 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectStaff |
|
||||||
|
| REQ-AUTH-045 | 项目员工角色 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectStaffRole |
|
||||||
|
| REQ-AUTH-050 | 住户信息管理 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: Resident |
|
||||||
|
| REQ-AUTH-051 | 住户认证流程 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: Resident |
|
||||||
|
| REQ-AUTH-052 | 住户房屋绑定 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: ResidentSpace |
|
||||||
|
| REQ-AUTH-053 | 房屋空间管理 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: Space |
|
||||||
|
| REQ-AUTH-060 | 用户登录 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-061 | 用户登出 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-062 | Token刷新 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-063 | 获取当前用户 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuthController |
|
||||||
|
| REQ-AUTH-064 | JWT Token规范 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: JwtTokenProvider |
|
||||||
|
| REQ-AUTH-065 | 登录失败锁定 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: LoginAttemptService |
|
||||||
|
| REQ-AUTH-066 | 项目上下文传递 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: ProjectContextInterceptor |
|
||||||
|
| REQ-AUTH-070 | 操作日志记录 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: OperationLogAspect |
|
||||||
|
| REQ-AUTH-071 | 异步持久化 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuditLogService |
|
||||||
|
| REQ-AUTH-072 | 审计日志查询 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: AuditLogController |
|
||||||
|
| REQ-AUTH-073 | 模块与操作类型 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: AuditLog |
|
||||||
|
| REQ-AUTH-074 | 日志统计 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: AuditLogController |
|
||||||
|
| REQ-AUTH-075 | 日志归档 | 低 | 部分实现 | REVERSE-AUTH.md | module-auth: AuditLogService |
|
||||||
|
| REQ-AUTH-080 | 键值配置管理 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: SysConfigController |
|
||||||
|
| REQ-AUTH-081 | 密码策略配置 | 高 | 已实现 | REVERSE-AUTH.md | module-auth: PasswordStrengthValidator |
|
||||||
|
| REQ-AUTH-082 | 数据访问授权 | 中 | 已实现 | REVERSE-AUTH.md | module-auth: DataAccessController |
|
||||||
|
|
||||||
|
### 5.2 空间与项目域
|
||||||
|
|
||||||
|
| 需求ID | 名称 | 优先级 | 实现状态 | 设计文档 | 代码模块 |
|
||||||
|
|--------|------|--------|----------|----------|----------|
|
||||||
|
| REQ-MDM-001 | 项目创建 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-002 | 项目查询 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-003 | 项目更新与删除 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-004 | 项目状态流转 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-005 | 状态变更历史 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectStatusHistory |
|
||||||
|
| REQ-MDM-006 | 项目统计 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-007 | 项目配置管理 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectConfigController |
|
||||||
|
| REQ-MDM-008 | 项目成员管理 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectMemberController |
|
||||||
|
| REQ-MDM-009 | 项目删除检查 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: ProjectController |
|
||||||
|
| REQ-MDM-010 | 空间节点创建 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-MDM-011 | 15种节点类型 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-012 | 树形结构维护 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNodeService |
|
||||||
|
| REQ-MDM-013 | 空间节点查询 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-MDM-014 | 空间节点更新与删除 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-MDM-015 | 面积信息管理 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-016 | 地理信息管理 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-017 | 设备扩展模式 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-018 | 扩展属性JSON | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNode |
|
||||||
|
| REQ-MDM-019 | 空间编码自动生成 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-020 | 节点移动 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-021 | 祖先/子孙查询 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-022 | 空间统计分析 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-023 | PostGIS空间查询 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-024 | 地图服务 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-025 | 空间节点导入导出 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-MDM-030 | 巡检标准项CRUD | 高 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionItemController |
|
||||||
|
| REQ-MDM-031 | 标准项查询筛选 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionItemService |
|
||||||
|
| REQ-MDM-032 | 巡检模板管理 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionTemplateController |
|
||||||
|
| REQ-MDM-033 | 巡检记录管理 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionRecordController |
|
||||||
|
| REQ-MDM-034 | 巡检记录查询 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionRecordService |
|
||||||
|
|
||||||
|
### 5.3 设备与资产域
|
||||||
|
|
||||||
|
| 需求ID | 名称 | 优先级 | 实现状态 | 设计文档 | 代码模块 |
|
||||||
|
|--------|------|--------|----------|----------|----------|
|
||||||
|
| REQ-ASSET-001 | 设备创建 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-002 | 设备查询 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-003 | 设备更新与删除 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-004 | 设备与空间关联 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-005 | 系统类型分类 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-006 | 设备统计 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-007 | 设备导入 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-008 | 设备导出 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentController |
|
||||||
|
| REQ-ASSET-009 | 设备照片管理 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-010 | 设备文档管理 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-011 | 设备二维码 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-ASSET-012 | 设备规格字段 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-ASSET-020 | 电梯扩展表 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentElevatorService |
|
||||||
|
| REQ-ASSET-021 | 暖通扩展表 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentHvacService |
|
||||||
|
| REQ-ASSET-022 | 消防扩展表 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentFireService |
|
||||||
|
| REQ-ASSET-023 | 能源计量扩展表 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentEnergyService |
|
||||||
|
| REQ-ASSET-024 | 扩展表读写模式 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment*Service |
|
||||||
|
| REQ-ASSET-025 | 无扩展表设备类型 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-030 | 健康评分算法 | 高 | 已实现[Beta] | REVERSE-ASSET.md | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-031 | 5级健康等级 | 高 | 已实现[Beta] | REVERSE-ASSET.md | module-asset: HealthLevel |
|
||||||
|
| REQ-ASSET-032 | MTBF计算 | 中 | 已实现[Beta] | REVERSE-ASSET.md | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-033 | MTTR计算 | 中 | 已实现[Beta] | REVERSE-ASSET.md | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-034 | 健康评分查询 | 中 | 已实现[Beta] | REVERSE-ASSET.md | module-asset: EquipmentHealthController |
|
||||||
|
| REQ-ASSET-035 | 维保完成率计算 | 中 | 部分实现 | REVERSE-ASSET.md | module-asset: EquipmentHealthServiceImpl |
|
||||||
|
| REQ-ASSET-040 | 故障记录 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentHealthController |
|
||||||
|
| REQ-ASSET-041 | 故障自动计算 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentFailureHistory |
|
||||||
|
| REQ-ASSET-042 | 故障历史查询 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: EquipmentHealthController |
|
||||||
|
| REQ-ASSET-043 | 故障自动工单 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-ASSET-050 | 归属主体CRUD | 高 | 已实现 | REVERSE-ASSET.md | module-asset: OwnershipEntityController |
|
||||||
|
| REQ-ASSET-051 | 归属主体查询 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: OwnershipEntityController |
|
||||||
|
| REQ-ASSET-052 | 归属主体逻辑删除 | 高 | 已实现 | REVERSE-ASSET.md | module-asset: OwnershipEntityController |
|
||||||
|
| REQ-ASSET-053 | 设备归属冗余 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-060 | 特种设备标记 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-061 | 年检周期管理 | 中 | 已实现 | REVERSE-ASSET.md | module-asset: Equipment |
|
||||||
|
| REQ-ASSET-062 | 特种设备列表查询 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-ASSET-063 | 即将年检预警 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SpaceNodeController |
|
||||||
|
| REQ-ASSET-064 | 维保到期定时提醒 | 中 | 未实现 | - | - |
|
||||||
|
|
||||||
|
### 5.4 运营与服务域
|
||||||
|
|
||||||
|
| 需求ID | 名称 | 优先级 | 实现状态 | 设计文档 | 代码模块 |
|
||||||
|
|--------|------|--------|----------|----------|----------|
|
||||||
|
| REQ-OPS-001 | 工单创建 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-002 | 工单查询 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-003 | 工单状态流转 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderService |
|
||||||
|
| REQ-OPS-004 | 工单派单 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-005 | 工单执行 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-006 | 工单完成 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-007 | 工单验收 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-008 | 工单取消 | 中 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-009 | 工单优先级 | 高 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrder |
|
||||||
|
| REQ-OPS-010 | 工单明细管理 | 中 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-011 | 工单统计 | 中 | 已实现 | REVERSE-OPS.md | module-wo: WorkOrderController |
|
||||||
|
| REQ-OPS-012 | 工单挂起与恢复 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-013 | 工单退回 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-014 | 工单流转记录 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-015 | 工单报修人信息 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-016 | 工单分页查询 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-020 | 维保计划创建 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenancePlan |
|
||||||
|
| REQ-OPS-021 | 维保计划状态管理 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenancePlan |
|
||||||
|
| REQ-OPS-022 | 维保计划周期调度 | 中 | 已实现 | REVERSE-OPS.md | module-wo: MaintenancePlan |
|
||||||
|
| REQ-OPS-023 | 维保计划自动调度 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-030 | 维保任务创建 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-031 | 维保任务状态流转 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskService |
|
||||||
|
| REQ-OPS-032 | 4种触发方式 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTask |
|
||||||
|
| REQ-OPS-033 | 自动优先级判定 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskServiceImpl |
|
||||||
|
| REQ-OPS-034 | 双完成接口 | 中 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-035 | 维保任务验收 | 中 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-036 | 完成后设备联动 | 高 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskServiceImpl |
|
||||||
|
| REQ-OPS-037 | 维保任务统计 | 中 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTaskController |
|
||||||
|
| REQ-OPS-038 | 使用备件记录 | 中 | 已实现 | REVERSE-OPS.md | module-wo: MaintenanceTask |
|
||||||
|
| REQ-OPS-040 | 巡检模板管理 | 高 | 已实现 | REVERSE-OPS.md | module-wo: InspectionTemplate |
|
||||||
|
| REQ-OPS-041 | 巡检项管理 | 高 | 已实现 | REVERSE-OPS.md | module-wo: InspectionItem |
|
||||||
|
| REQ-OPS-042 | 巡检签到 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionRecord |
|
||||||
|
| REQ-OPS-043 | 巡检异常上报 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: InspectionRecord |
|
||||||
|
| REQ-OPS-044 | 巡检扫码签到 | 中 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-050 | 备件分类管理 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-051 | 备件台账CRUD | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-052 | 入库操作 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-053 | 出库操作 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-054 | 低库存预警 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-055 | 出入库记录查询 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartController |
|
||||||
|
| REQ-OPS-056 | 盘点与调整 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: SparePartRecord |
|
||||||
|
| REQ-OPS-060 | 计量点管理 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: EnergyController |
|
||||||
|
| REQ-OPS-061 | 能耗抄表录入 | 高 | 已实现 | REVERSE-MDM.md | module-mdm: EnergyConsumptionService |
|
||||||
|
| REQ-OPS-062 | 能耗记录查询 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: EnergyController |
|
||||||
|
| REQ-OPS-063 | 按类型统计能耗 | 中 | 部分实现 | REVERSE-MDM.md | module-mdm: EnergyConsumptionService |
|
||||||
|
| REQ-OPS-064 | 单位面积能耗 | 中 | 已实现 | REVERSE-MDM.md | module-mdm: EnergyConsumptionService |
|
||||||
|
| REQ-OPS-065 | IoT自动抄表 | 低 | 未实现 | - | - |
|
||||||
|
| REQ-OPS-066 | 前后端能源类型统一 | 中 | 未实现 | - | - |
|
||||||
|
|
||||||
|
### 5.5 财务与收费域
|
||||||
|
|
||||||
|
| 需求ID | 名称 | 优先级 | 实现状态 | 设计文档 | 代码模块 |
|
||||||
|
|--------|------|--------|----------|----------|----------|
|
||||||
|
| REQ-FIN-001 | 收费项目实体 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-002 | 收费项目CRUD | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-010 | 账单实体 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-011 | 账单自动生成 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-012 | 账单状态流转 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-013 | 批量账单生成 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-014 | 账单导出 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-020 | 支付记录实体 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-021 | 线下收款登记 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-022 | 支付记录查询 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-030 | 退款实体 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-031 | 退款流程 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-040 | 费用催缴定时任务 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-041 | 滞纳金自动计算 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-050 | 能耗数据与账单对接 | 高 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-051 | 按面积计费 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-052 | 固定金额计费 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-053 | 支付网关对接 | 中 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
| REQ-FIN-054 | 财务报表 | 低 | 未实现 | REVERSE-FINANCE.md | - |
|
||||||
|
|
||||||
|
### 5.6 需求统计汇总
|
||||||
|
|
||||||
|
| 业务域 | 已实现 | 部分实现 | 未实现 | 合计 |
|
||||||
|
|--------|--------|----------|--------|------|
|
||||||
|
| 身份与权限域 | 37 | 1 | 6 | 44 |
|
||||||
|
| 空间与项目域 | 23 | 0 | 7 | 30 |
|
||||||
|
| 设备与资产域 | 28 | 1 | 4 | 33 |
|
||||||
|
| 运营与服务域 | 36 | 1 | 9 | 46 |
|
||||||
|
| 财务与收费域 | 0 | 0 | 19 | 19 |
|
||||||
|
| 前端交互域 | 14 | 0 | 7 | 21 |
|
||||||
|
| 非功能需求 | 12 | 1 | 8 | 21 |
|
||||||
|
| **合计** | **150** | **4** | **60** | **214** |
|
||||||
|
|
||||||
|
**实现率**:已实现 70.1% | 部分实现 1.9% | 未实现 28.0%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **文档说明**: 本文档基于7份源文档整合生成,所有需求项均标注唯一ID和实现状态。已实现功能以反推文档(REVERSE-*.md)中的实际代码为准,未实现功能从原需求文档和特性清单中提取。需求描述力求具体到可验证程度。
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,578 @@
|
||||||
|
# 跨域业务流程 - 详细设计
|
||||||
|
|
||||||
|
**文档类型**: 详细设计文档
|
||||||
|
**生成日期**: 2026-05-18
|
||||||
|
**数据来源**: REVERSE-FINANCE.md + REVERSE-AUTH.md + 路由/API层源码 + 后端实体分析
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、业主报修全流程
|
||||||
|
|
||||||
|
### 1.1 流程概述
|
||||||
|
|
||||||
|
业主报修是Ether系统中最核心的跨域业务流程,涉及从业主发起报修到最终评价的完整生命周期:
|
||||||
|
|
||||||
|
**业主提交报修 -> 系统创建工单 -> 调度派单 -> 维保人员执行 -> 业主验收 -> 评价反馈**
|
||||||
|
|
||||||
|
### 1.2 涉及域和功能点
|
||||||
|
|
||||||
|
| 步骤 | 涉及域 | 功能点 | API端点 |
|
||||||
|
|------|--------|--------|---------|
|
||||||
|
| 1. 业主身份识别 | 认证域 | 业主登录、Token验证 | `POST /api/auth/login` |
|
||||||
|
| 2. 项目/空间定位 | 空间域 | 获取业主关联房产 | `GET /api/mdm/space-nodes/project/{projectId}/tree` |
|
||||||
|
| 3. 设备关联(可选) | 设备域 | 按空间查询设备 | `GET /api/asset/equipment/by-space/{spaceNodeId}` |
|
||||||
|
| 4. 创建工单 | 运营域 | 创建报修工单 | `POST /api/wo/work-orders` |
|
||||||
|
| 5. 工单派发 | 运营域 | 指派维保人员 | `POST /api/wo/work-orders/{id}/assign` |
|
||||||
|
| 6. 开始执行 | 运营域 | 维保人员接单 | `POST /api/wo/work-orders/{id}/start` |
|
||||||
|
| 7. 完成工单 | 运营域 | 填写处理结果 | `POST /api/wo/work-orders/{id}/complete` |
|
||||||
|
| 8. 业主验收 | 运营域 | 确认维修结果 | `POST /api/wo/work-orders/{id}/verify` |
|
||||||
|
| 9. 评价反馈 | 运营域 | 评分评价 | `POST /api/wo/work-orders/{id}/verify` (含rating) |
|
||||||
|
| 10. 备件消耗 | 备件域 | 出库记录 | `POST /api/ops/spare-parts/out-stock` |
|
||||||
|
| 11. 审计记录 | 认证域 | 操作日志 | 自动记录(AOP) |
|
||||||
|
|
||||||
|
### 1.3 数据流图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[业主登录] -->|POST /api/auth/login| B[获取Token+角色]
|
||||||
|
B --> C[选择项目/房产]
|
||||||
|
C -->|GET /api/mdm/space-nodes/project/id/tree| D[加载空间树]
|
||||||
|
D --> E{是否关联设备?}
|
||||||
|
E -->|是| F[选择设备]
|
||||||
|
F -->|GET /api/asset/equipment/by-space/spaceNodeId| G[设备列表]
|
||||||
|
E -->|否| H[直接描述问题]
|
||||||
|
G --> I[填写报修信息]
|
||||||
|
H --> I
|
||||||
|
I -->|POST /api/wo/work-orders| J[工单创建 PENDING]
|
||||||
|
J -->|POST /api/wo/work-orders/id/assign| K[派发 ASSIGNED]
|
||||||
|
K -->|POST /api/wo/work-orders/id/start| L[执行中 IN_PROGRESS]
|
||||||
|
L --> M{是否需要备件?}
|
||||||
|
M -->|是| N[备件出库]
|
||||||
|
N -->|POST /api/ops/spare-parts/out-stock| O[更新库存]
|
||||||
|
M -->|否| P[填写处理结果]
|
||||||
|
O --> P
|
||||||
|
P -->|POST /api/wo/work-orders/id/complete| Q[已完成 COMPLETED]
|
||||||
|
Q -->|POST /api/wo/work-orders/id/verify| R[已验收 VERIFIED]
|
||||||
|
R --> S[评价评分]
|
||||||
|
|
||||||
|
style A fill:#e1f5fe
|
||||||
|
style J fill:#fff3e0
|
||||||
|
style K fill:#e3f2fd
|
||||||
|
style L fill:#fff8e1
|
||||||
|
style Q fill:#e8f5e9
|
||||||
|
style R fill:#e0f7fa
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 状态流转图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> PENDING: 业主提交报修
|
||||||
|
PENDING --> ASSIGNED: 管理员派单
|
||||||
|
PENDING --> CANCELLED: 业主取消
|
||||||
|
ASSIGNED --> IN_PROGRESS: 维保人员开始执行
|
||||||
|
ASSIGNED --> PENDING: 退回重派
|
||||||
|
ASSIGNED --> CANCELLED: 管理员取消
|
||||||
|
IN_PROGRESS --> SUSPENDED: 挂起(缺件/等审批)
|
||||||
|
IN_PROGRESS --> RETURNED: 退回(无法处理)
|
||||||
|
IN_PROGRESS --> COMPLETED: 填写处理结果
|
||||||
|
SUSPENDED --> IN_PROGRESS: 恢复执行
|
||||||
|
RETURNED --> ASSIGNED: 重新派单
|
||||||
|
COMPLETED --> VERIFIED: 业主验收通过
|
||||||
|
COMPLETED --> IN_PROGRESS: 验收不通过,返工
|
||||||
|
VERIFIED --> [*]: 流程结束
|
||||||
|
CANCELLED --> [*]: 流程结束
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 异常场景处理
|
||||||
|
|
||||||
|
| 异常场景 | 触发条件 | 处理策略 | 涉及API |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| 设备不存在 | 业主选择的设备ID在数据库中不存在 | 返回404错误,提示设备不存在,允许不关联设备提交 | `GET /api/asset/equipment/{id}` |
|
||||||
|
| 派单失败 | 指定的维保人员不存在或不可用 | 返回错误提示,管理员重新选择人员 | `POST /api/wo/work-orders/{id}/assign` |
|
||||||
|
| 执行超时 | 工单超过SLA时间未完成 | 系统自动标记超时,通知项目经理 | 定时任务检查 |
|
||||||
|
| 验收不通过 | 业主对维修结果不满意 | 工单回退到IN_PROGRESS,维保人员返工 | `POST /api/wo/work-orders/{id}/complete` -> 重新执行 |
|
||||||
|
| 备件缺货 | 维修所需备件库存不足 | 工单挂起SUSPENDED,等待备件入库 | `GET /api/ops/spare-parts/low-stock` |
|
||||||
|
| 业主取消 | 业主在派单前取消 | 工单状态变为CANCELLED | `POST /api/wo/work-orders/{id}/cancel` |
|
||||||
|
| 重复提交 | 业主短时间内重复提交相同报修 | 前端防抖 + 后端幂等校验 | `POST /api/wo/work-orders` |
|
||||||
|
| 维保人员退回 | 维保人员无法处理该工单 | 工单退回RETURNED,管理员重新派单 | `POST /api/wo/work-orders/{id}/return` |
|
||||||
|
| 工单挂起 | 执行中遇到阻塞(等审批/等配件) | 工单挂起SUSPENDED,解除后恢复 | `POST /api/wo/work-orders/{id}/suspend` |
|
||||||
|
| 业主未验收 | 完成后业主长时间未验收 | 超过7天自动验收通过 | 定时任务检查 |
|
||||||
|
|
||||||
|
### 1.6 权限控制
|
||||||
|
|
||||||
|
| 角色 | 可执行操作 | 数据范围 |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| 业主(OWNER) | 创建工单、查看自己的工单、验收、评价 | 仅自己创建的工单(SELF) |
|
||||||
|
| 项目管理员(PROJECT_ADMIN) | 查看项目所有工单、派单、取消 | 本项目工单(PROJECT) |
|
||||||
|
| 维保人员(MAINTENANCE) | 查看分配给自己的工单、开始、完成、退回 | 分配给自己的工单(SELF) |
|
||||||
|
| 系统管理员(SYS_ADMIN) | 所有操作 | 全部数据(ALL) |
|
||||||
|
|
||||||
|
**权限校验点**:
|
||||||
|
|
||||||
|
```
|
||||||
|
创建工单: 业主只能为关联房产创建工单
|
||||||
|
派单: 仅项目管理员/系统管理员可派单
|
||||||
|
执行: 仅被指派的维保人员可开始/完成
|
||||||
|
验收: 仅工单创建者(业主)可验收
|
||||||
|
评价: 仅工单创建者(业主)可评价
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、设备全生命周期管理
|
||||||
|
|
||||||
|
### 2.1 流程概述
|
||||||
|
|
||||||
|
设备全生命周期管理覆盖设备从采购入库到最终报废的完整过程:
|
||||||
|
|
||||||
|
**采购登记 -> 安装调试 -> 正常运行 -> 定期维保 -> 巡检监控 -> 故障维修 -> 报废处置**
|
||||||
|
|
||||||
|
### 2.2 涉及域和功能点
|
||||||
|
|
||||||
|
| 步骤 | 涉及域 | 功能点 | API端点 |
|
||||||
|
|------|--------|--------|---------|
|
||||||
|
| 1. 设备采购登记 | 设备域 | 创建设备记录 | `POST /api/asset/equipment` |
|
||||||
|
| 2. 安装位置关联 | 空间域 | 关联空间节点 | `PUT /api/asset/equipment/{id}` (spaceNodeId) |
|
||||||
|
| 3. 扩展信息录入 | 设备域 | 电梯/暖通/消防等扩展 | `PUT /api/asset/equipment/{id}/elevator` 等 |
|
||||||
|
| 4. 照片/文档上传 | 设备域 | 照片和文档管理 | PhotoManager / DocumentManager |
|
||||||
|
| 5. 归属主体关联 | 设备域 | 设置归属类型和主体 | `GET /api/asset/ownership-entity` |
|
||||||
|
| 6. 正常运行监控 | 设备域 | 健康度计算 | `POST /api/asset/equipment-health/calculate` |
|
||||||
|
| 7. 定期维保 | 维保域 | 创建维保计划 | `POST /api/mdm/maintenance-plans` |
|
||||||
|
| 8. 维保执行 | 维保域 | 维保工单流转 | `POST /api/ops/maintenance-tasks/{id}/start` 等 |
|
||||||
|
| 9. 巡检监控 | 巡检域 | 巡检记录 | `POST /api/mdm/inspection-records` |
|
||||||
|
| 10. 故障维修 | 运营域 | 创建维修工单 | `POST /api/wo/work-orders` |
|
||||||
|
| 11. 健康度评估 | 设备域 | MTBF/MTTR分析 | `GET /api/asset/equipment-health/mtbf/{id}` |
|
||||||
|
| 12. 设备报废 | 设备域 | 更新设备状态 | `PUT /api/asset/equipment/{id}` (status=SCRAPPED) |
|
||||||
|
|
||||||
|
### 2.3 数据流图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[设备采购] -->|POST /api/asset/equipment| B[设备登记]
|
||||||
|
B --> C[安装调试]
|
||||||
|
C -->|PUT /api/asset/equipment/id + spaceNodeId| D[关联空间位置]
|
||||||
|
D -->|PUT /api/asset/equipment/id/elevator| E[录入扩展信息]
|
||||||
|
E --> F[正常运行]
|
||||||
|
F -->|POST /api/asset/equipment-health/calculate| G[健康度评估]
|
||||||
|
G --> H{设备状态判断}
|
||||||
|
H -->|健康| I[定期维保]
|
||||||
|
H -->|预警| J[加强巡检]
|
||||||
|
H -->|异常| K[故障维修]
|
||||||
|
|
||||||
|
I -->|POST /api/mdm/maintenance-plans| L[创建维保计划]
|
||||||
|
L -->|定时任务| M[生成维保工单]
|
||||||
|
M -->|POST /api/ops/maintenance-tasks/id/start| N[执行维保]
|
||||||
|
N -->|POST /api/ops/maintenance-tasks/id/complete| O[维保完成]
|
||||||
|
O --> F
|
||||||
|
|
||||||
|
J -->|POST /api/mdm/inspection-records| P[巡检记录]
|
||||||
|
P --> Q{发现异常?}
|
||||||
|
Q -->|是| K
|
||||||
|
Q -->|否| F
|
||||||
|
|
||||||
|
K -->|POST /api/wo/work-orders| R[创建维修工单]
|
||||||
|
R -->|POST /api/wo/work-orders/id/complete| S[维修完成]
|
||||||
|
S -->|POST /api/asset/equipment-health/calculate| T[重新评估健康度]
|
||||||
|
T --> H
|
||||||
|
|
||||||
|
F --> U{达到报废条件?}
|
||||||
|
U -->|是| V[设备报废]
|
||||||
|
U -->|否| I
|
||||||
|
V -->|PUT /api/asset/equipment/id status=SCRAPPED| W[报废处置]
|
||||||
|
|
||||||
|
style F fill:#e8f5e9
|
||||||
|
style K fill:#ffebee
|
||||||
|
style V fill:#f3e5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 状态流转图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> PURCHASED: 采购登记
|
||||||
|
PURCHASED --> INSTALLING: 安装调试
|
||||||
|
INSTALLING --> COMMISSIONING: 调试验收
|
||||||
|
COMMISSIONING --> RUNNING: 验收通过
|
||||||
|
COMMISSIONING --> INSTALLING: 验收不通过
|
||||||
|
RUNNING --> MAINTENANCE: 定期维保
|
||||||
|
RUNNING --> INSPECTION: 巡检监控
|
||||||
|
RUNNING --> FAULT: 故障发生
|
||||||
|
MAINTENANCE --> RUNNING: 维保完成
|
||||||
|
INSPECTION --> RUNNING: 巡检正常
|
||||||
|
INSPECTION --> FAULT: 巡检发现异常
|
||||||
|
FAULT --> REPAIRING: 维修中
|
||||||
|
REPAIRING --> RUNNING: 维修完成
|
||||||
|
REPAIRING --> SCRAPPED: 无法修复
|
||||||
|
RUNNING --> SCRAPPED: 达到使用年限
|
||||||
|
RUNNING --> SCRAPPED: 经济性报废
|
||||||
|
SCRAPPED --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 异常场景处理
|
||||||
|
|
||||||
|
| 异常场景 | 触发条件 | 处理策略 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| 设备重复登记 | 相同序列号/资产编码已存在 | 后端校验唯一性,返回409冲突 |
|
||||||
|
| 安装位置冲突 | 同一空间节点已有同类型设备 | 允许同空间多设备,但给出提示 |
|
||||||
|
| 维保计划冲突 | 同一设备存在重叠的维保计划 | 校验计划周期不重叠 |
|
||||||
|
| 健康度计算失败 | 设备缺少历史数据 | 返回默认健康度,标记为"数据不足" |
|
||||||
|
| 维修无法修复 | 多次维修后仍故障 | 建议报废,升级审批流程 |
|
||||||
|
| 备件不兼容 | 维修使用的备件与设备不匹配 | 备件选择时按设备类型筛选 |
|
||||||
|
| 巡检数据异常 | 巡检结果与设备状态不符 | 标记为"需复核",触发二次巡检 |
|
||||||
|
| 报废审批 | 高价值设备报废 | 需项目经理审批确认 |
|
||||||
|
|
||||||
|
### 2.6 权限控制
|
||||||
|
|
||||||
|
| 角色 | 可执行操作 | 数据范围 |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| 系统管理员(SYS_ADMIN) | 设备全生命周期操作 | ALL |
|
||||||
|
| 项目管理员(PROJECT_ADMIN) | 本项目设备管理 | PROJECT |
|
||||||
|
| 工程人员(ENGINEERING) | 设备登记、维保、巡检 | PROJECT + DEPARTMENT |
|
||||||
|
| 维保人员(MAINTENANCE) | 执行维保工单 | SELF(分配给自己的) |
|
||||||
|
| 业主(OWNER) | 查看关联设备信息 | SELF(关联房产的设备) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、预防性维保调度流程
|
||||||
|
|
||||||
|
### 3.1 流程概述
|
||||||
|
|
||||||
|
预防性维保调度是基于维保计划自动生成维保工单的定时调度流程:
|
||||||
|
|
||||||
|
**创建维保计划 -> 定时调度检查 -> 自动生成维保工单 -> 派单执行 -> 验收 -> 更新设备记录**
|
||||||
|
|
||||||
|
### 3.2 涉及域和功能点
|
||||||
|
|
||||||
|
| 步骤 | 涉及域 | 功能点 | API端点 |
|
||||||
|
|------|--------|--------|---------|
|
||||||
|
| 1. 创建维保计划 | 维保域 | 设置设备、周期、维保内容 | `POST /api/mdm/maintenance-plans` |
|
||||||
|
| 2. 定时调度检查 | 维保域 | MaintenanceScheduler检查到期计划 | 定时任务(Cron) |
|
||||||
|
| 3. 自动生成工单 | 运营域 | 根据计划创建维保工单 | `POST /api/ops/maintenance-tasks` |
|
||||||
|
| 4. 自动派单 | 运营域 | 按计划指定维保人员/供应商 | `POST /api/ops/maintenance-tasks/{id}/assign` |
|
||||||
|
| 5. 执行维保 | 运营域 | 维保人员执行 | `POST /api/ops/maintenance-tasks/{id}/start` |
|
||||||
|
| 6. 记录备件消耗 | 备件域 | 出库记录 | `POST /api/ops/spare-parts/out-stock` |
|
||||||
|
| 7. 完成维保 | 运营域 | 填写维保结果 | `POST /api/ops/maintenance-tasks/{id}/complete-details` |
|
||||||
|
| 8. 验收 | 运营域 | 验收确认 | `POST /api/ops/maintenance-tasks/{id}/verify` |
|
||||||
|
| 9. 更新计划 | 维保域 | 更新lastDate和nextDate | `PUT /api/mdm/maintenance-plans/{id}` |
|
||||||
|
| 10. 更新设备 | 设备域 | 更新设备维保信息 | `PUT /api/asset/equipment/{id}` |
|
||||||
|
|
||||||
|
### 3.3 数据流图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[管理员创建维保计划] -->|POST /api/mdm/maintenance-plans| B[计划存储 ACTIVE]
|
||||||
|
B --> C{MaintenanceScheduler}
|
||||||
|
C -->|每天凌晨检查| D{nextDate <= 今天?}
|
||||||
|
D -->|是| E{计划状态=ACTIVE?}
|
||||||
|
D -->|否| C
|
||||||
|
E -->|是| F[自动生成维保工单]
|
||||||
|
E -->|否| C
|
||||||
|
F -->|POST /api/ops/maintenance-tasks| G[工单 PENDING]
|
||||||
|
G -->|POST /api/ops/maintenance-tasks/id/assign| H[派发 ASSIGNED]
|
||||||
|
H -->|POST /api/ops/maintenance-tasks/id/start| I[执行中 IN_PROGRESS]
|
||||||
|
I --> J{需要备件?}
|
||||||
|
J -->|是| K[备件出库]
|
||||||
|
K -->|POST /api/ops/spare-parts/out-stock| L[更新库存]
|
||||||
|
J -->|否| M[填写维保结果]
|
||||||
|
L --> M
|
||||||
|
M -->|POST /api/ops/maintenance-tasks/id/complete-details| N[已完成 COMPLETED]
|
||||||
|
N -->|POST /api/ops/maintenance-tasks/id/verify| O[已验收 VERIFIED]
|
||||||
|
O -->|PUT /api/mdm/maintenance-plans/id| P[更新计划lastDate/nextDate]
|
||||||
|
P -->|PUT /api/asset/equipment/id| Q[更新设备维保信息]
|
||||||
|
Q --> C
|
||||||
|
|
||||||
|
style C fill:#fff3e0
|
||||||
|
style F fill:#e3f2fd
|
||||||
|
style O fill:#e0f7fa
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 定时调度设计(MaintenanceScheduler)
|
||||||
|
|
||||||
|
**调度器配置**:
|
||||||
|
|
||||||
|
| 配置项 | 值 | 说明 |
|
||||||
|
|--------|-----|------|
|
||||||
|
| Cron表达式 | `0 0 6 * * ?` | 每天凌晨6点执行 |
|
||||||
|
| 检查范围 | 所有ACTIVE状态的维保计划 | nextDate <= 当天 |
|
||||||
|
| 生成策略 | 每个到期计划生成一个维保工单 | triggerType = PLAN |
|
||||||
|
| 防重复 | 检查同一计划+同一天是否已生成工单 | 避免重复生成 |
|
||||||
|
| 通知 | 生成工单后通知项目管理员 | 待实现 |
|
||||||
|
|
||||||
|
**调度逻辑伪代码**:
|
||||||
|
|
||||||
|
```
|
||||||
|
MaintenanceScheduler.execute():
|
||||||
|
1. 查询所有 ACTIVE 状态的维保计划
|
||||||
|
2. 过滤 nextDate <= 今天 的计划
|
||||||
|
3. 对每个到期计划:
|
||||||
|
a. 检查是否已生成工单(planId + 当天日期)
|
||||||
|
b. 若未生成,创建维保工单:
|
||||||
|
- taskType = PREVENTIVE
|
||||||
|
- triggerType = PLAN
|
||||||
|
- equipmentId = plan.equipmentId
|
||||||
|
- title = "预防性维护: " + plan.planName
|
||||||
|
- assignedVendor = plan.assignedVendor
|
||||||
|
c. 更新计划 lastDate = 今天, nextDate = 今天 + plan.cycleDays
|
||||||
|
4. 发送通知给相关项目管理员
|
||||||
|
```
|
||||||
|
|
||||||
|
**nextDate计算规则**:
|
||||||
|
|
||||||
|
```
|
||||||
|
nextDate = lastDate + cycleDays
|
||||||
|
首次: nextDate = 创建日期 + cycleDays
|
||||||
|
完成后: nextDate = 实际完成日期 + cycleDays
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 异常场景处理
|
||||||
|
|
||||||
|
| 异常场景 | 触发条件 | 处理策略 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| 计划已暂停 | 计划状态为SUSPENDED | 跳过该计划,不生成工单 |
|
||||||
|
| 计划已停用 | 计划状态为INACTIVE | 跳过该计划,不生成工单 |
|
||||||
|
| 设备已报废 | 关联设备状态为SCRAPPED | 自动暂停计划,通知管理员 |
|
||||||
|
| 重复生成 | 同一计划同一天已生成工单 | 跳过,幂等处理 |
|
||||||
|
| 供应商不可用 | assignedVendor对应的供应商已失效 | 生成工单但不自动派单,通知管理员手动派单 |
|
||||||
|
| 备件不足 | 维保所需备件库存不足 | 工单挂起SUSPENDED,等待备件入库 |
|
||||||
|
| 维保超时 | 工单超过预估时间未完成 | 通知项目经理,标记超时 |
|
||||||
|
| 调度服务宕机 | 定时任务未执行 | 启动时补偿执行,检查遗漏的计划 |
|
||||||
|
|
||||||
|
### 3.6 权限控制
|
||||||
|
|
||||||
|
| 角色 | 可执行操作 | 数据范围 |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| 系统管理员(SYS_ADMIN) | 创建/修改/删除维保计划 | ALL |
|
||||||
|
| 项目管理员(PROJECT_ADMIN) | 创建/修改本项目维保计划 | PROJECT |
|
||||||
|
| 工程人员(ENGINEERING) | 查看维保计划 | PROJECT |
|
||||||
|
| 维保人员(MAINTENANCE) | 执行分配的维保工单 | SELF |
|
||||||
|
| 外部供应商 | 执行指派的维保工单 | SELF(通过assignedVendor) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、巡检异常处理流程
|
||||||
|
|
||||||
|
### 4.1 流程概述
|
||||||
|
|
||||||
|
巡检异常处理流程描述巡检过程中发现异常后的上报和处理机制:
|
||||||
|
|
||||||
|
**巡检执行 -> 发现异常 -> 上报异常 -> 创建工单 -> 处理异常 -> 复检确认**
|
||||||
|
|
||||||
|
### 4.2 涉及域和功能点
|
||||||
|
|
||||||
|
| 步骤 | 涉及域 | 功能点 | API端点 |
|
||||||
|
|------|--------|--------|---------|
|
||||||
|
| 1. 加载巡检模板 | 巡检域 | 获取点检模板 | `GET /api/ops/inspection-templates?projectId=` |
|
||||||
|
| 2. 执行巡检 | 巡检域 | 创建巡检记录 | `POST /api/mdm/inspection-records` |
|
||||||
|
| 3. 记录巡检项 | 巡检域 | 逐项检查并记录 | InspectionRecordItem[] |
|
||||||
|
| 4. 发现异常 | 巡检域 | 标记异常项 + 问题描述 | problems[] + status=ABNORMAL |
|
||||||
|
| 5. 完成巡检 | 巡检域 | 提交巡检结果 | `POST /api/mdm/inspection-records/{id}/complete` |
|
||||||
|
| 6. 自动创建工单 | 运营域 | 根据异常自动创建工单 | `POST /api/wo/work-orders` |
|
||||||
|
| 7. 派单处理 | 运营域 | 指派维修人员 | `POST /api/wo/work-orders/{id}/assign` |
|
||||||
|
| 8. 维修执行 | 运营域 | 执行维修 | `POST /api/wo/work-orders/{id}/start` -> `complete` |
|
||||||
|
| 9. 复检 | 巡检域 | 对异常项进行复检 | `POST /api/mdm/inspection-records` (复检记录) |
|
||||||
|
| 10. 更新设备状态 | 设备域 | 更新设备运行状态 | `PUT /api/asset/equipment/{id}` |
|
||||||
|
|
||||||
|
### 4.3 数据流图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[巡检人员选择设备] -->|GET /api/ops/inspection-templates| B[加载点检模板]
|
||||||
|
B --> C[逐项检查]
|
||||||
|
C --> D{检查结果}
|
||||||
|
D -->|全部正常| E[标记NORMAL]
|
||||||
|
D -->|部分预警| F[标记WARNING]
|
||||||
|
D -->|发现异常| G[标记ABNORMAL]
|
||||||
|
|
||||||
|
E -->|POST /api/mdm/inspection-records/id/complete| H[巡检完成]
|
||||||
|
F --> H
|
||||||
|
G --> I[记录异常详情]
|
||||||
|
I -->|problems数组| J[提交巡检记录]
|
||||||
|
J -->|POST /api/mdm/inspection-records/id/complete| K[巡检完成 ABNORMAL]
|
||||||
|
|
||||||
|
K --> L{异常严重度}
|
||||||
|
L -->|HIGH 严重| M[自动创建紧急工单]
|
||||||
|
L -->|MEDIUM 中等| N[自动创建普通工单]
|
||||||
|
L -->|LOW 轻微| O[记录待处理]
|
||||||
|
|
||||||
|
M -->|POST /api/wo/work-orders priority=URGENT| P[工单 PENDING]
|
||||||
|
N -->|POST /api/wo/work-orders priority=MEDIUM| P
|
||||||
|
O --> Q[定期汇总处理]
|
||||||
|
|
||||||
|
P -->|POST /api/wo/work-orders/id/assign| R[派发 ASSIGNED]
|
||||||
|
R -->|POST /api/wo/work-orders/id/start| S[执行中 IN_PROGRESS]
|
||||||
|
S -->|POST /api/wo/work-orders/id/complete| T[已完成 COMPLETED]
|
||||||
|
T -->|POST /api/wo/work-orders/id/verify| U[已验收 VERIFIED]
|
||||||
|
|
||||||
|
U --> V[复检]
|
||||||
|
V -->|POST /api/mdm/inspection-records 复检记录| W{复检结果}
|
||||||
|
W -->|通过| X[更新设备状态正常]
|
||||||
|
W -->|不通过| Y[重新创建工单]
|
||||||
|
X -->|PUT /api/asset/equipment/id| Z[流程结束]
|
||||||
|
Y --> P
|
||||||
|
|
||||||
|
style G fill:#ffebee
|
||||||
|
style K fill:#fff3e0
|
||||||
|
style M fill:#ffcdd2
|
||||||
|
style X fill:#e8f5e9
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 异常场景处理
|
||||||
|
|
||||||
|
| 异常场景 | 触发条件 | 处理策略 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| 巡检模板缺失 | 设备类型无对应模板 | 允许自由录入巡检项,提示管理员创建模板 |
|
||||||
|
| 巡检中断 | 巡检过程中网络断开 | 本地暂存巡检数据,恢复后提交 |
|
||||||
|
| 异常误报 | 巡检人员误判为异常 | 复检时纠正,工单可取消 |
|
||||||
|
| 工单创建失败 | 自动创建工单时后端异常 | 巡检记录仍保存,手动创建工单 |
|
||||||
|
| 维修后复检不通过 | 异常未完全修复 | 重新创建工单,升级优先级 |
|
||||||
|
| 多次复检不通过 | 同一异常反复出现 | 标记为"顽固故障",建议设备报废评估 |
|
||||||
|
| 巡检人员不足 | 无可用巡检人员 | 延迟巡检,通知项目经理 |
|
||||||
|
| 设备无法巡检 | 设备位置不可达或停机 | 记录原因,标记为"待巡检" |
|
||||||
|
|
||||||
|
### 4.5 权限控制
|
||||||
|
|
||||||
|
| 角色 | 可执行操作 | 数据范围 |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| 巡检人员(INSPECTOR) | 执行巡检、记录异常 | SELF(分配给自己的巡检任务) |
|
||||||
|
| 项目管理员(PROJECT_ADMIN) | 查看巡检结果、派单处理 | PROJECT |
|
||||||
|
| 维保人员(MAINTENANCE) | 处理异常工单 | SELF(分配给自己的工单) |
|
||||||
|
| 系统管理员(SYS_ADMIN) | 管理巡检模板 | ALL |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、能耗计费流程
|
||||||
|
|
||||||
|
### 5.1 流程概述
|
||||||
|
|
||||||
|
能耗计费流程覆盖从抄表到最终支付的完整计费链路,部分环节已实现:
|
||||||
|
|
||||||
|
**抄表录入 -> 消耗量计算 -> 费用计算 -> 账单生成 -> 催缴提醒 -> 支付 -> 滞纳金计算 [部分实现]**
|
||||||
|
|
||||||
|
### 5.2 涉及域和功能点
|
||||||
|
|
||||||
|
| 步骤 | 涉及域 | 功能点 | 实现状态 | API端点 |
|
||||||
|
|------|--------|--------|---------|---------|
|
||||||
|
| 1. 计量点管理 | MDM域 | 创建/管理计量点 | 已实现 | `POST/GET/PUT/DELETE /api/ops/energy/meters` |
|
||||||
|
| 2. 抄表录入 | MDM域 | 录入能耗读数 | 已实现 | `POST /api/ops/energy/consumption` |
|
||||||
|
| 3. 消耗量计算 | MDM域 | currentReading - previousReading | 已实现 | 自动计算 |
|
||||||
|
| 4. 费用计算 | MDM域 | consumption * unitPrice | 已实现 | 自动计算 |
|
||||||
|
| 5. 按类型统计 | MDM域 | 按能源类型汇总 | 已实现(有缺陷) | `GET /api/ops/energy/statistics/by-type` |
|
||||||
|
| 6. 单方能耗 | MDM域 | 项目总消耗/总面积 | 已实现 | `GET /api/ops/energy/statistics/unit-consumption` |
|
||||||
|
| 7. 收费项目配置 | 财务域 | 创建按用量计费的收费项目 | 未实现 | `POST /api/finance/fee-items` |
|
||||||
|
| 8. 账单生成 | 财务域 | 根据能耗数据生成账单 | 未实现 | `POST /api/finance/bills/generate` |
|
||||||
|
| 9. 催缴提醒 | 财务域 | 到期/逾期提醒 | 未实现 | 定时任务 |
|
||||||
|
| 10. 滞纳金计算 | 财务域 | 逾期天数 * 日利率 | 未实现 | 定时任务 |
|
||||||
|
| 11. 线下收款 | 财务域 | 登记支付 | 未实现 | `POST /api/finance/payments` |
|
||||||
|
| 12. 线上支付 | 财务域 | 微信/支付宝 | 未实现 | `POST /api/finance/payments/online/prepare` |
|
||||||
|
| 13. 退款 | 财务域 | 退款申请/审批/执行 | 未实现 | `POST /api/finance/refunds` |
|
||||||
|
|
||||||
|
### 5.3 数据流图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph 已实现
|
||||||
|
A[创建计量点] -->|POST /api/ops/energy/meters| B[EnergyMeter]
|
||||||
|
B --> C[抄表录入]
|
||||||
|
C -->|POST /api/ops/energy/consumption| D[EnergyConsumption]
|
||||||
|
D -->|自动计算| E[consumption = current - previous]
|
||||||
|
E -->|自动计算| F[amount = consumption * unitPrice]
|
||||||
|
F --> G[能耗统计]
|
||||||
|
G -->|GET /api/ops/energy/statistics/by-type| H[按类型汇总]
|
||||||
|
G -->|GET /api/ops/energy/statistics/unit-consumption| I[单方能耗]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 未实现
|
||||||
|
F --> J[收费项目配置]
|
||||||
|
J -->|POST /api/finance/fee-items type=ELECTRICITY/WATER/GAS billingMethod=USAGE| K[FeeItem]
|
||||||
|
K --> L[账单自动生成]
|
||||||
|
L -->|POST /api/finance/bills/generate sourceType=AUTO sourceId=EnergyConsumption.id| M[FeeBill UNPAID]
|
||||||
|
M --> N{到期提醒}
|
||||||
|
N -->|到期前3天| O[发送提醒]
|
||||||
|
N -->|逾期| P[计算滞纳金]
|
||||||
|
P -->|lateFee = 逾期天数 * payableAmount * lateFeeRate| Q[更新滞纳金]
|
||||||
|
M --> R[支付]
|
||||||
|
R -->|线下| S[登记收款 POST /api/finance/payments]
|
||||||
|
R -->|线上| T[发起支付 POST /api/finance/payments/online/prepare]
|
||||||
|
S --> U[FeePayment SUCCESS]
|
||||||
|
T --> V[等待回调]
|
||||||
|
V --> U
|
||||||
|
U --> W[更新账单状态 PAID]
|
||||||
|
U --> X{需要退款?}
|
||||||
|
X -->|是| Y[申请退款 POST /api/finance/refunds]
|
||||||
|
Y --> Z[审批]
|
||||||
|
Z --> AA[执行退款]
|
||||||
|
X -->|否| AB[流程结束]
|
||||||
|
end
|
||||||
|
|
||||||
|
style A fill:#e8f5e9
|
||||||
|
style B fill:#e8f5e9
|
||||||
|
style C fill:#e8f5e9
|
||||||
|
style D fill:#e8f5e9
|
||||||
|
style E fill:#e8f5e9
|
||||||
|
style F fill:#e8f5e9
|
||||||
|
style G fill:#e8f5e9
|
||||||
|
style H fill:#e8f5e9
|
||||||
|
style I fill:#e8f5e9
|
||||||
|
style J fill:#fff3e0
|
||||||
|
style K fill:#fff3e0
|
||||||
|
style L fill:#fff3e0
|
||||||
|
style M fill:#fff3e0
|
||||||
|
style N fill:#fff3e0
|
||||||
|
style O fill:#fff3e0
|
||||||
|
style P fill:#fff3e0
|
||||||
|
style Q fill:#fff3e0
|
||||||
|
style R fill:#fff3e0
|
||||||
|
style S fill:#fff3e0
|
||||||
|
style T fill:#fff3e0
|
||||||
|
style U fill:#fff3e0
|
||||||
|
style V fill:#fff3e0
|
||||||
|
style W fill:#fff3e0
|
||||||
|
style X fill:#fff3e0
|
||||||
|
style Y fill:#fff3e0
|
||||||
|
style Z fill:#fff3e0
|
||||||
|
style AA fill:#fff3e0
|
||||||
|
style AB fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 异常场景处理
|
||||||
|
|
||||||
|
| 异常场景 | 触发条件 | 处理策略 | 实现状态 |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| 读数递减 | 当前读数小于上次读数 | 拒绝录入,返回错误码6103 | 已实现 |
|
||||||
|
| 仪表不存在 | meterId在数据库中不存在 | 拒绝录入,返回错误码6101 | 已实现 |
|
||||||
|
| 仪表已停用 | 仪表状态为INACTIVE | 拒绝录入,返回错误码6102 | 已实现 |
|
||||||
|
| 单价未设置 | meter.unitPrice为null | amount设为0 | 已实现 |
|
||||||
|
| 按类型统计错误 | 全部归入LIGHTING | 修复为按meter.energyType分项汇总 | 待修复 |
|
||||||
|
| 前后端枚举不一致 | 后端6种 vs 前端5种 | 统一枚举定义 | 待修复 |
|
||||||
|
| 能耗数据缺失 | 账期内无抄表记录 | 生成0元账单,标记"无抄表数据" | 未实现 |
|
||||||
|
| 业主无关联房产 | 生成账单时业主无房产 | 跳过该业主,记录日志 | 未实现 |
|
||||||
|
| 重复生成账单 | 同一收费项目+业主+账期 | 幂等校验,跳过已存在的账单 | 未实现 |
|
||||||
|
| 支付超时 | 线上支付未收到回调 | 对账任务检查,人工介入 | 未实现 |
|
||||||
|
| 滞纳金溢出 | 计算结果超过maxLateFee | 截断为maxLateFee上限 | 未实现 |
|
||||||
|
| 部分支付后退款 | 业主部分支付后申请退款 | 按支付记录逐笔退款 | 未实现 |
|
||||||
|
|
||||||
|
### 5.5 权限控制
|
||||||
|
|
||||||
|
| 角色 | 可执行操作 | 数据范围 |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| 系统管理员(SYS_ADMIN) | 管理计量点、收费项目、账单 | ALL |
|
||||||
|
| 项目管理员(PROJECT_ADMIN) | 查看本项目能耗和账单 | PROJECT |
|
||||||
|
| 财务人员(FINANCE_STAFF) | 收费登记、账单管理 | PROJECT |
|
||||||
|
| 财务主管(FINANCE_SUPERVISOR) | 退款审批、财务报表 | PROJECT |
|
||||||
|
| 业主(OWNER) | 查看自己的账单、在线缴费 | SELF |
|
||||||
|
| 巡检/维保人员 | 抄表录入 | SELF(分配给自己的) |
|
||||||
|
|
||||||
|
**已实现的权限控制**:
|
||||||
|
|
||||||
|
- 计量点管理:无特定角色限制(仅要求已登录)
|
||||||
|
- 能耗录入:无特定角色限制(仅要求已登录)
|
||||||
|
- 能耗统计:无特定角色限制(仅要求已登录)
|
||||||
|
|
||||||
|
**待实现的权限控制**:
|
||||||
|
|
||||||
|
- 收费项目管理:finance:fee-item:* 权限
|
||||||
|
- 账单管理:finance:bill:* 权限
|
||||||
|
- 支付管理:finance:payment:* 权限
|
||||||
|
- 退款管理:finance:refund:* 权限
|
||||||
|
- 业主端:仅查看自己的账单和支付记录
|
||||||
|
|
@ -0,0 +1,810 @@
|
||||||
|
# 财务与收费域 - 详细设计
|
||||||
|
|
||||||
|
**文档类型**: 详细设计文档
|
||||||
|
**生成日期**: 2026-05-18
|
||||||
|
**数据来源**: REVERSE-FINANCE.md(反推文档)+ 04-FINANCE.md(原设计文档)+ 实际代码分析
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、功能点清单
|
||||||
|
|
||||||
|
| 编号 | 功能模块 | 功能点 | 实现状态 | 说明 |
|
||||||
|
|------|---------|--------|---------|------|
|
||||||
|
| FIN-001 | 能耗计量管理 | 计量点CRUD | 已实现 | module-mdm 中 EnergyMeter |
|
||||||
|
| FIN-002 | 能耗计量管理 | 计量点编码自动生成 | 已实现 | EM + yyyyMMddHHmmss |
|
||||||
|
| FIN-003 | 能耗计量管理 | 计量点软删除 | 已实现 | 状态设为 INACTIVE |
|
||||||
|
| FIN-004 | 能耗录入 | 能耗数据录入 | 已实现 | 手动录入 + IoT预留 |
|
||||||
|
| FIN-005 | 能耗录入 | 读数递增校验 | 已实现 | 当前读数 >= 上次读数 |
|
||||||
|
| FIN-006 | 能耗录入 | 自动计算消耗量与费用 | 已实现 | consumption = current - previous; amount = consumption * unitPrice |
|
||||||
|
| FIN-007 | 能耗统计 | 按类型统计消耗 | 已实现(有缺陷) | 当前全部归入LIGHTING,需修复 |
|
||||||
|
| FIN-008 | 能耗统计 | 单方能耗 | 已实现 | 遍历项目ACTIVE计量点累加 |
|
||||||
|
| FIN-009 | 收费项目管理 | 收费项目CRUD | 未实现 | FeeItem 实体不存在 |
|
||||||
|
| FIN-010 | 收费项目管理 | 周期性收费配置 | 未实现 | 按月/季/年收费 |
|
||||||
|
| FIN-011 | 收费项目管理 | 一次性收费配置 | 未实现 | 临时收费项目 |
|
||||||
|
| FIN-012 | 收费项目管理 | 按面积计费配置 | 未实现 | 面积 * 单价 |
|
||||||
|
| FIN-013 | 收费项目管理 | 按用量计费配置 | 未实现 | 用量 * 单价(对接能耗) |
|
||||||
|
| FIN-014 | 收费项目管理 | 固定金额计费配置 | 未实现 | 每月固定金额 |
|
||||||
|
| FIN-015 | 账单管理 | 账单自动生成 | 未实现 | 根据收费项目规则按账期生成 |
|
||||||
|
| FIN-016 | 账单管理 | 账单手动生成 | 未实现 | 人工创建单笔账单 |
|
||||||
|
| FIN-017 | 账单管理 | 批量账单生成 | 未实现 | 一次性为项目所有业主生成 |
|
||||||
|
| FIN-018 | 账单管理 | 账单状态流转 | 未实现 | UNPAID/PARTIAL_PAID/PAID/OVERDUE/CANCELLED |
|
||||||
|
| FIN-019 | 账单管理 | 催缴提醒 | 未实现 | 到期前3天提醒 + 逾期催缴 |
|
||||||
|
| FIN-020 | 账单管理 | 账单导出 | 未实现 | Excel/PDF 格式 |
|
||||||
|
| FIN-021 | 支付管理 | 线下收款登记 | 未实现 | 现金/银行转账/刷卡 |
|
||||||
|
| FIN-022 | 支付管理 | 线上支付对接 | 未实现 | 微信/支付宝 |
|
||||||
|
| FIN-023 | 支付管理 | 支付确认 | 未实现 | 支付状态确认 |
|
||||||
|
| FIN-024 | 支付管理 | 支付记录查询 | 未实现 | 按账单/业主/时间查询 |
|
||||||
|
| FIN-025 | 退款管理 | 退款申请 | 未实现 | 业主发起退款 |
|
||||||
|
| FIN-026 | 退款管理 | 退款审核 | 未实现 | 审批流程 |
|
||||||
|
| FIN-027 | 退款管理 | 退款执行 | 未实现 | 原路退回/线下退款 |
|
||||||
|
| FIN-028 | 滞纳金 | 滞纳金自动计算 | 未实现 | 逾期天数 * 日利率 |
|
||||||
|
| FIN-029 | 滞纳金 | 滞纳金上限控制 | 未实现 | 不超过 maxLateFee |
|
||||||
|
| FIN-030 | 能耗对接 | 能耗数据与账单对接 | 未实现 | EnergyConsumption -> FeeBill |
|
||||||
|
| FIN-031 | 能耗修复 | 修复按类型统计 | 未实现 | 按 meter.energyType 真正分项汇总 |
|
||||||
|
| FIN-032 | 能耗修复 | 统一前后端能源类型枚举 | 未实现 | 后端6种 vs 前端5种 |
|
||||||
|
| FIN-033 | 财务报表 | 收费统计 | 未实现 | 按项目/类型/时间维度 |
|
||||||
|
| FIN-034 | 财务报表 | 欠费分析 | 未实现 | 逾期/欠费金额统计 |
|
||||||
|
| FIN-035 | 财务报表 | 收入趋势 | 未实现 | 月度/季度趋势图 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、数据结构设计
|
||||||
|
|
||||||
|
### 2.1 已实现实体
|
||||||
|
|
||||||
|
#### 2.1.1 EnergyMeter(能源计量点)
|
||||||
|
|
||||||
|
**所在模块**: module-mdm
|
||||||
|
**数据库表**: `ops_energy_meter`
|
||||||
|
**源码**: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java`
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|
||||||
|
|--------|------|-----------|------|------|
|
||||||
|
| id | UUID | id | PK | 主键 |
|
||||||
|
| projectId | UUID | project_id | NOT NULL | 所属项目 |
|
||||||
|
| meterCode | String | meter_code | NOT NULL, UNIQUE | 计量点编码(EM + yyyyMMddHHmmss) |
|
||||||
|
| meterName | String | meter_name | NOT NULL | 计量点名称 |
|
||||||
|
| energyType | EnergyType | energy_type | NOT NULL | 能源类型枚举 |
|
||||||
|
| spaceNodeId | UUID | space_node_id | -- | 关联空间节点 |
|
||||||
|
| installationLocation | String | installation_location | -- | 安装位置 |
|
||||||
|
| ratedCapacity | BigDecimal(10,2) | rated_capacity | -- | 额定容量 |
|
||||||
|
| unitPrice | BigDecimal(10,4) | unit_price | -- | 单价(4位小数精度) |
|
||||||
|
| status | Status | status | NOT NULL, 默认ACTIVE | 状态枚举 |
|
||||||
|
| createdAt | LocalDateTime | created_at | -- | 创建时间 |
|
||||||
|
| updatedAt | LocalDateTime | updated_at | -- | 更新时间 |
|
||||||
|
|
||||||
|
**枚举定义**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum EnergyType {
|
||||||
|
LIGHTING("照明插座用电"),
|
||||||
|
HVAC("空调用电"),
|
||||||
|
POWER("动力用电"),
|
||||||
|
SPECIAL("特殊用电"),
|
||||||
|
WATER("给排水"),
|
||||||
|
GAS("燃气");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
ACTIVE, INACTIVE
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.1.2 EnergyConsumption(能耗记录)
|
||||||
|
|
||||||
|
**所在模块**: module-mdm
|
||||||
|
**数据库表**: `ops_energy_consumption`
|
||||||
|
**源码**: `ether-pms/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java`
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|
||||||
|
|--------|------|-----------|------|------|
|
||||||
|
| id | UUID | id | PK | 主键 |
|
||||||
|
| projectId | UUID | project_id | NOT NULL | 所属项目 |
|
||||||
|
| meterId | UUID | meter_id | NOT NULL | 关联计量点 |
|
||||||
|
| consumptionDate | LocalDate | consumption_date | NOT NULL | 记录日期 |
|
||||||
|
| previousReading | BigDecimal(12,2) | previous_reading | -- | 上次读数 |
|
||||||
|
| currentReading | BigDecimal(12,2) | current_reading | -- | 当前读数 |
|
||||||
|
| consumption | BigDecimal(12,2) | consumption | NOT NULL | 消耗量 = current - previous |
|
||||||
|
| amount | BigDecimal(10,2) | amount | -- | 费用 = consumption * unitPrice |
|
||||||
|
| recordedBy | UUID | recorded_by | -- | 记录人 |
|
||||||
|
| recordMethod | RecordMethod | record_method | -- | 记录方式,默认MANUAL |
|
||||||
|
| remarks | String(1000) | remarks | -- | 备注 |
|
||||||
|
| createdAt | LocalDateTime | created_at | -- | 创建时间 |
|
||||||
|
|
||||||
|
**数据库索引**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
idx_ec_meter_date -- (meter_id, consumption_date)
|
||||||
|
idx_ec_project_date -- (project_id, consumption_date)
|
||||||
|
```
|
||||||
|
|
||||||
|
**枚举定义**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum RecordMethod {
|
||||||
|
MANUAL, // 手动录入
|
||||||
|
IOT // IoT自动采集
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 待实现实体
|
||||||
|
|
||||||
|
#### 2.2.1 FeeItem(收费项目)
|
||||||
|
|
||||||
|
**目标模块**: module-finance(待创建)
|
||||||
|
**目标表**: `fin_fee_item`
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|
||||||
|
|--------|------|-----------|------|------|
|
||||||
|
| id | UUID | id | PK | 主键 |
|
||||||
|
| projectId | UUID | project_id | NOT NULL | 所属项目 |
|
||||||
|
| code | VARCHAR(50) | code | NOT NULL, UNIQUE | 收费项目编码(FI + yyyyMMddHHmmss) |
|
||||||
|
| name | VARCHAR(100) | name | NOT NULL | 收费项目名称 |
|
||||||
|
| type | VARCHAR(20) | type | NOT NULL | 收费类型枚举 |
|
||||||
|
| billingMethod | VARCHAR(20) | billing_method | NOT NULL | 计费方式枚举 |
|
||||||
|
| unitPrice | DECIMAL(12,4) | unit_price | -- | 单价(按用量/面积计费时使用) |
|
||||||
|
| unit | VARCHAR(20) | unit | -- | 计量单位(kWh/吨/平方米/月/次) |
|
||||||
|
| fixedAmount | DECIMAL(12,2) | fixed_amount | -- | 固定金额(FIXED计费方式) |
|
||||||
|
| billDay | INT | bill_day | -- | 出账日(每月几号出账,1-28) |
|
||||||
|
| dueDay | INT | due_day | -- | 到期日(出账后第N天到期) |
|
||||||
|
| overdueDay | INT | overdue_day | -- | 逾期日(到期后第N天开始计滞纳金) |
|
||||||
|
| enableLateFee | BOOLEAN | enable_late_fee | NOT NULL, 默认false | 是否启用滞纳金 |
|
||||||
|
| lateFeeRate | DECIMAL(8,6) | late_fee_rate | -- | 日滞纳金利率(如0.0005 = 万分之五/天) |
|
||||||
|
| maxLateFee | DECIMAL(12,2) | max_late_fee | -- | 滞纳金上限金额 |
|
||||||
|
| description | VARCHAR(500) | description | -- | 收费项目说明 |
|
||||||
|
| status | VARCHAR(20) | status | NOT NULL, 默认ENABLED | 状态:ENABLED/DISABLED |
|
||||||
|
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
|
||||||
|
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
|
||||||
|
| createdBy | UUID | created_by | -- | 创建人 |
|
||||||
|
|
||||||
|
**枚举定义**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum FeeType {
|
||||||
|
PROPERTY("物业费"),
|
||||||
|
PARKING("停车费"),
|
||||||
|
WATER("水费"),
|
||||||
|
ELECTRICITY("电费"),
|
||||||
|
GAS("燃气费"),
|
||||||
|
HEATING("供暖费"),
|
||||||
|
REPAIR_FUND("维修基金"),
|
||||||
|
OTHER("其他");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BillingMethod {
|
||||||
|
FIXED("固定金额"), // 每月固定金额
|
||||||
|
AREA("按面积计费"), // 面积 * 单价
|
||||||
|
USAGE("按用量计费"), // 用量 * 单价(对接能耗)
|
||||||
|
CUSTOM("自定义"); // 自定义计费规则
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据库索引**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
idx_fi_project_status -- (project_id, status)
|
||||||
|
idx_fi_code -- (code) -- UNIQUE约束自带索引
|
||||||
|
idx_fi_type -- (type, status)
|
||||||
|
```
|
||||||
|
|
||||||
|
**外键关系**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
fk_fi_project -- project_id REFERENCES mdm_project(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2.2.2 FeeBill(收费账单)
|
||||||
|
|
||||||
|
**目标模块**: module-finance(待创建)
|
||||||
|
**目标表**: `fin_fee_bill`
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|
||||||
|
|--------|------|-----------|------|------|
|
||||||
|
| id | UUID | id | PK | 主键 |
|
||||||
|
| billNo | VARCHAR(30) | bill_no | NOT NULL, UNIQUE | 账单编号(BL + yyyyMMdd + 4位序号) |
|
||||||
|
| feeItemId | UUID | fee_item_id | NOT NULL | 关联收费项目 |
|
||||||
|
| projectId | UUID | project_id | NOT NULL | 所属项目 |
|
||||||
|
| spaceNodeId | UUID | space_node_id | -- | 关联空间节点(房产) |
|
||||||
|
| ownerId | UUID | owner_id | -- | 关联业主ID |
|
||||||
|
| billPeriod | VARCHAR(20) | bill_period | NOT NULL | 账期(如2026-05) |
|
||||||
|
| billDate | DATE | bill_date | NOT NULL | 出账日期 |
|
||||||
|
| dueDate | DATE | due_date | NOT NULL | 到期日期 |
|
||||||
|
| overdueDate | DATE | overdue_date | -- | 逾期日期 |
|
||||||
|
| usageAmount | DECIMAL(12,2) | usage_amount | -- | 用量(按用量计费时) |
|
||||||
|
| usageUnit | VARCHAR(20) | usage_unit | -- | 用量单位 |
|
||||||
|
| area | DECIMAL(12,2) | area | -- | 面积(按面积计费时) |
|
||||||
|
| amount | DECIMAL(12,2) | amount | NOT NULL | 应收金额 |
|
||||||
|
| lateFee | DECIMAL(12,2) | late_fee | 默认0 | 滞纳金 |
|
||||||
|
| discount | DECIMAL(12,2) | discount | 默认0 | 优惠金额 |
|
||||||
|
| payableAmount | DECIMAL(12,2) | payable_amount | NOT NULL | 应付金额 = amount + lateFee - discount |
|
||||||
|
| paidAmount | DECIMAL(12,2) | paid_amount | 默认0 | 已付金额 |
|
||||||
|
| status | VARCHAR(20) | status | NOT NULL, 默认UNPAID | 账单状态枚举 |
|
||||||
|
| sourceType | VARCHAR(20) | source_type | -- | 来源类型:AUTO/MANUAL/IMPORT |
|
||||||
|
| sourceId | UUID | source_id | -- | 来源ID(如能耗记录ID) |
|
||||||
|
| remark | VARCHAR(500) | remark | -- | 备注 |
|
||||||
|
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
|
||||||
|
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
|
||||||
|
| createdBy | UUID | created_by | -- | 创建人 |
|
||||||
|
|
||||||
|
**枚举定义**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum BillStatus {
|
||||||
|
UNPAID("未支付"),
|
||||||
|
PARTIAL_PAID("部分支付"),
|
||||||
|
PAID("已支付"),
|
||||||
|
OVERDUE("已逾期"),
|
||||||
|
CANCELLED("已取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BillSourceType {
|
||||||
|
AUTO("自动生成"),
|
||||||
|
MANUAL("手动创建"),
|
||||||
|
IMPORT("导入");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据库索引**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
idx_fb_bill_no -- (bill_no) -- UNIQUE约束自带索引
|
||||||
|
idx_fb_project_period -- (project_id, bill_period)
|
||||||
|
idx_fb_owner_status -- (owner_id, status)
|
||||||
|
idx_fb_fee_item -- (fee_item_id, bill_period)
|
||||||
|
idx_fb_space_node -- (space_node_id, bill_period)
|
||||||
|
idx_fb_due_date -- (due_date, status) -- 用于催缴查询
|
||||||
|
idx_fb_overdue -- (status, overdue_date) -- 用于滞纳金计算
|
||||||
|
```
|
||||||
|
|
||||||
|
**外键关系**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
fk_fb_fee_item -- fee_item_id REFERENCES fin_fee_item(id)
|
||||||
|
fk_fb_project -- project_id REFERENCES mdm_project(id)
|
||||||
|
fk_fb_space_node -- space_node_id REFERENCES mdm_space_node(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2.2.3 FeePayment(支付记录)
|
||||||
|
|
||||||
|
**目标模块**: module-finance(待创建)
|
||||||
|
**目标表**: `fin_fee_payment`
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|
||||||
|
|--------|------|-----------|------|------|
|
||||||
|
| id | UUID | id | PK | 主键 |
|
||||||
|
| paymentNo | VARCHAR(30) | payment_no | NOT NULL, UNIQUE | 支付编号(PY + yyyyMMddHHmmss) |
|
||||||
|
| billId | UUID | bill_id | NOT NULL | 关联账单 |
|
||||||
|
| amount | DECIMAL(12,2) | amount | NOT NULL | 支付金额 |
|
||||||
|
| method | VARCHAR(20) | method | NOT NULL | 支付方式枚举 |
|
||||||
|
| thirdPartyNo | VARCHAR(100) | third_party_no | -- | 第三方交易号(微信/支付宝) |
|
||||||
|
| status | VARCHAR(20) | status | NOT NULL, 默认PENDING | 支付状态枚举 |
|
||||||
|
| failReason | VARCHAR(500) | fail_reason | -- | 失败原因 |
|
||||||
|
| paymentTime | TIMESTAMP | payment_time | -- | 实际支付时间 |
|
||||||
|
| operatorId | UUID | operator_id | -- | 操作人(线下收款时为收费员) |
|
||||||
|
| receiptNo | VARCHAR(30) | receipt_no | -- | 收据编号 |
|
||||||
|
| remark | VARCHAR(500) | remark | -- | 备注 |
|
||||||
|
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
|
||||||
|
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
|
||||||
|
|
||||||
|
**枚举定义**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum PaymentMethod {
|
||||||
|
WECHAT("微信支付"),
|
||||||
|
ALIPAY("支付宝"),
|
||||||
|
CASH("现金"),
|
||||||
|
BANK_TRANSFER("银行转账"),
|
||||||
|
CARD("刷卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PaymentStatus {
|
||||||
|
PENDING("待支付"),
|
||||||
|
SUCCESS("支付成功"),
|
||||||
|
FAILED("支付失败"),
|
||||||
|
REFUNDED("已退款");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据库索引**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
idx_fp_payment_no -- (payment_no) -- UNIQUE约束自带索引
|
||||||
|
idx_fp_bill_id -- (bill_id)
|
||||||
|
idx_fp_status -- (status, payment_time)
|
||||||
|
idx_fp_method -- (method, payment_time)
|
||||||
|
idx_fp_operator -- (operator_id, payment_time)
|
||||||
|
```
|
||||||
|
|
||||||
|
**外键关系**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
fk_fp_bill -- bill_id REFERENCES fin_fee_bill(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2.2.4 FeeRefund(退款记录)
|
||||||
|
|
||||||
|
**目标模块**: module-finance(待创建)
|
||||||
|
**目标表**: `fin_fee_refund`
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 数据库列名 | 约束 | 说明 |
|
||||||
|
|--------|------|-----------|------|------|
|
||||||
|
| id | UUID | id | PK | 主键 |
|
||||||
|
| refundNo | VARCHAR(30) | refund_no | NOT NULL, UNIQUE | 退款编号(RF + yyyyMMddHHmmss) |
|
||||||
|
| paymentId | UUID | payment_id | NOT NULL | 关联支付记录 |
|
||||||
|
| billId | UUID | bill_id | NOT NULL | 关联账单 |
|
||||||
|
| amount | DECIMAL(12,2) | amount | NOT NULL | 退款金额 |
|
||||||
|
| reason | VARCHAR(500) | reason | NOT NULL | 退款原因 |
|
||||||
|
| status | VARCHAR(20) | status | NOT NULL, 默认PENDING | 退款状态枚举 |
|
||||||
|
| applicantId | UUID | applicant_id | NOT NULL | 申请人ID |
|
||||||
|
| approverId | UUID | approver_id | -- | 审批人ID |
|
||||||
|
| approveTime | TIMESTAMP | approve_time | -- | 审批时间 |
|
||||||
|
| approveRemark | VARCHAR(500) | approve_remark | -- | 审批备注 |
|
||||||
|
| thirdPartyRefundNo | VARCHAR(100) | third_party_refund_no | -- | 第三方退款单号 |
|
||||||
|
| refundTime | TIMESTAMP | refund_time | -- | 实际退款时间 |
|
||||||
|
| refundMethod | VARCHAR(20) | refund_method | -- | 退款方式:ORIGINAL/OFFLINE |
|
||||||
|
| remark | VARCHAR(500) | remark | -- | 备注 |
|
||||||
|
| createdAt | TIMESTAMP | created_at | NOT NULL | 创建时间 |
|
||||||
|
| updatedAt | TIMESTAMP | updated_at | NOT NULL | 更新时间 |
|
||||||
|
|
||||||
|
**枚举定义**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum RefundStatus {
|
||||||
|
PENDING("待审批"),
|
||||||
|
APPROVED("已审批"),
|
||||||
|
REJECTED("已拒绝"),
|
||||||
|
REFUNDED("已退款"),
|
||||||
|
FAILED("退款失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RefundMethod {
|
||||||
|
ORIGINAL("原路退回"),
|
||||||
|
OFFLINE("线下退款");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据库索引**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
idx_fr_refund_no -- (refund_no) -- UNIQUE约束自带索引
|
||||||
|
idx_fr_payment -- (payment_id)
|
||||||
|
idx_fr_bill -- (bill_id)
|
||||||
|
idx_fr_status -- (status)
|
||||||
|
idx_fr_applicant -- (applicant_id, status)
|
||||||
|
```
|
||||||
|
|
||||||
|
**外键关系**:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
fk_fr_payment -- payment_id REFERENCES fin_fee_payment(id)
|
||||||
|
fk_fr_bill -- bill_id REFERENCES fin_fee_bill(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 实体关系图
|
||||||
|
|
||||||
|
```
|
||||||
|
EnergyMeter (module-mdm) FeeItem (module-finance)
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
EnergyConsumption ---费用计算---> FeeBill (自动/手动生成)
|
||||||
|
(抄表数据) | |
|
||||||
|
| +---> FeePayment (支付记录)
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| FeeRefund (退款记录)
|
||||||
|
|
|
||||||
|
+-- sourceType=AUTO, sourceId=EnergyConsumption.id
|
||||||
|
```
|
||||||
|
|
||||||
|
**跨模块引用关系**:
|
||||||
|
|
||||||
|
- FeeBill.sourceId 可指向 EnergyConsumption.id(按用量计费时)
|
||||||
|
- FeeBill.spaceNodeId 引用 mdm_space_node.id
|
||||||
|
- FeeBill.ownerId 引用 auth_user.id(业主)
|
||||||
|
- FeePayment.operatorId 引用 auth_user.id(收费员)
|
||||||
|
- FeeRefund.applicantId / approverId 引用 auth_user.id
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、API设计
|
||||||
|
|
||||||
|
### 3.1 已实现API:能耗管理相关端点
|
||||||
|
|
||||||
|
**基础路径**: `/api/ops/energy`
|
||||||
|
**控制器**: `EnergyController`
|
||||||
|
|
||||||
|
#### 3.1.1 计量点管理
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求参数 | 响应类型 |
|
||||||
|
|------|------|------|---------|---------|
|
||||||
|
| POST | `/meters` | 创建计量点 | EnergyMeter JSON | `ApiResponse<EnergyMeter>` |
|
||||||
|
| GET | `/meters` | 查询计量点列表 | projectId(必填), energyType(可选) | `ApiResponse<List<EnergyMeter>>` |
|
||||||
|
| GET | `/meters/{id}` | 获取计量点详情 | path: id | `ApiResponse<EnergyMeter>` |
|
||||||
|
| PUT | `/meters/{id}` | 更新计量点 | path: id, EnergyMeter JSON | `ApiResponse<EnergyMeter>` |
|
||||||
|
| DELETE | `/meters/{id}` | 删除计量点(软删除) | path: id | `ApiResponse<Void>` |
|
||||||
|
|
||||||
|
#### 3.1.2 能耗记录
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求参数 | 响应类型 |
|
||||||
|
|------|------|------|---------|---------|
|
||||||
|
| POST | `/consumption` | 录入能耗数据 | `{meterId, currentReading, recordedBy}` | `ApiResponse<EnergyConsumption>` |
|
||||||
|
| GET | `/consumption/{meterId}` | 查询能耗记录 | path: meterId, startDate(可选), endDate(可选) | `ApiResponse<List<EnergyConsumption>>` |
|
||||||
|
|
||||||
|
#### 3.1.3 能耗统计
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求参数 | 响应类型 |
|
||||||
|
|------|------|------|---------|---------|
|
||||||
|
| GET | `/statistics/by-type` | 按类型统计消耗 | projectId, month(yyyy-MM-dd) | `ApiResponse<Map<EnergyType, BigDecimal>>` |
|
||||||
|
| GET | `/statistics/unit-consumption` | 单方能耗 | projectId, month(yyyy-MM-dd) | `ApiResponse<BigDecimal>` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 待实现API:收费项目/账单/支付/退款相关端点
|
||||||
|
|
||||||
|
#### 3.2.1 FeeItemController -- 收费项目管理
|
||||||
|
|
||||||
|
**基础路径**: `/api/finance/fee-items`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|
||||||
|
|------|------|------|------------|---------|
|
||||||
|
| POST | `/` | 创建收费项目 | FeeItemForm JSON | `ApiResponse<FeeItem>` |
|
||||||
|
| GET | `/` | 查询收费项目列表 | projectId(必填), type(可选), status(可选), page, size | `ApiResponse<PageResponse<FeeItem>>` |
|
||||||
|
| GET | `/{id}` | 获取收费项目详情 | path: id | `ApiResponse<FeeItem>` |
|
||||||
|
| PUT | `/{id}` | 更新收费项目 | path: id, FeeItemForm JSON | `ApiResponse<FeeItem>` |
|
||||||
|
| PUT | `/{id}/status` | 启用/禁用收费项目 | `{status: "ENABLED"/"DISABLED"}` | `ApiResponse<Void>` |
|
||||||
|
| DELETE | `/{id}` | 删除收费项目 | path: id | `ApiResponse<Void>` |
|
||||||
|
|
||||||
|
**FeeItemForm**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectId": "UUID",
|
||||||
|
"name": "物业费",
|
||||||
|
"type": "PROPERTY",
|
||||||
|
"billingMethod": "AREA",
|
||||||
|
"unitPrice": 3.50,
|
||||||
|
"unit": "平方米/月",
|
||||||
|
"fixedAmount": null,
|
||||||
|
"billDay": 1,
|
||||||
|
"dueDay": 15,
|
||||||
|
"overdueDay": 5,
|
||||||
|
"enableLateFee": true,
|
||||||
|
"lateFeeRate": 0.0005,
|
||||||
|
"maxLateFee": 500.00,
|
||||||
|
"description": "住宅物业费"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.2 FeeBillController -- 账单管理
|
||||||
|
|
||||||
|
**基础路径**: `/api/finance/bills`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|
||||||
|
|------|------|------|------------|---------|
|
||||||
|
| POST | `/` | 手动创建账单 | FeeBillForm JSON | `ApiResponse<FeeBill>` |
|
||||||
|
| POST | `/generate` | 按收费项目自动生成账单 | `{projectId, feeItemId, billPeriod}` | `ApiResponse<List<FeeBill>>` |
|
||||||
|
| POST | `/generate-batch` | 批量生成账单 | `{projectId, billPeriod, feeItemIds[]}` | `ApiResponse<BatchResult>` |
|
||||||
|
| GET | `/` | 查询账单列表 | projectId, ownerId, status, billPeriod, page, size | `ApiResponse<PageResponse<FeeBill>>` |
|
||||||
|
| GET | `/{id}` | 获取账单详情 | path: id | `ApiResponse<FeeBill>` |
|
||||||
|
| PUT | `/{id}` | 更新账单 | path: id, FeeBillForm JSON | `ApiResponse<FeeBill>` |
|
||||||
|
| POST | `/{id}/cancel` | 取消账单 | `{reason}` | `ApiResponse<Void>` |
|
||||||
|
| GET | `/overdue` | 查询逾期账单 | projectId, page, size | `ApiResponse<PageResponse<FeeBill>>` |
|
||||||
|
| GET | `/statistics` | 账单统计 | projectId, billPeriod | `ApiResponse<BillStatistics>` |
|
||||||
|
| GET | `/export` | 导出账单 | projectId, billPeriod, format | `Blob` |
|
||||||
|
|
||||||
|
**FeeBillForm**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"feeItemId": "UUID",
|
||||||
|
"projectId": "UUID",
|
||||||
|
"spaceNodeId": "UUID",
|
||||||
|
"ownerId": "UUID",
|
||||||
|
"billPeriod": "2026-05",
|
||||||
|
"usageAmount": 150.50,
|
||||||
|
"usageUnit": "kWh",
|
||||||
|
"area": 120.00,
|
||||||
|
"amount": 525.00,
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**BillStatistics**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"totalAmount": 150000.00,
|
||||||
|
"paidAmount": 120000.00,
|
||||||
|
"unpaidAmount": 30000.00,
|
||||||
|
"overdueAmount": 15000.00,
|
||||||
|
"lateFeeAmount": 500.00,
|
||||||
|
"totalCount": 200,
|
||||||
|
"paidCount": 160,
|
||||||
|
"unpaidCount": 30,
|
||||||
|
"overdueCount": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.3 FeePaymentController -- 支付管理
|
||||||
|
|
||||||
|
**基础路径**: `/api/finance/payments`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|
||||||
|
|------|------|------|------------|---------|
|
||||||
|
| POST | `/` | 创建支付记录(线下收款) | PaymentForm JSON | `ApiResponse<FeePayment>` |
|
||||||
|
| GET | `/` | 查询支付记录 | billId, ownerId, method, status, startDate, endDate, page, size | `ApiResponse<PageResponse<FeePayment>>` |
|
||||||
|
| GET | `/{id}` | 获取支付详情 | path: id | `ApiResponse<FeePayment>` |
|
||||||
|
| POST | `/{id}/confirm` | 确认支付 | `{status: "SUCCESS"/"FAILED", failReason}` | `ApiResponse<FeePayment>` |
|
||||||
|
| POST | `/online/prepare` | 发起线上支付 | `{billId, method}` | `ApiResponse<PaymentPrepareResult>` |
|
||||||
|
| POST | `/online/callback` | 支付回调 | 第三方回调数据 | `ApiResponse<Void>` |
|
||||||
|
|
||||||
|
**PaymentForm**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"billId": "UUID",
|
||||||
|
"amount": 525.00,
|
||||||
|
"method": "CASH",
|
||||||
|
"receiptNo": "RCP20260518001",
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.4 FeeRefundController -- 退款管理
|
||||||
|
|
||||||
|
**基础路径**: `/api/finance/refunds`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 请求体/参数 | 响应类型 |
|
||||||
|
|------|------|------|------------|---------|
|
||||||
|
| POST | `/` | 申请退款 | RefundForm JSON | `ApiResponse<FeeRefund>` |
|
||||||
|
| GET | `/` | 查询退款记录 | billId, status, startDate, endDate, page, size | `ApiResponse<PageResponse<FeeRefund>>` |
|
||||||
|
| GET | `/{id}` | 获取退款详情 | path: id | `ApiResponse<FeeRefund>` |
|
||||||
|
| POST | `/{id}/approve` | 审批通过 | `{approveRemark}` | `ApiResponse<FeeRefund>` |
|
||||||
|
| POST | `/{id}/reject` | 审批拒绝 | `{approveRemark}` | `ApiResponse<FeeRefund>` |
|
||||||
|
| POST | `/{id}/execute` | 执行退款 | `{thirdPartyRefundNo, refundMethod}` | `ApiResponse<FeeRefund>` |
|
||||||
|
|
||||||
|
**RefundForm**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"paymentId": "UUID",
|
||||||
|
"billId": "UUID",
|
||||||
|
"amount": 525.00,
|
||||||
|
"reason": "重复缴费",
|
||||||
|
"refundMethod": "ORIGINAL"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.5 FeeReminderJob -- 催缴定时任务
|
||||||
|
|
||||||
|
| 任务 | Cron表达式 | 说明 |
|
||||||
|
|------|-----------|------|
|
||||||
|
| 到期提醒 | `0 0 9 * * ?` | 每天上午9点检查3天内到期账单,发送提醒 |
|
||||||
|
| 逾期催缴 | `0 0 10 * * ?` | 每天上午10点检查逾期账单,发送催缴通知 |
|
||||||
|
| 滞纳金计算 | `0 0 2 * * ?` | 每天凌晨2点计算逾期账单滞纳金 |
|
||||||
|
| 周汇总 | `0 0 18 ? * MON` | 每周一18点汇总逾期账单,通知项目经理 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、业务规则
|
||||||
|
|
||||||
|
### 4.1 能耗计费规则(已实现 + 待完善)
|
||||||
|
|
||||||
|
#### 4.1.1 已实现规则
|
||||||
|
|
||||||
|
| 规则编号 | 规则名称 | 规则描述 | 实现位置 |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| E-001 | 仪表状态校验 | 只能对ACTIVE状态的仪表进行抄表 | EnergyConsumptionServiceImpl |
|
||||||
|
| E-002 | 读数递增校验 | 当前读数不能小于上次读数 | EnergyConsumptionServiceImpl |
|
||||||
|
| E-003 | 自动获取上次读数 | 从最近一条记录获取previousReading,首次为0 | EnergyConsumptionServiceImpl |
|
||||||
|
| E-004 | 自动计算消耗量 | consumption = currentReading - previousReading | EnergyConsumptionServiceImpl |
|
||||||
|
| E-005 | 自动计算费用 | amount = consumption * meter.unitPrice(单价为空时为0) | EnergyConsumptionServiceImpl |
|
||||||
|
| E-006 | 按月统计 | 根据month参数计算月份起止日期 | EnergyConsumptionServiceImpl |
|
||||||
|
| E-007 | 单方能耗 | 遍历项目所有ACTIVE计量点,累加月度消耗量 | EnergyConsumptionServiceImpl |
|
||||||
|
|
||||||
|
#### 4.1.2 待修复缺陷
|
||||||
|
|
||||||
|
| 缺陷编号 | 描述 | 严重度 | 修复方案 |
|
||||||
|
|---------|------|--------|---------|
|
||||||
|
| E-BUG-001 | getConsumptionByType()将总消耗全部归入LIGHTING | 高 | 按 meter.energyType 真正分项汇总 |
|
||||||
|
| E-BUG-002 | 前后端能源类型枚举不一致 | 中 | 统一为后端6种枚举,前端对齐 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.2 收费项目规则(待设计)
|
||||||
|
|
||||||
|
| 规则编号 | 规则名称 | 规则描述 | 适用计费方式 |
|
||||||
|
|---------|---------|---------|------------|
|
||||||
|
| FI-001 | 周期性收费 | 按月/季/年定期出账,billDay指定出账日 | FIXED / AREA / USAGE |
|
||||||
|
| FI-002 | 一次性收费 | 临时创建,不参与自动出账 | CUSTOM |
|
||||||
|
| FI-003 | 临时收费 | 针对特定业主的临时性收费 | CUSTOM |
|
||||||
|
| FI-004 | 固定金额计费 | 每期收取固定金额(如停车费300元/月) | FIXED |
|
||||||
|
| FI-005 | 按面积计费 | 面积 * 单价(如物业费3.5元/平方米/月) | AREA |
|
||||||
|
| FI-006 | 按用量计费 | 用量 * 单价(如电费0.85元/kWh),对接能耗数据 | USAGE |
|
||||||
|
| FI-007 | 编码自动生成 | 格式:FI + yyyyMMddHHmmss,冲突时追加后缀 | 全部 |
|
||||||
|
| FI-008 | 禁用不删除 | 收费项目禁用后不再参与自动出账,已有账单不受影响 | 全部 |
|
||||||
|
| FI-009 | 项目隔离 | 收费项目属于特定项目,跨项目不可见 | 全部 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.3 账单生成规则(待设计)
|
||||||
|
|
||||||
|
| 规则编号 | 规则名称 | 规则描述 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| BL-001 | 自动生成 | 根据收费项目的billDay,定时任务自动为项目下所有业主生成账单 |
|
||||||
|
| BL-002 | 手动生成 | 管理员手动为指定业主创建账单 |
|
||||||
|
| BL-003 | 批量生成 | 一次性为项目所有业主生成某类收费项目的账单 |
|
||||||
|
| BL-004 | 账单编号生成 | 格式:BL + yyyyMMdd + 4位序号,序号按天递增 |
|
||||||
|
| BL-005 | 到期日计算 | dueDate = billDate + feeItem.dueDay天 |
|
||||||
|
| BL-006 | 逾期日计算 | overdueDate = dueDate + feeItem.overdueDay天 |
|
||||||
|
| BL-007 | 按用量计费账单 | 从EnergyConsumption获取当期用量,计算金额 |
|
||||||
|
| BL-008 | 按面积计费账单 | 从SpaceNode获取面积,计算金额 = 面积 * 单价 |
|
||||||
|
| BL-009 | 固定金额账单 | 金额 = feeItem.fixedAmount |
|
||||||
|
| BL-010 | 防重复生成 | 同一收费项目 + 同一业主 + 同一账期不可重复生成 |
|
||||||
|
| BL-011 | 催缴提醒 | 到期前3天发送提醒,逾期后发送催缴通知 |
|
||||||
|
| BL-012 | 账单取消 | 已支付/部分支付的账单不可取消,需先退款 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.4 支付流程(待设计)
|
||||||
|
|
||||||
|
| 规则编号 | 规则名称 | 规则描述 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| PY-001 | 线下收款 | 管理员登记现金/转账/刷卡收款,直接确认成功 |
|
||||||
|
| PY-002 | 线上支付 | 调用微信/支付宝SDK生成预支付单,等待回调确认 |
|
||||||
|
| PY-003 | 支付确认 | 线上支付由回调确认,线下支付由操作员确认 |
|
||||||
|
| PY-004 | 部分支付 | 支持部分支付,账单状态变为PARTIAL_PAID |
|
||||||
|
| PY-005 | 超额支付 | 支付金额不可超过应付金额(payableAmount - paidAmount) |
|
||||||
|
| PY-006 | 支付编号生成 | 格式:PY + yyyyMMddHHmmss |
|
||||||
|
| PY-007 | 账单状态联动 | 支付成功后更新账单paidAmount和status |
|
||||||
|
| PY-008 | 收据编号 | 线下收款时生成收据编号 |
|
||||||
|
|
||||||
|
**支付状态流转**:
|
||||||
|
|
||||||
|
```
|
||||||
|
PENDING --(支付成功)--> SUCCESS
|
||||||
|
PENDING --(支付失败)--> FAILED
|
||||||
|
SUCCESS --(发起退款)--> REFUNDED
|
||||||
|
```
|
||||||
|
|
||||||
|
**账单状态联动**:
|
||||||
|
|
||||||
|
```
|
||||||
|
UNPAID --(部分支付)--> PARTIAL_PAID
|
||||||
|
UNPAID/PARTIAL_PAID --(全额支付)--> PAID
|
||||||
|
UNPAID --(超过逾期日)--> OVERDUE
|
||||||
|
任意状态 --(取消)--> CANCELLED(需先退款)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.5 退款流程(待设计)
|
||||||
|
|
||||||
|
| 规则编号 | 规则名称 | 规则描述 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| RF-001 | 退款申请 | 业主或管理员发起退款申请,需指定退款原因 |
|
||||||
|
| RF-002 | 退款金额限制 | 退款金额不可超过原支付金额 |
|
||||||
|
| RF-003 | 退款审批 | 退款金额 > 1000元需审批,否则自动通过 |
|
||||||
|
| RF-004 | 审批通过 | 审批人确认后,退款状态变为APPROVED |
|
||||||
|
| RF-005 | 审批拒绝 | 审批人可拒绝退款,需填写拒绝原因 |
|
||||||
|
| RF-006 | 原路退回 | 线上支付的退款原路退回至支付账户 |
|
||||||
|
| RF-007 | 线下退款 | 现金/转账支付的退款通过线下处理 |
|
||||||
|
| RF-008 | 退款执行 | 记录第三方退款单号和实际退款时间 |
|
||||||
|
| RF-009 | 账单金额回退 | 退款成功后,账单paidAmount减少,状态可能回退 |
|
||||||
|
| RF-010 | 退款编号生成 | 格式:RF + yyyyMMddHHmmss |
|
||||||
|
|
||||||
|
**退款状态流转**:
|
||||||
|
|
||||||
|
```
|
||||||
|
PENDING --(审批通过)--> APPROVED
|
||||||
|
PENDING --(审批拒绝)--> REJECTED
|
||||||
|
APPROVED --(退款成功)--> REFUNDED
|
||||||
|
APPROVED --(退款失败)--> FAILED
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.6 滞纳金计算(待设计)
|
||||||
|
|
||||||
|
| 规则编号 | 规则名称 | 规则描述 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| LF-001 | 计算触发 | 每天凌晨2点定时任务检查逾期账单 |
|
||||||
|
| LF-002 | 计算公式 | lateFee = 逾期天数 * payableAmount * lateFeeRate |
|
||||||
|
| LF-003 | 逾期天数 | 从overdueDate开始计算,到当前日期 |
|
||||||
|
| LF-004 | 上限控制 | lateFee不超过feeItem.maxLateFee |
|
||||||
|
| LF-005 | 累加计算 | 每天累加,不覆盖之前的滞纳金 |
|
||||||
|
| LF-006 | 前提条件 | 仅当feeItem.enableLateFee = true时计算 |
|
||||||
|
| LF-007 | 已支付账单 | 已全额支付的账单不再计算滞纳金 |
|
||||||
|
| LF-008 | 已取消账单 | 已取消的账单不再计算滞纳金 |
|
||||||
|
| LF-009 | 部分支付 | 滞纳金基于剩余应付金额计算 |
|
||||||
|
|
||||||
|
**滞纳金计算示例**:
|
||||||
|
|
||||||
|
```
|
||||||
|
假设:
|
||||||
|
payableAmount = 1000元
|
||||||
|
lateFeeRate = 0.0005(万分之五/天)
|
||||||
|
maxLateFee = 500元
|
||||||
|
overdueDate = 2026-05-10
|
||||||
|
当前日期 = 2026-05-18
|
||||||
|
|
||||||
|
计算:
|
||||||
|
逾期天数 = 8天
|
||||||
|
每日滞纳金 = 1000 * 0.0005 = 0.5元
|
||||||
|
累计滞纳金 = 8 * 0.5 = 4元(未超过上限500元)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、执行约束
|
||||||
|
|
||||||
|
| 约束编号 | 约束名称 | 约束描述 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| CON-001 | 金额精度 | 所有金额字段使用DECIMAL(12,2),单价使用DECIMAL(12,4),利率使用DECIMAL(8,6) |
|
||||||
|
| CON-002 | 事务一致性 | 账单生成、支付确认、退款执行必须在同一事务中完成账单状态更新 |
|
||||||
|
| CON-003 | 并发控制 | 支付操作使用乐观锁(version字段或状态校验),防止重复支付 |
|
||||||
|
| CON-004 | 审计日志 | 所有收费/支付/退款操作必须记录审计日志 |
|
||||||
|
| CON-005 | 项目隔离 | 所有查询必须按projectId过滤,确保项目间数据隔离 |
|
||||||
|
| CON-006 | 软删除 | 收费项目使用启用/禁用替代物理删除,账单/支付/退款不允许删除 |
|
||||||
|
| CON-007 | 编码唯一性 | billNo/paymentNo/refundNo全局唯一,生成时需处理冲突 |
|
||||||
|
| CON-008 | 定时任务幂等 | 催缴/滞纳金计算任务必须幂等,重复执行不产生副作用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、权限控制
|
||||||
|
|
||||||
|
| 权限编码 | 权限名称 | 适用角色 | 说明 |
|
||||||
|
|---------|---------|---------|------|
|
||||||
|
| finance:fee-item:list | 查看收费项目 | 项目管理员/财务人员 | 查看项目下收费项目列表 |
|
||||||
|
| finance:fee-item:create | 创建收费项目 | 系统管理员 | 创建新的收费项目 |
|
||||||
|
| finance:fee-item:update | 修改收费项目 | 系统管理员 | 修改收费项目配置 |
|
||||||
|
| finance:fee-item:delete | 删除收费项目 | 系统管理员 | 删除/禁用收费项目 |
|
||||||
|
| finance:bill:list | 查看账单 | 项目管理员/财务人员 | 查看项目下账单列表 |
|
||||||
|
| finance:bill:create | 创建账单 | 财务人员 | 手动创建账单 |
|
||||||
|
| finance:bill:generate | 生成账单 | 财务人员 | 自动/批量生成账单 |
|
||||||
|
| finance:bill:cancel | 取消账单 | 财务主管 | 取消账单(需审批) |
|
||||||
|
| finance:bill:export | 导出账单 | 财务人员 | 导出账单Excel/PDF |
|
||||||
|
| finance:payment:create | 登记收款 | 财务人员 | 线下收款登记 |
|
||||||
|
| finance:payment:confirm | 确认支付 | 财务人员 | 确认支付状态 |
|
||||||
|
| finance:payment:list | 查看支付记录 | 财务人员 | 查看支付记录列表 |
|
||||||
|
| finance:refund:apply | 申请退款 | 财务人员/业主 | 发起退款申请 |
|
||||||
|
| finance:refund:approve | 审批退款 | 财务主管 | 审批退款申请 |
|
||||||
|
| finance:refund:execute | 执行退款 | 财务人员 | 执行退款操作 |
|
||||||
|
| finance:refund:list | 查看退款记录 | 财务人员 | 查看退款记录列表 |
|
||||||
|
| finance:statistics:view | 查看财务统计 | 项目管理员/财务主管 | 查看财务报表 |
|
||||||
|
| finance:late-fee:config | 配置滞纳金 | 系统管理员 | 配置滞纳金规则 |
|
||||||
|
|
||||||
|
**数据级权限**:
|
||||||
|
|
||||||
|
- 财务人员(PROJECT数据范围):只能查看/操作所属项目的财务数据
|
||||||
|
- 财务主管(ALL数据范围):可查看所有项目的财务数据
|
||||||
|
- 业主(SELF数据范围):只能查看自己的账单和支付记录
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、例外情况处理
|
||||||
|
|
||||||
|
| 例外编号 | 例外场景 | 处理策略 | 错误码 |
|
||||||
|
|---------|---------|---------|--------|
|
||||||
|
| EX-001 | 重复生成账单 | 检查feeItemId + ownerId + billPeriod唯一性,已存在则跳过 | 7001 |
|
||||||
|
| EX-002 | 支付金额超过应付金额 | 校验支付金额 <= payableAmount - paidAmount | 7002 |
|
||||||
|
| EX-003 | 已支付账单取消 | 拒绝取消,提示需先退款 | 7003 |
|
||||||
|
| EX-004 | 退款金额超过支付金额 | 校验退款金额 <= 原支付金额 | 7004 |
|
||||||
|
| EX-005 | 重复支付 | 状态校验,已支付/已退款的支付记录不可再次确认 | 7005 |
|
||||||
|
| EX-006 | 能耗数据缺失 | 按用量计费时无对应能耗记录,生成0元账单并标记异常 | 7006 |
|
||||||
|
| EX-007 | 业主无关联房产 | 生成账单时业主无房产关联,跳过并记录日志 | 7007 |
|
||||||
|
| EX-008 | 线上支付超时 | 支付状态保持PENDING,由对账任务处理 | 7008 |
|
||||||
|
| EX-009 | 线上支付回调异常 | 记录原始回调数据,人工介入处理 | 7009 |
|
||||||
|
| EX-010 | 滞纳金计算溢出 | lateFee不超过maxLateFee上限 | 7010 |
|
||||||
|
| EX-011 | 收费项目被禁用后自动出账 | 跳过已禁用的收费项目 | 7011 |
|
||||||
|
| EX-012 | 批量生成部分失败 | 记录成功/失败数量和失败原因,返回BatchResult | 7012 |
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,370 +0,0 @@
|
||||||
# 设施设备领域技术方案
|
|
||||||
|
|
||||||
**领域编号**: 4.3
|
|
||||||
**微服务**: ether-asset
|
|
||||||
**最后更新**: 2026-02-14
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、领域概述
|
|
||||||
|
|
||||||
### 1.1 领域职责
|
|
||||||
|
|
||||||
设施设备领域负责管理物业资产全生命周期:
|
|
||||||
|
|
||||||
- 设备台账管理(电梯、空调、消防、给排水、供配电等)
|
|
||||||
- 设备维保计划与记录
|
|
||||||
- 设备故障管理
|
|
||||||
- 设备与工单联动
|
|
||||||
|
|
||||||
### 1.2 核心概念
|
|
||||||
|
|
||||||
| 概念 | 说明 | 对应实体 |
|
|
||||||
| ------------ | ---------------------------- | ----------------- |
|
|
||||||
| **设备台账** | 设备基础信息和运行状态 | Equipment |
|
|
||||||
| **维保计划** | 定期保养计划 | MaintenancePlan |
|
|
||||||
| **维保记录** | 保养执行记录 | MaintenanceRecord |
|
|
||||||
| **设备类型** | 设备分类(电梯/空调/消防等) | EquipmentType |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、领域模型
|
|
||||||
|
|
||||||
### 2.1 聚合根设计
|
|
||||||
|
|
||||||
#### Equipment(设备台账)
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Entity
|
|
||||||
@Table(name = "asset_equipment")
|
|
||||||
@Data
|
|
||||||
public class Equipment {
|
|
||||||
@Id
|
|
||||||
private UUID id;
|
|
||||||
private UUID projectId;
|
|
||||||
|
|
||||||
private String code; // 设备编码
|
|
||||||
private String name; // 设备名称
|
|
||||||
|
|
||||||
// 分类
|
|
||||||
private EquipmentType type; // ELEVATOR/AC/FIRE/PLUMBING/POWER
|
|
||||||
private EquipmentStatus status; // NORMAL/FAULT/MAINTAINING/SCRAPPED
|
|
||||||
|
|
||||||
// 品牌型号
|
|
||||||
private String brand;
|
|
||||||
private String model;
|
|
||||||
private String specifications;
|
|
||||||
private String serialNumber;
|
|
||||||
|
|
||||||
// 厂商信息
|
|
||||||
private String manufacturer;
|
|
||||||
private String supplier;
|
|
||||||
private String supplierPhone;
|
|
||||||
|
|
||||||
// 位置
|
|
||||||
private UUID spaceNodeId; // 关联空间节点
|
|
||||||
private String locationDesc; // 位置描述
|
|
||||||
|
|
||||||
// 时间
|
|
||||||
private LocalDate purchaseDate;
|
|
||||||
private LocalDate installDate;
|
|
||||||
private LocalDate warrantyDate;
|
|
||||||
private Integer lifespanYears; // 设计寿命(年)
|
|
||||||
|
|
||||||
// 维保
|
|
||||||
private Integer maintenanceCycle; // 保养周期(天)
|
|
||||||
private LocalDate lastMaintenanceDate;
|
|
||||||
private LocalDate nextMaintenanceDate;
|
|
||||||
|
|
||||||
// 负责人
|
|
||||||
private UUID managerId;
|
|
||||||
private String managerName;
|
|
||||||
private String contactPhone;
|
|
||||||
|
|
||||||
// 二维码
|
|
||||||
private String qrCode;
|
|
||||||
|
|
||||||
// 扩展属性(JSONB)
|
|
||||||
private String attributes;
|
|
||||||
|
|
||||||
// 审计字段
|
|
||||||
private LocalDateTime createdAt;
|
|
||||||
private LocalDateTime updatedAt;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**设备类型枚举**:
|
|
||||||
|
|
||||||
```java
|
|
||||||
public enum EquipmentType {
|
|
||||||
ELEVATOR("电梯"),
|
|
||||||
AIR_CONDITIONER("空调"),
|
|
||||||
FIRE_FIGHTING("消防设备"),
|
|
||||||
PLUMBING("给排水"),
|
|
||||||
POWER_SUPPLY("供配电"),
|
|
||||||
SECURITY("安防设备"),
|
|
||||||
PARKING("停车设备"),
|
|
||||||
OTHER("其他");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### MaintenancePlan(维保计划)
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Entity
|
|
||||||
@Table(name = "asset_maintenance_plan")
|
|
||||||
@Data
|
|
||||||
public class MaintenancePlan {
|
|
||||||
@Id
|
|
||||||
private UUID id;
|
|
||||||
private UUID projectId;
|
|
||||||
private UUID equipmentId; // 关联设备
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
private MaintenanceType type; // DAILY/WEEKLY/MONTHLY/QUARTERLY/YEARLY
|
|
||||||
|
|
||||||
// 周期
|
|
||||||
private Integer cycleDays; // 周期天数
|
|
||||||
private String cronExpression; // Cron表达式
|
|
||||||
|
|
||||||
// 内容
|
|
||||||
private String content; // 保养内容描述
|
|
||||||
private String checkItems; // 检查项清单(JSON)
|
|
||||||
|
|
||||||
// 负责人
|
|
||||||
private UUID maintainerId;
|
|
||||||
private String maintainerName;
|
|
||||||
|
|
||||||
// 提醒
|
|
||||||
private Integer remindDays; // 提前提醒天数
|
|
||||||
|
|
||||||
// 状态
|
|
||||||
private Boolean enabled;
|
|
||||||
|
|
||||||
// 审计字段
|
|
||||||
private LocalDateTime createdAt;
|
|
||||||
private LocalDateTime updatedAt;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### MaintenanceRecord(维保记录)
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Entity
|
|
||||||
@Table(name = "asset_maintenance_record")
|
|
||||||
@Data
|
|
||||||
public class MaintenanceRecord {
|
|
||||||
@Id
|
|
||||||
private UUID id;
|
|
||||||
private UUID equipmentId;
|
|
||||||
private UUID planId; // 关联计划
|
|
||||||
|
|
||||||
// 维保信息
|
|
||||||
private LocalDate maintenanceDate;
|
|
||||||
private MaintenanceType type;
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
// 执行人
|
|
||||||
private UUID maintainerId;
|
|
||||||
private String maintainerName;
|
|
||||||
|
|
||||||
// 结果
|
|
||||||
private MaintenanceResult result; // NORMAL/ABNORMAL
|
|
||||||
private String remark;
|
|
||||||
private String images;
|
|
||||||
|
|
||||||
// 检查项结果
|
|
||||||
private String checkResults; // [{"item":"检查A","result":"PASS"}]
|
|
||||||
|
|
||||||
// 关联工单
|
|
||||||
private UUID workOrderId; // 异常时关联的维修工单
|
|
||||||
|
|
||||||
// 下次维保
|
|
||||||
private LocalDate nextMaintenanceDate;
|
|
||||||
|
|
||||||
// 审计字段
|
|
||||||
private LocalDateTime createdAt;
|
|
||||||
private LocalDateTime updatedAt;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、设备与工单联动
|
|
||||||
|
|
||||||
### 3.1 故障自动报修
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class EquipmentFaultHandler {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private WorkOrderService workOrderService;
|
|
||||||
|
|
||||||
@EventListener
|
|
||||||
public void onEquipmentFault(EquipmentFaultEvent event) {
|
|
||||||
Equipment equipment = event.getEquipment();
|
|
||||||
|
|
||||||
// 创建设备维修工单
|
|
||||||
WorkOrder workOrder = new WorkOrder();
|
|
||||||
workOrder.setOrderType(WorkOrderType.REPAIR);
|
|
||||||
workOrder.setTitle("设备故障: " + equipment.getName());
|
|
||||||
workOrder.setDescription(event.getFaultDescription());
|
|
||||||
workOrder.setEquipmentId(equipment.getId());
|
|
||||||
workOrder.setSpaceNodeId(equipment.getSpaceNodeId());
|
|
||||||
workOrder.setPriority(WorkOrderPriority.HIGH);
|
|
||||||
workOrder.setSource(WorkOrderSource.IOT);
|
|
||||||
|
|
||||||
workOrderService.create(workOrder);
|
|
||||||
|
|
||||||
// 更新设备状态
|
|
||||||
equipment.setStatus(EquipmentStatus.FAULT);
|
|
||||||
equipmentRepository.save(equipment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 维保到期提醒
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class MaintenanceReminderJob {
|
|
||||||
|
|
||||||
@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行
|
|
||||||
public void remindUpcomingMaintenance() {
|
|
||||||
// 查询7天内到期的维保计划
|
|
||||||
LocalDate targetDate = LocalDate.now().plusDays(7);
|
|
||||||
|
|
||||||
List<Equipment> equipments = equipmentRepository
|
|
||||||
.findByNextMaintenanceDateBeforeAndStatus(targetDate, EquipmentStatus.NORMAL);
|
|
||||||
|
|
||||||
for (Equipment equipment : equipments) {
|
|
||||||
notificationService.sendMaintenanceReminder(equipment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、实现状态与差异
|
|
||||||
|
|
||||||
### 4.1 实现状态
|
|
||||||
|
|
||||||
| 功能模块 | 实现状态 | 备注 |
|
|
||||||
| ----------------- | ----------- | ---------------------- |
|
|
||||||
| Equipment | 🟢 已实现 | 基础CRUD、二维码生成 |
|
|
||||||
| MaintenancePlan | 🟢 已实现 | 已迁移至 ether-asset |
|
|
||||||
| MaintenanceRecord | 🟢 已实现 | 已迁移至 ether-asset |
|
|
||||||
| 设备二维码 | 🟢 已实现 | 支持扫码查看设备信息 |
|
|
||||||
| 故障自动工单 | 🟡 部分实现 | 设备状态变更可关联工单 |
|
|
||||||
| IoT集成 | 🔴 未实现 | 待开发 |
|
|
||||||
|
|
||||||
### 4.2 与设计方案的差异
|
|
||||||
|
|
||||||
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|
|
||||||
| ------------ | ------------ | ----------- | --------------------------------------- |
|
|
||||||
| **维保归属** | ether-asset | ether-asset | ✅ 已修正:维保实体已统一在 ether-asset |
|
|
||||||
| **设备管理** | ether-asset | ether-asset | ✅ 已修正:设备实体已统一在 ether-asset |
|
|
||||||
| **设备联动** | 故障自动工单 | 部分实现 | 🟡 功能完善中 |
|
|
||||||
|
|
||||||
### 4.3 改进计划
|
|
||||||
|
|
||||||
| 优先级 | 改进项 | 说明 |
|
|
||||||
| ------ | ------------------ | ---------------------------------------------------- |
|
|
||||||
| ~~P1~~ | ~~迁移维保实体~~ | ~~将MaintenancePlan/Record迁移到ether-asset~~ 已完成 |
|
|
||||||
| P2 | 完善故障自动工单 | 设备状态变更时自动创建维修工单 |
|
|
||||||
| P2 | 完善设备二维码功能 | 支持扫码报修 |
|
|
||||||
| P3 | IoT传感器接入 | 对接传感器数据 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、数据库表结构
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 设备台账表
|
|
||||||
CREATE TABLE asset_equipment (
|
|
||||||
id UUID PRIMARY KEY,
|
|
||||||
project_id UUID NOT NULL,
|
|
||||||
code VARCHAR(50) NOT NULL,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
type VARCHAR(20) NOT NULL,
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'NORMAL',
|
|
||||||
brand VARCHAR(100),
|
|
||||||
model VARCHAR(100),
|
|
||||||
specifications VARCHAR(500),
|
|
||||||
serial_number VARCHAR(100),
|
|
||||||
manufacturer VARCHAR(100),
|
|
||||||
supplier VARCHAR(100),
|
|
||||||
supplier_phone VARCHAR(20),
|
|
||||||
space_node_id UUID,
|
|
||||||
location_desc VARCHAR(255),
|
|
||||||
purchase_date DATE,
|
|
||||||
install_date DATE,
|
|
||||||
warranty_date DATE,
|
|
||||||
lifespan_years INTEGER,
|
|
||||||
maintenance_cycle INTEGER,
|
|
||||||
last_maintenance_date DATE,
|
|
||||||
next_maintenance_date DATE,
|
|
||||||
manager_id UUID,
|
|
||||||
manager_name VARCHAR(100),
|
|
||||||
contact_phone VARCHAR(20),
|
|
||||||
qr_code VARCHAR(255),
|
|
||||||
attributes JSONB,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
||||||
UNIQUE(project_id, code)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 维保计划表
|
|
||||||
CREATE TABLE asset_maintenance_plan (
|
|
||||||
id UUID PRIMARY KEY,
|
|
||||||
project_id UUID NOT NULL,
|
|
||||||
equipment_id UUID NOT NULL,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
type VARCHAR(20) NOT NULL,
|
|
||||||
cycle_days INTEGER,
|
|
||||||
cron_expression VARCHAR(50),
|
|
||||||
content TEXT,
|
|
||||||
check_items JSONB,
|
|
||||||
maintainer_id UUID,
|
|
||||||
maintainer_name VARCHAR(100),
|
|
||||||
remind_days INTEGER DEFAULT 3,
|
|
||||||
enabled BOOLEAN DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 维保记录表
|
|
||||||
CREATE TABLE asset_maintenance_record (
|
|
||||||
id UUID PRIMARY KEY,
|
|
||||||
equipment_id UUID NOT NULL,
|
|
||||||
plan_id UUID,
|
|
||||||
maintenance_date DATE NOT NULL,
|
|
||||||
type VARCHAR(20) NOT NULL,
|
|
||||||
content TEXT,
|
|
||||||
maintainer_id UUID,
|
|
||||||
maintainer_name VARCHAR(100),
|
|
||||||
result VARCHAR(20),
|
|
||||||
remark VARCHAR(500),
|
|
||||||
images TEXT,
|
|
||||||
check_results JSONB,
|
|
||||||
work_order_id UUID,
|
|
||||||
next_maintenance_date DATE,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 创建索引
|
|
||||||
CREATE INDEX idx_equipment_project ON asset_equipment(project_id);
|
|
||||||
CREATE INDEX idx_equipment_type ON asset_equipment(type);
|
|
||||||
CREATE INDEX idx_equipment_status ON asset_equipment(status);
|
|
||||||
CREATE INDEX idx_equipment_space ON asset_equipment(space_node_id);
|
|
||||||
CREATE INDEX idx_equipment_next_maintenance ON asset_equipment(next_maintenance_date);
|
|
||||||
CREATE INDEX idx_maintenance_plan_equipment ON asset_maintenance_plan(equipment_id);
|
|
||||||
CREATE INDEX idx_maintenance_record_equipment ON asset_maintenance_record(equipment_id);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**文档维护**: 本领域技术方案由 ether-asset 服务负责人维护
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,486 @@
|
||||||
|
# Ether 物业管理系统 - 测试计划
|
||||||
|
|
||||||
|
**文档版本**: v1.0
|
||||||
|
**创建日期**: 2026-05-18
|
||||||
|
**适用范围**: Ether PMS 全系统测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、测试策略
|
||||||
|
|
||||||
|
### 1.1 测试目标
|
||||||
|
|
||||||
|
- 确保所有已实现功能的API接口正确性,API测试通过率 >= 95%
|
||||||
|
- 确保核心业务流程端到端可用,E2E测试通过率 >= 90%
|
||||||
|
- 建立TDD开发规范,新功能必须先写测试再实现
|
||||||
|
- 为6大业务域建立完整的测试用例体系
|
||||||
|
- 覆盖5条跨域业务流程的端到端验证
|
||||||
|
|
||||||
|
### 1.2 测试范围
|
||||||
|
|
||||||
|
**6大业务域**:
|
||||||
|
|
||||||
|
| 业务域 | 模块 | 核心功能 | 已实现需求数 |
|
||||||
|
|--------|------|---------|-------------|
|
||||||
|
| 身份与权限域 | module-auth | 用户/角色/权限/部门/认证/审计 | 37 |
|
||||||
|
| 空间与项目域 | module-mdm | 项目/空间节点/巡检标准 | 23 |
|
||||||
|
| 设备与资产域 | module-asset | 设备台账/扩展表/健康评分/故障/归属 | 28 |
|
||||||
|
| 运营与服务域 | module-wo + module-mdm | 工单/维保/巡检/备件/能耗 | 36 |
|
||||||
|
| 财务与收费域 | module-finance(待建) | 收费项目/账单/支付/退款 | 0 |
|
||||||
|
| 前端交互域 | ether-admin | 登录/权限/项目切换/各模块页面 | 14 |
|
||||||
|
|
||||||
|
**5条跨域业务流程**:
|
||||||
|
|
||||||
|
1. 业主报修全流程(认证 -> 空间定位 -> 工单创建 -> 派单 -> 执行 -> 验收)
|
||||||
|
2. 设备全生命周期管理(采购 -> 安装 -> 运行监控 -> 维保 -> 报废)
|
||||||
|
3. 预防性维保调度流程(计划 -> 周期调度 -> 任务生成 -> 执行 -> 验收 -> 设备联动)
|
||||||
|
4. 巡检异常处理流程(模板 -> 签到 -> 异常上报 -> 工单 -> 处理 -> 复检)
|
||||||
|
5. 能耗计费流程(计量点 -> 抄表 -> 费用计算 -> 账单 -> 缴费)
|
||||||
|
|
||||||
|
### 1.3 测试层级
|
||||||
|
|
||||||
|
| 测试层级 | 工具 | 目标 | 执行频率 |
|
||||||
|
|---------|------|------|---------|
|
||||||
|
| 单元测试 | JUnit 5 + Mockito | Service层业务逻辑、算法验证 | 每次提交 |
|
||||||
|
| API接口测试 | Shell curl脚本 | REST API正确性、状态码、响应格式 | 每日构建 |
|
||||||
|
| E2E端到端测试 | Playwright + TypeScript | 用户操作流程、跨域业务闭环 | 每日构建 |
|
||||||
|
|
||||||
|
### 1.4 TDD开发规范
|
||||||
|
|
||||||
|
**强制要求**:所有新功能开发必须遵循TDD流程。
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 红(Red) - 先编写失败的测试用例
|
||||||
|
2. 绿(Green) - 编写最小代码使测试通过
|
||||||
|
3. 重构(Refactor) - 优化代码结构,保持测试通过
|
||||||
|
```
|
||||||
|
|
||||||
|
**TDD准入规则**:
|
||||||
|
- 新增功能必须先编写测试用例,无测试用例的PR不予合并
|
||||||
|
- Bug修复必须先编写复现测试,验证修复后测试通过
|
||||||
|
- 重构必须确保既有测试全部通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、测试环境
|
||||||
|
|
||||||
|
### 2.1 后端测试环境
|
||||||
|
|
||||||
|
| 项目 | 配置 |
|
||||||
|
|------|------|
|
||||||
|
| 框架 | Spring Boot 3.x Test |
|
||||||
|
| 单元测试 | JUnit 5 + Mockito |
|
||||||
|
| 集成测试 | Spring Boot Test + @WebMvcTest / @SpringBootTest |
|
||||||
|
| 数据库 | H2内存数据库(单元测试) / PostgreSQL Testcontainers(集成测试) |
|
||||||
|
| 后端地址 | http://localhost:8080 |
|
||||||
|
| 认证方式 | JWT Token (Authorization: Bearer {token}) |
|
||||||
|
|
||||||
|
### 2.2 前端测试环境
|
||||||
|
|
||||||
|
| 项目 | 配置 |
|
||||||
|
|------|------|
|
||||||
|
| 单元测试 | Vitest |
|
||||||
|
| E2E测试 | Playwright |
|
||||||
|
| 管理后台地址 | http://localhost:5175 |
|
||||||
|
| 浏览器 | Chromium (CI) / Chrome (本地) |
|
||||||
|
|
||||||
|
### 2.3 测试数据管理
|
||||||
|
|
||||||
|
**测试数据准备**:
|
||||||
|
- 每个测试脚本开头通过API创建所需测试数据
|
||||||
|
- 使用固定的测试账号:admin / admin123
|
||||||
|
- 测试项目使用固定编码前缀:TEST-
|
||||||
|
- 测试数据命名规范:[TEST]-[模块]-[功能]-[序号]
|
||||||
|
|
||||||
|
**测试数据清理**:
|
||||||
|
- 每个测试脚本结尾删除创建的测试数据
|
||||||
|
- 使用唯一标识避免数据冲突
|
||||||
|
- API测试使用独立项目隔离数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、API接口测试
|
||||||
|
|
||||||
|
### 3.1 测试框架选型
|
||||||
|
|
||||||
|
| 方案 | 选定 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Shell curl脚本 | 是 | 轻量级,无额外依赖,适合CI环境 |
|
||||||
|
| Postman Collection | 否 | 需要GUI,不适合自动化CI |
|
||||||
|
| Newman | 备选 | 可从Postman Collection导出运行 |
|
||||||
|
|
||||||
|
### 3.2 测试脚本组织
|
||||||
|
|
||||||
|
```
|
||||||
|
04-TESTING/api/
|
||||||
|
auth/ - 认证授权域API测试
|
||||||
|
test-auth.sh
|
||||||
|
space/ - 空间与项目域API测试
|
||||||
|
test-space.sh
|
||||||
|
asset/ - 设备与资产域API测试
|
||||||
|
test-asset.sh
|
||||||
|
ops/ - 运营与服务域API测试
|
||||||
|
test-ops.sh
|
||||||
|
finance/ - 财务与收费域API测试
|
||||||
|
test-finance.sh
|
||||||
|
run-all.sh - 执行全部API测试
|
||||||
|
run-module.sh - 执行指定模块API测试
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 测试脚本规范
|
||||||
|
|
||||||
|
每个API测试脚本必须包含:
|
||||||
|
|
||||||
|
1. **前置条件** - 登录获取Token、创建依赖数据
|
||||||
|
2. **请求** - curl发送HTTP请求,携带正确的Header
|
||||||
|
3. **断言** - 检查HTTP状态码、响应JSON字段
|
||||||
|
4. **清理** - 删除测试创建的数据
|
||||||
|
|
||||||
|
**脚本结构**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
BASE_URL="http://localhost:8080/api"
|
||||||
|
TOKEN=""
|
||||||
|
|
||||||
|
login() { ... }
|
||||||
|
|
||||||
|
test_XXX() {
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" ...)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "test_XXX"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() { ... }
|
||||||
|
|
||||||
|
login
|
||||||
|
test_XXX
|
||||||
|
cleanup
|
||||||
|
print_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 测试脚本目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
04-TESTING/api/
|
||||||
|
auth/test-auth.sh - 认证授权域API测试
|
||||||
|
space/test-space.sh - 空间与项目域API测试
|
||||||
|
asset/test-asset.sh - 设备与资产域API测试
|
||||||
|
ops/test-ops.sh - 运营与服务域API测试
|
||||||
|
finance/test-finance.sh - 财务与收费域API测试
|
||||||
|
run-all.sh - 执行全部API测试
|
||||||
|
run-module.sh - 执行指定模块API测试
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、E2E端到端测试
|
||||||
|
|
||||||
|
### 4.1 测试框架选型
|
||||||
|
|
||||||
|
| 项目 | 配置 |
|
||||||
|
|------|------|
|
||||||
|
| 框架 | Playwright |
|
||||||
|
| 语言 | TypeScript |
|
||||||
|
| 运行器 | Playwright Test |
|
||||||
|
| 浏览器 | Chromium |
|
||||||
|
| 设计模式 | Page Object Model |
|
||||||
|
|
||||||
|
### 4.2 测试场景设计
|
||||||
|
|
||||||
|
**5条跨域业务流程**:
|
||||||
|
|
||||||
|
| 编号 | 流程名称 | 涉及域 | 关键步骤 |
|
||||||
|
|------|---------|--------|---------|
|
||||||
|
| E2E-CROSS-001 | 业主报修全流程 | auth+space+ops | 登录->选项目->定位设备->创建工单->派单->执行->完成->验收 |
|
||||||
|
| E2E-CROSS-002 | 设备全生命周期 | asset+ops | 创建设备->关联空间->录入扩展表->健康评分->创建维保->完成->报废 |
|
||||||
|
| E2E-CROSS-003 | 预防性维保调度 | ops+asset | 创建维保计划->创建维保任务->分配->执行->完成->验收->设备联动 |
|
||||||
|
| E2E-CROSS-004 | 巡检异常处理 | ops+asset | 创建巡检模板->执行巡检->上报异常->创建工单->处理->复检 |
|
||||||
|
| E2E-CROSS-005 | 能耗计费流程 | ops+finance | 创建计量点->抄表录入->费用计算->生成账单->缴费 |
|
||||||
|
|
||||||
|
**各域核心操作**:
|
||||||
|
|
||||||
|
| 域 | 测试场景 |
|
||||||
|
|----|---------|
|
||||||
|
| auth | 登录/登出/Token刷新/密码修改/角色分配 |
|
||||||
|
| space | 项目创建/空间树构建/节点CRUD |
|
||||||
|
| asset | 设备创建/扩展表管理/健康评分查询 |
|
||||||
|
| ops | 工单全流程/维保任务/备件出入库 |
|
||||||
|
| finance | 收费项目/账单生成/支付登记 |
|
||||||
|
|
||||||
|
### 4.3 Page Object模式设计
|
||||||
|
|
||||||
|
```
|
||||||
|
pages/
|
||||||
|
LoginPage.ts - 登录页:用户名/密码输入、登录按钮、错误提示
|
||||||
|
DashboardPage.ts - 仪表盘:菜单导航、项目选择器、数据概览
|
||||||
|
WorkOrderPage.ts - 工单页:列表/创建/详情/派单/执行/完成/验收
|
||||||
|
EquipmentPage.ts - 设备页:列表/创建/详情/健康评分
|
||||||
|
SpacePage.ts - 空间页:空间树/节点管理
|
||||||
|
MaintenancePage.ts - 维保页:计划列表/任务列表
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 测试脚本目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
04-TESTING/e2e/
|
||||||
|
specs/
|
||||||
|
auth/
|
||||||
|
login.spec.ts - 登录流程E2E测试
|
||||||
|
space/
|
||||||
|
project.spec.ts - 项目管理E2E测试
|
||||||
|
asset/
|
||||||
|
equipment.spec.ts - 设备管理E2E测试
|
||||||
|
ops/
|
||||||
|
work-order.spec.ts - 工单管理E2E测试
|
||||||
|
finance/
|
||||||
|
billing.spec.ts - 财务管理E2E测试
|
||||||
|
cross-domain/
|
||||||
|
owner-repair.spec.ts - 业主报修全流程E2E测试
|
||||||
|
equipment-lifecycle.spec.ts - 设备全生命周期E2E测试
|
||||||
|
maintenance-schedule.spec.ts - 预防性维保调度E2E测试
|
||||||
|
fixtures/
|
||||||
|
test-data.json - 测试数据
|
||||||
|
pages/
|
||||||
|
LoginPage.ts - 登录页Page Object
|
||||||
|
DashboardPage.ts - 仪表盘Page Object
|
||||||
|
playwright.config.ts - Playwright配置
|
||||||
|
run-all.sh - 执行全部E2E测试
|
||||||
|
run-module.sh - 执行指定模块E2E测试
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、测试用例清单
|
||||||
|
|
||||||
|
### 5.1 身份与权限域
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-AUTH-001 | 用户登录-正常 | 用户已存在 | POST /api/auth/login {username, password} | 200, 返回token/userId/roles | P0 |
|
||||||
|
| TC-AUTH-002 | 用户登录-密码错误 | 用户已存在 | POST /api/auth/login {username, wrong_pwd} | 401, 返回错误信息 | P0 |
|
||||||
|
| TC-AUTH-003 | 用户登录-锁定 | 连续5次失败 | 连续5次错误密码登录 | 账户锁定10分钟 | P0 |
|
||||||
|
| TC-AUTH-004 | Token刷新 | 已登录 | POST /api/auth/refresh | 200, 返回新Token | P0 |
|
||||||
|
| TC-AUTH-005 | 获取当前用户 | 已登录 | GET /api/auth/me | 200, 返回用户信息 | P0 |
|
||||||
|
| TC-AUTH-006 | 用户登出 | 已登录 | POST /api/auth/logout | 200 | P0 |
|
||||||
|
| TC-AUTH-007 | 创建用户 | 管理员已登录 | POST /api/auth/users {username, password, userType} | 201, 返回用户ID | P0 |
|
||||||
|
| TC-AUTH-008 | 查询用户列表 | 管理员已登录 | GET /api/auth/users?page=0&size=10 | 200, 返回分页数据 | P0 |
|
||||||
|
| TC-AUTH-009 | 更新用户 | 用户已存在 | PUT /api/auth/users/{id} {realName, phone} | 200, 返回更新后数据 | P0 |
|
||||||
|
| TC-AUTH-010 | 删除用户 | 用户已存在 | DELETE /api/auth/users/{id} | 200 | P1 |
|
||||||
|
| TC-AUTH-011 | 修改密码 | 用户已登录 | PUT /api/auth/users/{id}/password | 200 | P0 |
|
||||||
|
| TC-AUTH-012 | 弱密码拒绝 | 管理员已登录 | POST创建用户 password=123456 | 400, 拒绝弱密码 | P0 |
|
||||||
|
| TC-AUTH-013 | 创建角色 | 管理员已登录 | POST /api/auth/roles {code, name, type} | 201 | P0 |
|
||||||
|
| TC-AUTH-014 | 角色权限分配 | 角色/权限已存在 | POST /api/auth/roles/{id}/permissions | 200 | P0 |
|
||||||
|
| TC-AUTH-015 | 创建权限 | 管理员已登录 | POST /api/auth/permissions {code, name, type} | 201 | P0 |
|
||||||
|
| TC-AUTH-016 | 部门树查询 | 部门已存在 | GET /api/auth/depts/tree | 200, 返回树形结构 | P1 |
|
||||||
|
| TC-AUTH-017 | 部门删除约束 | 部门有子部门 | DELETE /api/auth/depts/{id} | 400, 拒绝删除 | P0 |
|
||||||
|
| TC-AUTH-018 | 项目成员管理 | 项目/用户已存在 | 添加/查询/移除项目成员 | 各操作返回正确结果 | P0 |
|
||||||
|
| TC-AUTH-019 | 住户认证流程 | 住户已存在 | UNVERIFIED->PENDING->VERIFIED | 状态正确流转 | P1 |
|
||||||
|
| TC-AUTH-020 | 审计日志查询 | 操作已产生日志 | GET /api/auth/audit-logs | 200, 返回日志列表 | P1 |
|
||||||
|
|
||||||
|
### 5.2 空间与项目域
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-SPACE-001 | 创建项目 | 管理员已登录 | POST /api/mdm/projects {code, name, projectType} | 201, 返回项目ID | P0 |
|
||||||
|
| TC-SPACE-002 | 查询项目列表 | 项目已存在 | GET /api/mdm/projects | 200, 返回分页数据 | P0 |
|
||||||
|
| TC-SPACE-003 | 项目状态流转 | 项目状态PENDING | PUT /api/mdm/projects/{id}/status {status: ACTIVE} | 200 | P0 |
|
||||||
|
| TC-SPACE-004 | 项目删除检查 | 项目有应收费用 | GET /api/mdm/projects/{id}/delete-check | canDelete=false | P0 |
|
||||||
|
| TC-SPACE-005 | 项目配置管理 | 项目已存在 | GET/PUT /api/mdm/projects/{id}/config | 配置正确读写 | P1 |
|
||||||
|
| TC-SPACE-006 | 创建空间节点 | 项目已存在 | POST /api/mdm/space-nodes {name, nodeType} | 201 | P0 |
|
||||||
|
| TC-SPACE-007 | 获取空间树 | 节点已创建 | GET /api/mdm/space-nodes/project/{id}/tree | 200, 返回树形结构 | P0 |
|
||||||
|
| TC-SPACE-008 | 空间节点删除检查 | 节点有子节点 | GET /api/mdm/space-nodes/{id}/delete-check | 返回子节点数 | P0 |
|
||||||
|
| TC-SPACE-009 | 空间节点级联删除 | 节点有子节点 | DELETE /api/mdm/space-nodes/{id}/cascade | 200, 子节点全部删除 | P0 |
|
||||||
|
| TC-SPACE-010 | 巡检标准项CRUD | 管理员已登录 | 创建/查询/更新/删除巡检标准项 | 各操作返回正确结果 | P0 |
|
||||||
|
| TC-SPACE-011 | 巡检模板管理 | 标准项已存在 | 创建/查询/更新/复制巡检模板 | 各操作返回正确结果 | P1 |
|
||||||
|
|
||||||
|
### 5.3 设备与资产域
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-ASSET-001 | 创建设备 | 项目/空间已存在 | POST /api/asset/equipment {equipmentCode, name, type} | 201 | P0 |
|
||||||
|
| TC-ASSET-002 | 按项目查询设备 | 设备已存在 | GET /api/asset/equipment/by-project/{projectId} | 200, 返回设备列表 | P0 |
|
||||||
|
| TC-ASSET-003 | 按空间查询设备 | 设备已关联空间 | GET /api/asset/equipment/by-space/{spaceNodeId} | 200, 返回设备列表 | P0 |
|
||||||
|
| TC-ASSET-004 | 设备批量删除 | 多个设备已存在 | POST /api/asset/equipment/batch-delete {ids} | 200 | P1 |
|
||||||
|
| TC-ASSET-005 | 电梯扩展表管理 | 电梯设备已存在 | GET/PUT /api/asset/equipment/{id}/elevator | 扩展表正确读写 | P0 |
|
||||||
|
| TC-ASSET-006 | 暖通扩展表管理 | 暖通设备已存在 | GET/PUT /api/asset/equipment/{id}/hvac | 扩展表正确读写 | P0 |
|
||||||
|
| TC-ASSET-007 | 消防扩展表管理 | 消防设备已存在 | GET/PUT /api/asset/equipment/{id}/fire | 扩展表正确读写 | P0 |
|
||||||
|
| TC-ASSET-008 | 能源计量扩展表管理 | 能源设备已存在 | GET/PUT /api/asset/equipment/{id}/energy | 扩展表正确读写 | P0 |
|
||||||
|
| TC-ASSET-009 | 设备Excel导入 | 准备xlsx文件 | POST /api/asset/equipment/import | 返回successCount/failCount | P1 |
|
||||||
|
| TC-ASSET-010 | 设备Excel导出 | 设备已存在 | GET /api/asset/equipment/export?projectId={id} | 返回xlsx文件 | P1 |
|
||||||
|
| TC-ASSET-011 | 健康评分查询 | 设备已存在 | GET /api/asset/equipment/{id}/health | 200, 返回健康度/等级 | P1 |
|
||||||
|
| TC-ASSET-012 | 故障记录 | 设备已存在 | POST /api/asset/equipment/{id}/failures | 201, 自动计算时长 | P0 |
|
||||||
|
| TC-ASSET-013 | 归属主体CRUD | 管理员已登录 | 创建/查询/更新/删除归属主体 | 各操作返回正确结果 | P1 |
|
||||||
|
| TC-ASSET-014 | 设备统计 | 设备已存在 | GET /api/asset/equipment/statistics/by-type | 200, 返回统计数据 | P1 |
|
||||||
|
|
||||||
|
### 5.4 运营与服务域
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-OPS-001 | 创建工单 | 项目/设备已存在 | POST /api/wo/work-orders {title, source, type} | 201, workNo自动生成 | P0 |
|
||||||
|
| TC-OPS-002 | 工单派单 | 工单状态PENDING | POST /api/wo/work-orders/{id}/assign | 200, 状态变ASSIGNED | P0 |
|
||||||
|
| TC-OPS-003 | 工单开始执行 | 工单状态ASSIGNED | POST /api/wo/work-orders/{id}/start | 200, 状态变IN_PROGRESS | P0 |
|
||||||
|
| TC-OPS-004 | 工单完成 | 工单状态IN_PROGRESS | POST /api/wo/work-orders/{id}/complete | 200, 状态变COMPLETED | P0 |
|
||||||
|
| TC-OPS-005 | 工单验收 | 工单状态COMPLETED | POST /api/wo/work-orders/{id}/verify {rating} | 200, 状态变VERIFIED | P0 |
|
||||||
|
| TC-OPS-006 | 工单取消-待分配 | 工单状态PENDING | POST /api/wo/work-orders/{id}/cancel | 200, 状态变CANCELLED | P1 |
|
||||||
|
| TC-OPS-007 | 工单取消-已完成 | 工单状态COMPLETED | POST /api/wo/work-orders/{id}/cancel | 400, 拒绝取消 | P0 |
|
||||||
|
| TC-OPS-008 | 工单统计 | 工单已存在 | GET /api/wo/work-orders/statistics | 200, 返回统计数据 | P1 |
|
||||||
|
| TC-OPS-009 | 创建维保任务 | 设备已存在 | POST /api/wo/maintenance-tasks {equipmentId, type} | 201 | P0 |
|
||||||
|
| TC-OPS-010 | 维保任务状态流转 | 任务状态PENDING | assign->start->complete->verify | 状态正确流转 | P0 |
|
||||||
|
| TC-OPS-011 | 维保任务自动优先级 | 创建紧急维修任务 | POST创建type=EMERGENCY | priority自动为URGENT | P0 |
|
||||||
|
| TC-OPS-012 | 维保任务完成后设备联动 | 任务关联设备 | 完成维保任务 | 设备维保商/下次巡检日期更新 | P0 |
|
||||||
|
| TC-OPS-013 | 备件入库 | 备件已存在 | POST /api/ops/spare-parts/in-stock | 200, currentStock增加 | P0 |
|
||||||
|
| TC-OPS-014 | 备件出库 | 备件库存充足 | POST /api/ops/spare-parts/out-stock | 200, currentStock减少 | P0 |
|
||||||
|
| TC-OPS-015 | 备件出库-库存不足 | 备件库存不足 | POST出库数量>库存 | 400, 拒绝出库 | P0 |
|
||||||
|
| TC-OPS-016 | 低库存预警 | 备件currentStock<safeStock | GET /api/ops/spare-parts/low-stock | 返回低库存备件列表 | P1 |
|
||||||
|
| TC-OPS-017 | 能耗抄表录入 | 计量点已存在 | POST /api/ops/energy/consumption | 201, 自动计算消耗量和费用 | P0 |
|
||||||
|
| TC-OPS-018 | 能耗抄表-读数递减 | 上次读数100 | POST currentReading=50 | 400, 拒绝递减读数 | P0 |
|
||||||
|
| TC-OPS-019 | 维保计划管理 | 管理员已登录 | 创建/查询/更新维保计划 | 各操作返回正确结果 | P1 |
|
||||||
|
| TC-OPS-020 | 巡检模板管理 | 管理员已登录 | 创建/查询/更新/复制巡检模板 | 各操作返回正确结果 | P1 |
|
||||||
|
|
||||||
|
### 5.5 财务与收费域
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-FIN-001 | 创建收费项目 | 模块已实现 | POST /api/finance/fee-items | 201 | P0 |
|
||||||
|
| TC-FIN-002 | 查询收费项目 | 收费项目已存在 | GET /api/finance/fee-items | 200 | P0 |
|
||||||
|
| TC-FIN-003 | 生成账单 | 收费项目/业主已存在 | POST /api/finance/fee-bills/generate | 201 | P0 |
|
||||||
|
| TC-FIN-004 | 账单状态流转 | 账单已存在 | UNPAID->PARTIAL_PAID->PAID | 状态正确流转 | P0 |
|
||||||
|
| TC-FIN-005 | 线下收款登记 | 账单已存在 | POST /api/finance/fee-payments | 201 | P0 |
|
||||||
|
| TC-FIN-006 | 退款申请 | 支付已完成 | POST /api/finance/fee-refunds | 201 | P1 |
|
||||||
|
| TC-FIN-007 | 滞纳金计算 | 账单逾期 | 自动计算滞纳金 | 金额正确 | P1 |
|
||||||
|
|
||||||
|
### 5.6 前端交互域
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-FE-001 | 登录页面-正常登录 | 系统可访问 | 输入用户名密码->点击登录 | 跳转至仪表盘 | P0 |
|
||||||
|
| TC-FE-002 | 登录页面-错误密码 | 系统可访问 | 输入错误密码->点击登录 | 显示错误提示 | P0 |
|
||||||
|
| TC-FE-003 | 项目选择 | 已登录 | 点击项目选择器->选择项目 | 页面数据刷新 | P0 |
|
||||||
|
| TC-FE-004 | 权限控制 | 不同角色用户 | 访问无权限页面/按钮 | 隐藏或禁用 | P0 |
|
||||||
|
| TC-FE-005 | 用户管理页面 | 管理员已登录 | 列表/创建/编辑/删除用户 | 各操作正常 | P0 |
|
||||||
|
| TC-FE-006 | 设备管理页面 | 管理员已登录 | 列表/创建/详情/导入 | 各操作正常 | P0 |
|
||||||
|
| TC-FE-007 | 工单管理页面 | 管理员已登录 | 列表/创建/派单/执行/验收 | 各操作正常 | P0 |
|
||||||
|
|
||||||
|
### 5.7 跨域业务流程
|
||||||
|
|
||||||
|
| 用例ID | 名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|
||||||
|
|--------|------|---------|---------|---------|--------|
|
||||||
|
| TC-CROSS-001 | 业主报修全流程 | 业主/设备/维保人员已存在 | 登录->选项目->创建工单->派单->执行->完成->验收 | 工单状态PENDING->VERIFIED | P0 |
|
||||||
|
| TC-CROSS-002 | 设备全生命周期 | 项目/空间已存在 | 创建设备->关联空间->录入扩展表->健康评分->维保->报废 | 设备状态ACTIVE->SCRAPPED | P0 |
|
||||||
|
| TC-CROSS-003 | 预防性维保调度 | 设备/维保计划已存在 | 创建计划->创建任务->分配->执行->完成->验收 | 任务完成+设备信息更新 | P0 |
|
||||||
|
| TC-CROSS-004 | 巡检异常处理 | 巡检模板/设备已存在 | 创建模板->执行巡检->上报异常->创建工单->处理 | 异常问题解决 | P0 |
|
||||||
|
| TC-CROSS-005 | 能耗计费流程 | 计量点/收费项目已存在 | 创建计量点->抄表->费用计算->生成账单->缴费 | 账单状态PAID | P0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、测试执行与报告
|
||||||
|
|
||||||
|
### 6.1 单模块测试执行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API测试
|
||||||
|
./04-TESTING/api/run-module.sh auth
|
||||||
|
./04-TESTING/api/run-module.sh space
|
||||||
|
./04-TESTING/api/run-module.sh asset
|
||||||
|
./04-TESTING/api/run-module.sh ops
|
||||||
|
./04-TESTING/api/run-module.sh finance
|
||||||
|
|
||||||
|
# E2E测试
|
||||||
|
./04-TESTING/e2e/run-module.sh auth
|
||||||
|
./04-TESTING/e2e/run-module.sh space
|
||||||
|
./04-TESTING/e2e/run-module.sh asset
|
||||||
|
./04-TESTING/e2e/run-module.sh ops
|
||||||
|
./04-TESTING/e2e/run-module.sh finance
|
||||||
|
./04-TESTING/e2e/run-module.sh cross-domain
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 全量测试执行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API全量测试
|
||||||
|
./04-TESTING/api/run-all.sh
|
||||||
|
|
||||||
|
# E2E全量测试
|
||||||
|
./04-TESTING/e2e/run-all.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 串联测试执行
|
||||||
|
|
||||||
|
跨域流程测试需要按顺序执行,确保数据依赖正确:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 业主报修全流程
|
||||||
|
./04-TESTING/e2e/run-module.sh cross-domain
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 测试报告生成
|
||||||
|
|
||||||
|
**API测试报告**:
|
||||||
|
- 脚本执行后自动输出测试摘要(PASS/FAIL/总计/通过率)
|
||||||
|
- 详细日志输出到 04-TESTING/reports/api-{module}-{date}.log
|
||||||
|
|
||||||
|
**E2E测试报告**:
|
||||||
|
- Playwright HTML报告:04-TESTING/reports/e2e-report/
|
||||||
|
- 执行命令:`npx playwright test --reporter=html`
|
||||||
|
- 报告内容:通过率、失败截图、执行耗时
|
||||||
|
|
||||||
|
**报告指标**:
|
||||||
|
- 总用例数 / 通过数 / 失败数 / 跳过数
|
||||||
|
- 通过率 = 通过数 / 总用例数 * 100%
|
||||||
|
- 失败分析:按失败原因分类(断言失败/超时/环境问题)
|
||||||
|
|
||||||
|
### 6.5 测试报告输出目录
|
||||||
|
|
||||||
|
```
|
||||||
|
04-TESTING/reports/
|
||||||
|
api-auth-YYYYMMDD.log
|
||||||
|
api-space-YYYYMMDD.log
|
||||||
|
api-asset-YYYYMMDD.log
|
||||||
|
api-ops-YYYYMMDD.log
|
||||||
|
api-finance-YYYYMMDD.log
|
||||||
|
e2e-report/
|
||||||
|
index.html
|
||||||
|
trace/
|
||||||
|
screenshots/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、测试准入准出标准
|
||||||
|
|
||||||
|
### 7.1 准入标准
|
||||||
|
|
||||||
|
代码提交测试必须满足以下条件:
|
||||||
|
|
||||||
|
| 准入项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| 代码审查 | PR必须经过至少1人审查通过 |
|
||||||
|
| 单元测试 | 新增/修改代码必须有对应单元测试 |
|
||||||
|
| 编译通过 | mvn clean install -DskipTests 无错误 |
|
||||||
|
| 类型检查 | npx vue-tsc --noEmit 无错误 |
|
||||||
|
| API文档 | 新增/修改接口必须更新API文档 |
|
||||||
|
|
||||||
|
### 7.2 准出标准
|
||||||
|
|
||||||
|
版本发布必须满足以下条件:
|
||||||
|
|
||||||
|
| 准出项 | 要求 |
|
||||||
|
|--------|------|
|
||||||
|
| API测试通过率 | >= 95% |
|
||||||
|
| E2E测试通过率 | >= 90% |
|
||||||
|
| P0缺陷 | 0个 |
|
||||||
|
| P1缺陷 | 0个 |
|
||||||
|
| P2缺陷 | 不超过5个(需有修复计划) |
|
||||||
|
| 单元测试覆盖率 | 核心Service层 >= 80% |
|
||||||
|
| 性能测试 | 关键接口响应时间 < 500ms |
|
||||||
|
|
||||||
|
**缺陷优先级定义**:
|
||||||
|
|
||||||
|
| 级别 | 定义 | 修复时限 |
|
||||||
|
|------|------|---------|
|
||||||
|
| P0 | 系统崩溃/数据丢失/安全漏洞 | 4小时内 |
|
||||||
|
| P1 | 核心功能不可用/业务流程阻断 | 24小时内 |
|
||||||
|
| P2 | 功能异常但有规避方案 | 3个工作日内 |
|
||||||
|
| P3 | UI显示问题/体验优化 | 下个迭代 |
|
||||||
|
|
@ -0,0 +1,321 @@
|
||||||
|
#!/bin/bash
|
||||||
|
BASE_URL="http://localhost:8080/api"
|
||||||
|
TOKEN=""
|
||||||
|
PROJECT_ID=""
|
||||||
|
PASS_COUNT=0
|
||||||
|
FAIL_COUNT=0
|
||||||
|
TOTAL_COUNT=0
|
||||||
|
|
||||||
|
assert_eq() {
|
||||||
|
local actual="$1"
|
||||||
|
local expected="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if [ "$actual" = "$expected" ]; then
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: $name (expected=$expected, actual=$actual)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local haystack="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if echo "$haystack" | grep -q "$needle"; then
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: $name (expected to contain: $needle)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
echo "=== 登录获取Token ==="
|
||||||
|
local response=$(curl -s -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"admin123"}')
|
||||||
|
TOKEN=$(echo "$response" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo "FATAL: 登录失败,无法获取Token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Token获取成功"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_project() {
|
||||||
|
echo "=== 创建测试项目 ==="
|
||||||
|
local response=$(curl -s -X POST "$BASE_URL/mdm/projects" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: " \
|
||||||
|
-d "{\"code\":\"TEST-ASSET-$$\",\"name\":\"ASSET测试项目\",\"projectType\":\"RESIDENTIAL\"}")
|
||||||
|
PROJECT_ID=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$PROJECT_ID" ]; then
|
||||||
|
echo "FATAL: 创建测试项目失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "测试项目创建成功: $PROJECT_ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_create_equipment() {
|
||||||
|
echo "--- TC-ASSET-001: 创建设备 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/asset/equipment" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentCode\":\"TEST-EQ-$$_$RANDOM\",\"equipmentName\":\"测试电梯设备\",\"equipmentType\":\"ELEVATOR\",\"systemType\":\"ELEVATOR\",\"ownershipType\":\"PROJECT\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "201" "TC-ASSET-001: 创建设备返回201"
|
||||||
|
EQUIPMENT_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_query_equipment_by_project() {
|
||||||
|
echo "--- TC-ASSET-002: 按项目查询设备 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/asset/equipment/by-project/$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-ASSET-002: 按项目查询设备返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_query_equipment_by_space() {
|
||||||
|
echo "--- TC-ASSET-003: 按空间查询设备 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/asset/equipment/by-space/00000000-0000-0000-0000-000000000000" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-ASSET-003: 按空间查询设备返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_batch_delete_equipment() {
|
||||||
|
echo "--- TC-ASSET-004: 设备批量删除 ---"
|
||||||
|
local eq1_response=$(curl -s -X POST "$BASE_URL/asset/equipment" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentCode\":\"BATCH-DEL-1-$$_$RANDOM\",\"equipmentName\":\"批量删除测试1\",\"equipmentType\":\"HVAC\",\"systemType\":\"HVAC\",\"ownershipType\":\"PROJECT\"}")
|
||||||
|
local eq1_id=$(echo "$eq1_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
local eq2_response=$(curl -s -X POST "$BASE_URL/asset/equipment" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentCode\":\"BATCH-DEL-2-$$_$RANDOM\",\"equipmentName\":\"批量删除测试2\",\"equipmentType\":\"HVAC\",\"systemType\":\"HVAC\",\"ownershipType\":\"PROJECT\"}")
|
||||||
|
local eq2_id=$(echo "$eq2_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -n "$eq1_id" ] && [ -n "$eq2_id" ]; then
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/asset/equipment/batch-delete" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"ids\":[\"$eq1_id\",\"$eq2_id\"]}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-ASSET-004: 批量删除返回200"
|
||||||
|
else
|
||||||
|
echo " SKIP: TC-ASSET-004 (无法创建测试设备)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_elevator_extension() {
|
||||||
|
echo "--- TC-ASSET-005: 电梯扩展表管理 ---"
|
||||||
|
if [ -z "$EQUIPMENT_ID" ]; then
|
||||||
|
echo " SKIP: TC-ASSET-005 (无设备ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local put_response=$(curl -s -w "\n%{http_code}" -X PUT "$BASE_URL/asset/equipment/$EQUIPMENT_ID/elevator" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"elevatorType":"PASSENGER","model":"TEST-001","loadCapacity":1000,"speed":2.5,"floorCount":20}')
|
||||||
|
local put_status=$(echo "$put_response" | tail -n 1)
|
||||||
|
assert_eq "$put_status" "200" "TC-ASSET-005: 更新电梯扩展表返回200"
|
||||||
|
local get_response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/asset/equipment/$EQUIPMENT_ID/elevator" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local get_status=$(echo "$get_response" | tail -n 1)
|
||||||
|
assert_eq "$get_status" "200" "TC-ASSET-005: 查询电梯扩展表返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_hvac_extension() {
|
||||||
|
echo "--- TC-ASSET-006: 暖通扩展表管理 ---"
|
||||||
|
local hvac_response=$(curl -s -X POST "$BASE_URL/asset/equipment" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentCode\":\"HVAC-TEST-$$_$RANDOM\",\"equipmentName\":\"测试暖通设备\",\"equipmentType\":\"HVAC\",\"systemType\":\"HVAC\",\"ownershipType\":\"PROJECT\"}")
|
||||||
|
local hvac_id=$(echo "$hvac_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$hvac_id" ]; then
|
||||||
|
echo " SKIP: TC-ASSET-006 (无法创建暖通设备)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local put_response=$(curl -s -w "\n%{http_code}" -X PUT "$BASE_URL/asset/equipment/$hvac_id/hvac" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"hvacType":"CHILLER","coolingCapacity":500,"heatingCapacity":400}')
|
||||||
|
local put_status=$(echo "$put_response" | tail -n 1)
|
||||||
|
assert_eq "$put_status" "200" "TC-ASSET-006: 更新暖通扩展表返回200"
|
||||||
|
HVAC_EQ_ID="$hvac_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_fire_extension() {
|
||||||
|
echo "--- TC-ASSET-007: 消防扩展表管理 ---"
|
||||||
|
local fire_response=$(curl -s -X POST "$BASE_URL/asset/equipment" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentCode\":\"FIRE-TEST-$$_$RANDOM\",\"equipmentName\":\"测试消防设备\",\"equipmentType\":\"FIRE_PROTECTION\",\"systemType\":\"FIRE\",\"ownershipType\":\"PROJECT\"}")
|
||||||
|
local fire_id=$(echo "$fire_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$fire_id" ]; then
|
||||||
|
echo " SKIP: TC-ASSET-007 (无法创建消防设备)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local put_response=$(curl -s -w "\n%{http_code}" -X PUT "$BASE_URL/asset/equipment/$fire_id/fire" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"fireEquipmentType":"SPRINKLER","installationArea":200}')
|
||||||
|
local put_status=$(echo "$put_response" | tail -n 1)
|
||||||
|
assert_eq "$put_status" "200" "TC-ASSET-007: 更新消防扩展表返回200"
|
||||||
|
FIRE_EQ_ID="$fire_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_energy_extension() {
|
||||||
|
echo "--- TC-ASSET-008: 能源计量扩展表管理 ---"
|
||||||
|
local energy_response=$(curl -s -X POST "$BASE_URL/asset/equipment" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentCode\":\"ENERGY-TEST-$$_$RANDOM\",\"equipmentName\":\"测试能源计量设备\",\"equipmentType\":\"ENERGY_METER\",\"systemType\":\"ELECTRICAL\",\"ownershipType\":\"PROJECT\"}")
|
||||||
|
local energy_id=$(echo "$energy_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$energy_id" ]; then
|
||||||
|
echo " SKIP: TC-ASSET-008 (无法创建能源计量设备)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local put_response=$(curl -s -w "\n%{http_code}" -X PUT "$BASE_URL/asset/equipment/$energy_id/energy" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"meterType":"ELECTRICITY","energyType":"LIGHTING","accuracyClass":"1.0"}')
|
||||||
|
local put_status=$(echo "$put_response" | tail -n 1)
|
||||||
|
assert_eq "$put_status" "200" "TC-ASSET-008: 更新能源计量扩展表返回200"
|
||||||
|
ENERGY_EQ_ID="$energy_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_equipment_statistics() {
|
||||||
|
echo "--- TC-ASSET-009: 设备统计 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/asset/equipment/statistics/by-type?projectId=$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-ASSET-009: 设备统计返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_health_score() {
|
||||||
|
echo "--- TC-ASSET-010: 健康评分查询 ---"
|
||||||
|
if [ -z "$EQUIPMENT_ID" ]; then
|
||||||
|
echo " SKIP: TC-ASSET-010 (无设备ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/asset/equipment/$EQUIPMENT_ID/health" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-ASSET-010: 健康评分查询返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_failure_record() {
|
||||||
|
echo "--- TC-ASSET-011: 故障记录 ---"
|
||||||
|
if [ -z "$EQUIPMENT_ID" ]; then
|
||||||
|
echo " SKIP: TC-ASSET-011 (无设备ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/asset/equipment/$EQUIPMENT_ID/failures" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"failureType":"SUDDEN","failureLevel":"MEDIUM","description":"测试故障记录","failureTime":"2026-05-18T10:00:00"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "201" "TC-ASSET-011: 故障记录返回201"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_ownership_entity_crud() {
|
||||||
|
echo "--- TC-ASSET-012: 归属主体CRUD ---"
|
||||||
|
local create_response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/asset/ownership-entities" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"entityName\":\"测试归属主体-$$_$RANDOM\",\"entityType\":\"COMPANY\",\"contactPerson\":\"张三\",\"contactPhone\":\"13800138000\"}")
|
||||||
|
local create_status=$(echo "$create_response" | tail -n 1)
|
||||||
|
local create_body=$(echo "$create_response" | head -n -1)
|
||||||
|
assert_eq "$create_status" "201" "TC-ASSET-012: 创建归属主体返回201"
|
||||||
|
OWNERSHIP_ID=$(echo "$create_body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -n "$OWNERSHIP_ID" ]; then
|
||||||
|
local query_response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/asset/ownership-entities/$OWNERSHIP_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local query_status=$(echo "$query_response" | tail -n 1)
|
||||||
|
assert_eq "$query_status" "200" "TC-ASSET-012: 查询归属主体返回200"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "=== 清理测试数据 ==="
|
||||||
|
if [ -n "$PROJECT_ID" ]; then
|
||||||
|
curl -s -X DELETE "$BASE_URL/mdm/projects/$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Asset Domain API Test Summary"
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Total: $TOTAL_COUNT"
|
||||||
|
echo " Passed: $PASS_COUNT"
|
||||||
|
echo " Failed: $FAIL_COUNT"
|
||||||
|
if [ "$TOTAL_COUNT" -gt 0 ]; then
|
||||||
|
local rate=$(echo "scale=1; $PASS_COUNT * 100 / $TOTAL_COUNT" | bc)
|
||||||
|
echo " Rate: ${rate}%"
|
||||||
|
fi
|
||||||
|
echo "=========================================="
|
||||||
|
if [ "$FAIL_COUNT" -gt 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
EQUIPMENT_ID=""
|
||||||
|
HVAC_EQ_ID=""
|
||||||
|
FIRE_EQ_ID=""
|
||||||
|
ENERGY_EQ_ID=""
|
||||||
|
OWNERSHIP_ID=""
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Ether PMS - Asset Domain API Tests"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
login
|
||||||
|
setup_project
|
||||||
|
|
||||||
|
test_create_equipment
|
||||||
|
test_query_equipment_by_project
|
||||||
|
test_query_equipment_by_space
|
||||||
|
test_batch_delete_equipment
|
||||||
|
test_elevator_extension
|
||||||
|
test_hvac_extension
|
||||||
|
test_fire_extension
|
||||||
|
test_energy_extension
|
||||||
|
test_equipment_statistics
|
||||||
|
test_health_score
|
||||||
|
test_failure_record
|
||||||
|
test_ownership_entity_crud
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
print_summary
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
#!/bin/bash
|
||||||
|
BASE_URL="http://localhost:8080/api"
|
||||||
|
TOKEN=""
|
||||||
|
PASS_COUNT=0
|
||||||
|
FAIL_COUNT=0
|
||||||
|
TOTAL_COUNT=0
|
||||||
|
|
||||||
|
assert_eq() {
|
||||||
|
local actual="$1"
|
||||||
|
local expected="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if [ "$actual" = "$expected" ]; then
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: $name (expected=$expected, actual=$actual)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local haystack="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if echo "$haystack" | grep -q "$needle"; then
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: $name (expected to contain: $needle)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_not_contains() {
|
||||||
|
local haystack="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if echo "$haystack" | grep -q "$needle"; then
|
||||||
|
echo " FAIL: $name (expected NOT to contain: $needle)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
echo "=== 登录获取Token ==="
|
||||||
|
local response=$(curl -s -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"admin123"}')
|
||||||
|
TOKEN=$(echo "$response" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo "FATAL: 登录失败,无法获取Token"
|
||||||
|
echo "Response: $response"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Token获取成功"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_login_success() {
|
||||||
|
echo "--- TC-AUTH-001: 用户登录-正常 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"admin123"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-001: 登录返回200"
|
||||||
|
assert_contains "$body" '"token"' "TC-AUTH-001: 响应包含token"
|
||||||
|
assert_contains "$body" '"userId"' "TC-AUTH-001: 响应包含userId"
|
||||||
|
assert_contains "$body" '"roles"' "TC-AUTH-001: 响应包含roles"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_login_wrong_password() {
|
||||||
|
echo "--- TC-AUTH-002: 用户登录-密码错误 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"wrongpassword"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "401" "TC-AUTH-002: 错误密码返回401"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_login_lockout() {
|
||||||
|
echo "--- TC-AUTH-003: 用户登录-锁定机制 ---"
|
||||||
|
local test_user="lockout_test_$$"
|
||||||
|
curl -s -X POST "$BASE_URL/auth/users" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d "{\"username\":\"$test_user\",\"password\":\"Test@1234\",\"userType\":\"ENTERPRISE\"}" > /dev/null 2>&1
|
||||||
|
for i in $(seq 1 5); do
|
||||||
|
curl -s -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\":\"$test_user\",\"password\":\"wrong$i\"}" > /dev/null 2>&1
|
||||||
|
done
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\":\"$test_user\",\"password\":\"Test@1234\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "401" "TC-AUTH-003: 5次失败后账户锁定"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_get_current_user() {
|
||||||
|
echo "--- TC-AUTH-005: 获取当前用户 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/auth/me" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-005: 获取当前用户返回200"
|
||||||
|
assert_contains "$body" '"username"' "TC-AUTH-005: 响应包含username"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_refresh_token() {
|
||||||
|
echo "--- TC-AUTH-004: Token刷新 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/refresh" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-004: Token刷新返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_logout() {
|
||||||
|
echo "--- TC-AUTH-006: 用户登出 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/logout" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-006: 登出返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_create_user() {
|
||||||
|
echo "--- TC-AUTH-007: 创建用户 ---"
|
||||||
|
local username="testuser_$$"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/users" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d "{\"username\":\"$username\",\"password\":\"Test@1234\",\"userType\":\"ENTERPRISE\",\"realName\":\"Test User\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "201" "TC-AUTH-007: 创建用户返回201"
|
||||||
|
CREATED_USER_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_list_users() {
|
||||||
|
echo "--- TC-AUTH-008: 查询用户列表 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/auth/users?page=0&size=10" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-008: 查询用户列表返回200"
|
||||||
|
assert_contains "$body" '"content"' "TC-AUTH-008: 响应包含content"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_update_user() {
|
||||||
|
echo "--- TC-AUTH-009: 更新用户 ---"
|
||||||
|
if [ -z "$CREATED_USER_ID" ]; then
|
||||||
|
echo " SKIP: TC-AUTH-009 (无用户ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X PUT "$BASE_URL/auth/users/$CREATED_USER_ID" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{"realName":"Updated Name","phone":"13800138000"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-009: 更新用户返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_delete_user() {
|
||||||
|
echo "--- TC-AUTH-010: 删除用户 ---"
|
||||||
|
if [ -z "$CREATED_USER_ID" ]; then
|
||||||
|
echo " SKIP: TC-AUTH-010 (无用户ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X DELETE "$BASE_URL/auth/users/$CREATED_USER_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-010: 删除用户返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_weak_password_rejected() {
|
||||||
|
echo "--- TC-AUTH-012: 弱密码拒绝 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/users" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{"username":"weakpwd_$$_test","password":"123456","userType":"ENTERPRISE"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "400" "TC-AUTH-012: 弱密码返回400"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_create_role() {
|
||||||
|
echo "--- TC-AUTH-013: 创建角色 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/roles" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d "{\"code\":\"TEST_ROLE_$$\",\"name\":\"Test Role\",\"type\":\"SYSTEM\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "201" "TC-AUTH-013: 创建角色返回201"
|
||||||
|
CREATED_ROLE_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_assign_role_permissions() {
|
||||||
|
echo "--- TC-AUTH-014: 角色权限分配 ---"
|
||||||
|
if [ -z "$CREATED_ROLE_ID" ]; then
|
||||||
|
echo " SKIP: TC-AUTH-014 (无角色ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/roles/$CREATED_ROLE_ID/permissions" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{"permissionIds":[]}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-014: 角色权限分配返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_create_permission() {
|
||||||
|
echo "--- TC-AUTH-015: 创建权限 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/permissions" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d "{\"code\":\"test:resource:action_$$\",\"name\":\"Test Permission\",\"type\":\"API\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "201" "TC-AUTH-015: 创建权限返回201"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_dept_tree() {
|
||||||
|
echo "--- TC-AUTH-016: 部门树查询 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/auth/depts/tree" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-016: 部门树查询返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_audit_log_query() {
|
||||||
|
echo "--- TC-AUTH-020: 审计日志查询 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/auth/audit-logs?page=0&size=10" \
|
||||||
|
-H "Authorization: Bearer $TOKEN")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-AUTH-020: 审计日志查询返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "=== 清理测试数据 ==="
|
||||||
|
if [ -n "$CREATED_ROLE_ID" ]; then
|
||||||
|
curl -s -X DELETE "$BASE_URL/auth/roles/$CREATED_ROLE_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Auth Domain API Test Summary"
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Total: $TOTAL_COUNT"
|
||||||
|
echo " Passed: $PASS_COUNT"
|
||||||
|
echo " Failed: $FAIL_COUNT"
|
||||||
|
if [ "$TOTAL_COUNT" -gt 0 ]; then
|
||||||
|
local rate=$(echo "scale=1; $PASS_COUNT * 100 / $TOTAL_COUNT" | bc)
|
||||||
|
echo " Rate: ${rate}%"
|
||||||
|
fi
|
||||||
|
echo "=========================================="
|
||||||
|
if [ "$FAIL_COUNT" -gt 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATED_USER_ID=""
|
||||||
|
CREATED_ROLE_ID=""
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Ether PMS - Auth Domain API Tests"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
login
|
||||||
|
|
||||||
|
test_login_success
|
||||||
|
test_login_wrong_password
|
||||||
|
test_login_lockout
|
||||||
|
test_get_current_user
|
||||||
|
test_refresh_token
|
||||||
|
test_create_user
|
||||||
|
test_list_users
|
||||||
|
test_update_user
|
||||||
|
test_weak_password_rejected
|
||||||
|
test_create_role
|
||||||
|
test_assign_role_permissions
|
||||||
|
test_create_permission
|
||||||
|
test_dept_tree
|
||||||
|
test_delete_user
|
||||||
|
test_audit_log_query
|
||||||
|
test_logout
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
print_summary
|
||||||
|
|
@ -0,0 +1,357 @@
|
||||||
|
#!/bin/bash
|
||||||
|
BASE_URL="http://localhost:8080/api"
|
||||||
|
TOKEN=""
|
||||||
|
PROJECT_ID=""
|
||||||
|
PASS_COUNT=0
|
||||||
|
FAIL_COUNT=0
|
||||||
|
TOTAL_COUNT=0
|
||||||
|
|
||||||
|
assert_eq() {
|
||||||
|
local actual="$1"
|
||||||
|
local expected="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if [ "$actual" = "$expected" ]; then
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: $name (expected=$expected, actual=$actual)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local haystack="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local name="$3"
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
||||||
|
if echo "$haystack" | grep -q "$needle"; then
|
||||||
|
echo " PASS: $name"
|
||||||
|
PASS_COUNT=$((PASS_COUNT + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: $name (expected to contain: $needle)"
|
||||||
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
echo "=== 登录获取Token ==="
|
||||||
|
local response=$(curl -s -X POST "$BASE_URL/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"admin123"}')
|
||||||
|
TOKEN=$(echo "$response" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo "FATAL: 登录失败,无法获取Token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Token获取成功"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_project() {
|
||||||
|
echo "=== 创建测试项目 ==="
|
||||||
|
local response=$(curl -s -X POST "$BASE_URL/mdm/projects" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: " \
|
||||||
|
-d "{\"code\":\"TEST-OPS-$$\",\"name\":\"OPS测试项目\",\"projectType\":\"RESIDENTIAL\"}")
|
||||||
|
PROJECT_ID=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$PROJECT_ID" ]; then
|
||||||
|
echo "FATAL: 创建测试项目失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "测试项目创建成功: $PROJECT_ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_create_work_order() {
|
||||||
|
echo "--- TC-OPS-001: 创建工单 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/work-orders" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"title\":\"测试工单-$$\",\"source\":\"MANUAL\",\"type\":\"REPAIR\",\"priority\":\"MEDIUM\",\"description\":\"API测试工单\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
assert_eq "$status" "201" "TC-OPS-001: 创建工单返回201"
|
||||||
|
assert_contains "$body" '"workNo"' "TC-OPS-001: 响应包含workNo"
|
||||||
|
WORK_ORDER_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_query_work_orders() {
|
||||||
|
echo "--- TC-OPS-002: 查询工单列表 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/wo/work-orders?projectId=$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-002: 查询工单列表返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_assign_work_order() {
|
||||||
|
echo "--- TC-OPS-003: 工单派单 ---"
|
||||||
|
if [ -z "$WORK_ORDER_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-003 (无工单ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/work-orders/$WORK_ORDER_ID/assign" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"assignedTo":"test_maintainer","assignedDate":"2026-05-18"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-003: 工单派单返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_start_work_order() {
|
||||||
|
echo "--- TC-OPS-004: 工单开始执行 ---"
|
||||||
|
if [ -z "$WORK_ORDER_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-004 (无工单ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/work-orders/$WORK_ORDER_ID/start" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-004: 工单开始执行返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_complete_work_order() {
|
||||||
|
echo "--- TC-OPS-005: 工单完成 ---"
|
||||||
|
if [ -z "$WORK_ORDER_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-005 (无工单ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/work-orders/$WORK_ORDER_ID/complete" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"faultCause":"测试故障原因","solution":"测试解决方案","result":"已修复","laborCost":100,"partsCost":50}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-005: 工单完成返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_verify_work_order() {
|
||||||
|
echo "--- TC-OPS-006: 工单验收 ---"
|
||||||
|
if [ -z "$WORK_ORDER_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-006 (无工单ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/work-orders/$WORK_ORDER_ID/verify" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d '{"verifiedBy":"admin","rating":5,"remark":"验收通过"}')
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-006: 工单验收返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_cancel_completed_work_order() {
|
||||||
|
echo "--- TC-OPS-007: 工单取消-已完成拒绝 ---"
|
||||||
|
if [ -z "$WORK_ORDER_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-007 (无工单ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/work-orders/$WORK_ORDER_ID/cancel" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "400" "TC-OPS-007: 已完成工单取消返回400"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_work_order_statistics() {
|
||||||
|
echo "--- TC-OPS-008: 工单统计 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/wo/work-orders/statistics?projectId=$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-008: 工单统计返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_create_maintenance_task() {
|
||||||
|
echo "--- TC-OPS-009: 创建维保任务 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/wo/maintenance-tasks" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"equipmentId\":\"00000000-0000-0000-0000-000000000000\",\"taskType\":\"PREVENTIVE\",\"triggerType\":\"MANUAL\",\"title\":\"测试维保任务-$$\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
local body=$(echo "$response" | head -n -1)
|
||||||
|
if [ "$status" = "201" ] || [ "$status" = "200" ]; then
|
||||||
|
assert_eq "$status" "$status" "TC-OPS-009: 创建维保任务成功"
|
||||||
|
MAINT_TASK_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
else
|
||||||
|
assert_contains "$body" "equipment" "TC-OPS-009: 创建维保任务(设备不存在时返回错误)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_spare_part_crud() {
|
||||||
|
echo "--- TC-OPS-010: 备件CRUD ---"
|
||||||
|
local create_response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/ops/spare-parts" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"partCode\":\"TEST-SP-$$_$RANDOM\",\"partName\":\"测试备件\",\"unit\":\"个\",\"safeStock\":10,\"currentStock\":100,\"unitPrice\":25.00}")
|
||||||
|
local create_status=$(echo "$create_response" | tail -n 1)
|
||||||
|
local create_body=$(echo "$create_response" | head -n -1)
|
||||||
|
assert_eq "$create_status" "201" "TC-OPS-010: 创建备件返回201"
|
||||||
|
SPARE_PART_ID=$(echo "$create_body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_spare_part_in_stock() {
|
||||||
|
echo "--- TC-OPS-011: 备件入库 ---"
|
||||||
|
if [ -z "$SPARE_PART_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-011 (无备件ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/ops/spare-parts/in-stock" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"sparePartId\":\"$SPARE_PART_ID\",\"quantity\":50,\"operator\":\"admin\",\"remark\":\"API测试入库\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-011: 备件入库返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_spare_part_out_stock() {
|
||||||
|
echo "--- TC-OPS-012: 备件出库 ---"
|
||||||
|
if [ -z "$SPARE_PART_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-012 (无备件ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/ops/spare-parts/out-stock" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"sparePartId\":\"$SPARE_PART_ID\",\"quantity\":10,\"operator\":\"admin\",\"remark\":\"API测试出库\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-012: 备件出库返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_spare_part_out_stock_insufficient() {
|
||||||
|
echo "--- TC-OPS-013: 备件出库-库存不足 ---"
|
||||||
|
if [ -z "$SPARE_PART_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-013 (无备件ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/ops/spare-parts/out-stock" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"sparePartId\":\"$SPARE_PART_ID\",\"quantity\":99999,\"operator\":\"admin\",\"remark\":\"库存不足测试\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "400" "TC-OPS-013: 库存不足出库返回400"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_low_stock_warning() {
|
||||||
|
echo "--- TC-OPS-014: 低库存预警 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/ops/spare-parts/low-stock?projectId=$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-014: 低库存预警返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_energy_consumption() {
|
||||||
|
echo "--- TC-OPS-015: 能耗抄表录入 ---"
|
||||||
|
local meter_response=$(curl -s -X POST "$BASE_URL/ops/energy/meters" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"meterName\":\"测试计量点-$$\",\"energyType\":\"LIGHTING\",\"unitPrice\":0.85}")
|
||||||
|
local meter_id=$(echo "$meter_response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
if [ -z "$meter_id" ]; then
|
||||||
|
echo " SKIP: TC-OPS-015 (无法创建计量点)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/ops/energy/consumption" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"meterId\":\"$meter_id\",\"currentReading\":100,\"recordedBy\":\"admin\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "201" "TC-OPS-015: 能耗抄表录入返回201"
|
||||||
|
ENERGY_METER_ID="$meter_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_energy_decreasing_reading() {
|
||||||
|
echo "--- TC-OPS-016: 能耗抄表-读数递减 ---"
|
||||||
|
if [ -z "$ENERGY_METER_ID" ]; then
|
||||||
|
echo " SKIP: TC-OPS-016 (无计量点ID)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/ops/energy/consumption" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID" \
|
||||||
|
-d "{\"meterId\":\"$ENERGY_METER_ID\",\"currentReading\":50,\"recordedBy\":\"admin\"}")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "400" "TC-OPS-016: 读数递减返回400"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_maintenance_plan() {
|
||||||
|
echo "--- TC-OPS-017: 维保计划管理 ---"
|
||||||
|
local response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/wo/maintenance-plans?projectId=$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "X-Project-ID: $PROJECT_ID")
|
||||||
|
local status=$(echo "$response" | tail -n 1)
|
||||||
|
assert_eq "$status" "200" "TC-OPS-017: 查询维保计划返回200"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "=== 清理测试数据 ==="
|
||||||
|
if [ -n "$PROJECT_ID" ]; then
|
||||||
|
curl -s -X DELETE "$BASE_URL/mdm/projects/$PROJECT_ID" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Ops Domain API Test Summary"
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Total: $TOTAL_COUNT"
|
||||||
|
echo " Passed: $PASS_COUNT"
|
||||||
|
echo " Failed: $FAIL_COUNT"
|
||||||
|
if [ "$TOTAL_COUNT" -gt 0 ]; then
|
||||||
|
local rate=$(echo "scale=1; $PASS_COUNT * 100 / $TOTAL_COUNT" | bc)
|
||||||
|
echo " Rate: ${rate}%"
|
||||||
|
fi
|
||||||
|
echo "=========================================="
|
||||||
|
if [ "$FAIL_COUNT" -gt 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
WORK_ORDER_ID=""
|
||||||
|
MAINT_TASK_ID=""
|
||||||
|
SPARE_PART_ID=""
|
||||||
|
ENERGY_METER_ID=""
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Ether PMS - Ops Domain API Tests"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
login
|
||||||
|
setup_project
|
||||||
|
|
||||||
|
test_create_work_order
|
||||||
|
test_query_work_orders
|
||||||
|
test_assign_work_order
|
||||||
|
test_start_work_order
|
||||||
|
test_complete_work_order
|
||||||
|
test_verify_work_order
|
||||||
|
test_cancel_completed_work_order
|
||||||
|
test_work_order_statistics
|
||||||
|
test_create_maintenance_task
|
||||||
|
test_spare_part_crud
|
||||||
|
test_spare_part_in_stock
|
||||||
|
test_spare_part_out_stock
|
||||||
|
test_spare_part_out_stock_insufficient
|
||||||
|
test_low_stock_warning
|
||||||
|
test_energy_consumption
|
||||||
|
test_energy_decreasing_reading
|
||||||
|
test_maintenance_plan
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
print_summary
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
MODULES="auth space asset ops finance"
|
||||||
|
TOTAL_PASS=0
|
||||||
|
TOTAL_FAIL=0
|
||||||
|
TOTAL_COUNT=0
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Ether PMS - All API Tests"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for module in $MODULES; do
|
||||||
|
script="$SCRIPT_DIR/$module/test-$module.sh"
|
||||||
|
if [ -f "$script" ]; then
|
||||||
|
echo ">>> Running $module API tests..."
|
||||||
|
echo "----------------------------------------"
|
||||||
|
bash "$script"
|
||||||
|
exit_code=$?
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo ">>> SKIP: $module (test script not found: $script)"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " All API Tests Complete"
|
||||||
|
echo "=========================================="
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: ./run-module.sh <module>"
|
||||||
|
echo ""
|
||||||
|
echo "Available modules:"
|
||||||
|
echo " auth - 身份与权限域"
|
||||||
|
echo " space - 空间与项目域"
|
||||||
|
echo " asset - 设备与资产域"
|
||||||
|
echo " ops - 运营与服务域"
|
||||||
|
echo " finance - 财务与收费域"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODULE="$1"
|
||||||
|
SCRIPT="$SCRIPT_DIR/$MODULE/test-$MODULE.sh"
|
||||||
|
|
||||||
|
if [ ! -f "$SCRIPT" ]; then
|
||||||
|
echo "ERROR: Test script not found: $SCRIPT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ">>> Running $MODULE API tests..."
|
||||||
|
echo "----------------------------------------"
|
||||||
|
bash "$SCRIPT"
|
||||||
|
exit $?
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Page, Locator } from '@playwright/test';
|
||||||
|
|
||||||
|
export class DashboardPage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly projectSelector: Locator;
|
||||||
|
readonly sidebarMenu: Locator;
|
||||||
|
readonly logoutButton: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page;
|
||||||
|
this.projectSelector = page.locator('.project-selector, [data-testid="project-selector"]');
|
||||||
|
this.sidebarMenu = page.locator('.ant-menu, .sidebar-menu');
|
||||||
|
this.logoutButton = page.locator('button:has-text("退出"), [data-testid="logout"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.page.goto('/');
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectProject(projectName: string) {
|
||||||
|
await this.projectSelector.click();
|
||||||
|
await this.page.locator(`.ant-select-item:has-text("${projectName}")`).click();
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigateTo(menuItem: string) {
|
||||||
|
await this.sidebarMenu.locator(`text=${menuItem}`).click();
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
await this.logoutButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async isDashboardVisible(): Promise<boolean> {
|
||||||
|
return await this.page.locator('.dashboard, [data-testid="dashboard"]').isVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Page, Locator } from '@playwright/test';
|
||||||
|
|
||||||
|
export class LoginPage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly usernameInput: Locator;
|
||||||
|
readonly passwordInput: Locator;
|
||||||
|
readonly loginButton: Locator;
|
||||||
|
readonly errorMessage: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page;
|
||||||
|
this.usernameInput = page.locator('input[placeholder*="用户名"], input[id="username"], input[name="username"]');
|
||||||
|
this.passwordInput = page.locator('input[placeholder*="密码"], input[id="password"], input[name="password"]');
|
||||||
|
this.loginButton = page.locator('button[type="submit"], button:has-text("登录")');
|
||||||
|
this.errorMessage = page.locator('.ant-message-error, .ant-alert-error, .error-message');
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.page.goto('/auth/login');
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(username: string, password: string) {
|
||||||
|
await this.usernameInput.fill(username);
|
||||||
|
await this.passwordInput.fill(password);
|
||||||
|
await this.loginButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginAsAdmin() {
|
||||||
|
await this.login('admin', 'admin123');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getErrorMessage(): Promise<string> {
|
||||||
|
await this.errorMessage.waitFor({ state: 'visible', timeout: 5000 });
|
||||||
|
return await this.errorMessage.textContent() || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './specs',
|
||||||
|
fullyParallel: false,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: 1,
|
||||||
|
reporter: [
|
||||||
|
['html', { outputFolder: '../reports/e2e-report' }],
|
||||||
|
['list'],
|
||||||
|
],
|
||||||
|
timeout: 30000,
|
||||||
|
expect: {
|
||||||
|
timeout: 10000,
|
||||||
|
},
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:5175',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
video: 'retain-on-failure',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'auth',
|
||||||
|
testDir: './specs/auth',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
testDir: './specs/space',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'asset',
|
||||||
|
testDir: './specs/asset',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ops',
|
||||||
|
testDir: './specs/ops',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finance',
|
||||||
|
testDir: './specs/finance',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cross-domain',
|
||||||
|
testDir: './specs/cross-domain',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: {
|
||||||
|
command: 'cd ../../../ether-admin && npm run dev',
|
||||||
|
url: 'http://localhost:5175',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 60000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Ether PMS - All E2E Tests"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "playwright.config.ts" ]; then
|
||||||
|
echo "ERROR: playwright.config.ts not found in $SCRIPT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
npm install
|
||||||
|
npx playwright install chromium
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running all E2E tests..."
|
||||||
|
npx playwright test --reporter=html,list
|
||||||
|
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " E2E Tests Complete (exit code: $EXIT_CODE)"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
exit $EXIT_CODE
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: ./run-module.sh <module>"
|
||||||
|
echo ""
|
||||||
|
echo "Available modules:"
|
||||||
|
echo " auth - 认证授权域"
|
||||||
|
echo " space - 空间与项目域"
|
||||||
|
echo " asset - 设备与资产域"
|
||||||
|
echo " ops - 运营与服务域"
|
||||||
|
echo " finance - 财务与收费域"
|
||||||
|
echo " cross-domain - 跨域流程"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODULE="$1"
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "playwright.config.ts" ]; then
|
||||||
|
echo "ERROR: playwright.config.ts not found in $SCRIPT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
npm install
|
||||||
|
npx playwright install chromium
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running $MODULE E2E tests..."
|
||||||
|
npx playwright test --project="$MODULE" --reporter=list
|
||||||
|
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " $MODULE E2E Tests Complete (exit code: $EXIT_CODE)"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
exit $EXIT_CODE
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { LoginPage } from '../../pages/LoginPage';
|
||||||
|
import { DashboardPage } from '../../pages/DashboardPage';
|
||||||
|
|
||||||
|
test.describe('登录流程', () => {
|
||||||
|
let loginPage: LoginPage;
|
||||||
|
let dashboardPage: DashboardPage;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
loginPage = new LoginPage(page);
|
||||||
|
dashboardPage = new DashboardPage(page);
|
||||||
|
await loginPage.goto();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-FE-001: 正常登录成功跳转至仪表盘', async ({ page }) => {
|
||||||
|
await loginPage.loginAsAdmin();
|
||||||
|
await page.waitForURL('**/dashboard**', { timeout: 10000 });
|
||||||
|
await expect(dashboardPage.isDashboardVisible()).resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-FE-002: 错误密码显示错误提示', async ({ page }) => {
|
||||||
|
await loginPage.login('admin', 'wrongpassword');
|
||||||
|
const errorMsg = await loginPage.getErrorMessage();
|
||||||
|
expect(errorMsg.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-FE-003: 空用户名无法提交', async ({ page }) => {
|
||||||
|
await loginPage.passwordInput.fill('admin123');
|
||||||
|
await loginPage.loginButton.click();
|
||||||
|
await expect(page.locator('.ant-form-item-explain-error')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-FE-004: 登出后返回登录页', async ({ page }) => {
|
||||||
|
await loginPage.loginAsAdmin();
|
||||||
|
await page.waitForURL('**/dashboard**', { timeout: 10000 });
|
||||||
|
await dashboardPage.logout();
|
||||||
|
await page.waitForURL('**/login**', { timeout: 10000 });
|
||||||
|
await expect(loginPage.usernameInput).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-FE-005: Token过期后自动跳转登录页', async ({ page }) => {
|
||||||
|
await loginPage.loginAsAdmin();
|
||||||
|
await page.waitForURL('**/dashboard**', { timeout: 10000 });
|
||||||
|
await page.evaluate(() => localStorage.removeItem('token'));
|
||||||
|
await page.reload();
|
||||||
|
await page.waitForURL('**/login**', { timeout: 10000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { LoginPage } from '../../pages/LoginPage';
|
||||||
|
import { DashboardPage } from '../../pages/DashboardPage';
|
||||||
|
|
||||||
|
test.describe('业主报修全流程', () => {
|
||||||
|
let loginPage: LoginPage;
|
||||||
|
let dashboardPage: DashboardPage;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
loginPage = new LoginPage(page);
|
||||||
|
dashboardPage = new DashboardPage(page);
|
||||||
|
await loginPage.goto();
|
||||||
|
await loginPage.loginAsAdmin();
|
||||||
|
await page.waitForURL('**/dashboard**', { timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-CROSS-001: 业主报修完整流程 - 从创建工单到验收', async ({ page }) => {
|
||||||
|
await dashboardPage.navigateTo('工单管理');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const createButton = page.locator('button:has-text("新建"), button:has-text("创建")');
|
||||||
|
if (await createButton.isVisible()) {
|
||||||
|
await createButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const titleInput = page.locator('input[id="title"], input[placeholder*="标题"]');
|
||||||
|
if (await titleInput.isVisible()) {
|
||||||
|
await titleInput.fill('E2E测试-业主报修工单');
|
||||||
|
}
|
||||||
|
|
||||||
|
const descInput = page.locator('textarea[id="description"], textarea[placeholder*="描述"]');
|
||||||
|
if (await descInput.isVisible()) {
|
||||||
|
await descInput.fill('E2E自动化测试:业主报修全流程验证');
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitButton = page.locator('button:has-text("确定"), button:has-text("提交")');
|
||||||
|
if (await submitButton.isVisible()) {
|
||||||
|
await submitButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workOrderRow = page.locator('tr:has-text("E2E测试-业主报修工单"), .ant-card:has-text("E2E测试")');
|
||||||
|
if (await workOrderRow.isVisible()) {
|
||||||
|
await expect(workOrderRow).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-CROSS-001-派单: 工单派单流程', async ({ page }) => {
|
||||||
|
await dashboardPage.navigateTo('工单管理');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const pendingOrder = page.locator('tr:has-text("待分配"), .ant-tag:has-text("待分配")').first();
|
||||||
|
if (await pendingOrder.isVisible()) {
|
||||||
|
await pendingOrder.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const assignButton = page.locator('button:has-text("派单")');
|
||||||
|
if (await assignButton.isVisible()) {
|
||||||
|
await assignButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-CROSS-001-执行完成: 工单执行与完成', async ({ page }) => {
|
||||||
|
await dashboardPage.navigateTo('工单管理');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const assignedOrder = page.locator('tr:has-text("已派单"), .ant-tag:has-text("已派单")').first();
|
||||||
|
if (await assignedOrder.isVisible()) {
|
||||||
|
await assignedOrder.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const startButton = page.locator('button:has-text("开始执行")');
|
||||||
|
if (await startButton.isVisible()) {
|
||||||
|
await startButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
const completeButton = page.locator('button:has-text("完成")');
|
||||||
|
if (await completeButton.isVisible()) {
|
||||||
|
await completeButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-CROSS-001-验收: 工单验收流程', async ({ page }) => {
|
||||||
|
await dashboardPage.navigateTo('工单管理');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const completedOrder = page.locator('tr:has-text("已完成"), .ant-tag:has-text("已完成")').first();
|
||||||
|
if (await completedOrder.isVisible()) {
|
||||||
|
await completedOrder.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const verifyButton = page.locator('button:has-text("验收")');
|
||||||
|
if (await verifyButton.isVisible()) {
|
||||||
|
await verifyButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
const ratingSelect = page.locator('.ant-rate, [data-testid="rating"]');
|
||||||
|
if (await ratingSelect.isVisible()) {
|
||||||
|
await ratingSelect.locator('.ant-rate-star').nth(4).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmButton = page.locator('button:has-text("确认"), button:has-text("提交")');
|
||||||
|
if (await confirmButton.isVisible()) {
|
||||||
|
await confirmButton.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
**领域编号**: 4.2
|
**领域编号**: 4.2
|
||||||
**微服务**: ether-ops
|
**微服务**: ether-ops
|
||||||
**最后更新**: 2026-02-10
|
**最后更新**: 2026-04-26
|
||||||
|
|
||||||
|
> **更新记录**:
|
||||||
|
> - 2026-04-26: 反向同步实际代码实现到文档。主要变更:工单状态机更新为PENDING→ASSIGNED→IN_PROGRESS→COMPLETED→VERIFIED/CANCELLED(标注TODO: 增加SUSPENDED/RETURNED);工单类型枚举更新(REPAIR/INSPECTION/SECURITY/CLEANING/PROPERTY/CONSULTATION);工单来源枚举更新(OWNER/MAINTENANCE/INSPECTION/FAULT/REGULATORY/MANUAL);新增实体(WorkOrderItem/MaintenancePlan/MaintenanceTask/InspectionTemplate/InspectionItem);新增字段(planId/triggerType/assignedVendor/laborCost/partsCost/totalCost/signature);字段拆分(resultDescription→result+faultCause+solution);新增业务规则(优先级自动判定/设备联动更新/维保任务双完成接口);API路径从/api/v1/ops改为/api/wo;标注TODO: 工单列表需改为分页查询。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -21,7 +24,12 @@
|
||||||
| 概念 | 说明 | 对应实体 |
|
| 概念 | 说明 | 对应实体 |
|
||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| **工单** | 综合业务单据,支持多种类型 | WorkOrder |
|
| **工单** | 综合业务单据,支持多种类型 | WorkOrder |
|
||||||
|
| **工单明细** | 工单下的处理项目清单 | WorkOrderItem |
|
||||||
| **工单流转** | 工单状态变更记录 | WorkOrderFlow |
|
| **工单流转** | 工单状态变更记录 | WorkOrderFlow |
|
||||||
|
| **维保计划** | 定期保养计划 | MaintenancePlan |
|
||||||
|
| **维保任务** | 维保执行工单 | MaintenanceTask |
|
||||||
|
| **巡检模板** | 巡检检查项模板 | InspectionTemplate |
|
||||||
|
| **巡检项目** | 巡检模板中的检查项 | InspectionItem |
|
||||||
| **通知渠道** | 消息发送通道 | NotificationChannel |
|
| **通知渠道** | 消息发送通道 | NotificationChannel |
|
||||||
| **通知模板** | 消息内容模板 | NotificationTemplate |
|
| **通知模板** | 消息内容模板 | NotificationTemplate |
|
||||||
| **通知规则** | 触发条件和发送策略 | NotificationRule |
|
| **通知规则** | 触发条件和发送策略 | NotificationRule |
|
||||||
|
|
@ -47,10 +55,10 @@ public class WorkOrder {
|
||||||
private String orderNo; // 工单编号: WO2024021000001
|
private String orderNo; // 工单编号: WO2024021000001
|
||||||
|
|
||||||
// 工单类型
|
// 工单类型
|
||||||
private WorkOrderType orderType; // REPAIR/COMPLAINT/CLEANING/SECURITY/OTHER
|
private WorkOrderType orderType; // REPAIR/INSPECTION/SECURITY/CLEANING/PROPERTY/CONSULTATION
|
||||||
private WorkOrderStatus status; // 状态机
|
private WorkOrderStatus status; // 状态机
|
||||||
private WorkOrderPriority priority; // URGENT/HIGH/MEDIUM/LOW
|
private WorkOrderPriority priority; // URGENT/HIGH/MEDIUM/LOW
|
||||||
private WorkOrderSource source; // APP/PHONE/INSPECTION/IOT/SYSTEM
|
private WorkOrderSource source; // OWNER/MAINTENANCE/INSPECTION/FAULT/REGULATORY/MANUAL
|
||||||
|
|
||||||
// 基本信息
|
// 基本信息
|
||||||
private String title;
|
private String title;
|
||||||
|
|
@ -66,25 +74,35 @@ public class WorkOrder {
|
||||||
private UUID spaceNodeId; // 关联空间
|
private UUID spaceNodeId; // 关联空间
|
||||||
private UUID equipmentId; // 关联设备
|
private UUID equipmentId; // 关联设备
|
||||||
|
|
||||||
|
// 维保关联(新增)
|
||||||
|
private UUID planId; // 关联维保计划ID
|
||||||
|
private String triggerType; // 触发类型: MANUAL/AUTO/SCHEDULED
|
||||||
|
|
||||||
// 处理人信息
|
// 处理人信息
|
||||||
private UUID assigneeId;
|
private UUID assigneeId;
|
||||||
private String assigneeName;
|
private String assigneeName;
|
||||||
|
private UUID assignedVendor; // 指派供应商ID(新增)
|
||||||
private LocalDateTime assignedAt;
|
private LocalDateTime assignedAt;
|
||||||
private LocalDateTime acceptedAt;
|
private LocalDateTime acceptedAt;
|
||||||
private LocalDateTime startedAt;
|
private LocalDateTime startedAt;
|
||||||
private LocalDateTime completedAt;
|
private LocalDateTime completedAt;
|
||||||
private LocalDateTime closedAt;
|
private LocalDateTime closedAt;
|
||||||
|
|
||||||
// 费用
|
// 费用(拆分细化)
|
||||||
private BigDecimal actualCost;
|
private BigDecimal laborCost; // 人工费(新增)
|
||||||
private BigDecimal materialCost;
|
private BigDecimal partsCost; // 材料费(新增)
|
||||||
private BigDecimal laborCost;
|
private BigDecimal totalCost; // 总费用(新增,替代原actualCost)
|
||||||
|
|
||||||
// 结果
|
// 结果(拆分:原resultDescription → result + faultCause + solution)
|
||||||
private String resultDescription;
|
private String result; // 处理结果
|
||||||
|
private String faultCause; // 故障原因
|
||||||
|
private String solution; // 解决方案
|
||||||
private Integer satisfactionScore;
|
private Integer satisfactionScore;
|
||||||
private String satisfactionComment;
|
private String satisfactionComment;
|
||||||
|
|
||||||
|
// 签名确认(新增)
|
||||||
|
private String signature; // 签名图片URL
|
||||||
|
|
||||||
// 附件
|
// 附件
|
||||||
private String images;
|
private String images;
|
||||||
private String attachments;
|
private String attachments;
|
||||||
|
|
@ -99,10 +117,38 @@ public class WorkOrder {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**工单类型枚举(更新)**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum WorkOrderType {
|
||||||
|
REPAIR("报修"),
|
||||||
|
INSPECTION("巡检"),
|
||||||
|
SECURITY("安全"),
|
||||||
|
CLEANING("保洁"),
|
||||||
|
PROPERTY("物业"),
|
||||||
|
CONSULTATION("咨询");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**工单来源枚举(更新)**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum WorkOrderSource {
|
||||||
|
OWNER("业主报修"),
|
||||||
|
MAINTENANCE("维保触发"),
|
||||||
|
INSPECTION("巡检发现"),
|
||||||
|
FAULT("设备故障"),
|
||||||
|
REGULATORY("监管要求"),
|
||||||
|
MANUAL("手动创建");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**业务规则**:
|
**业务规则**:
|
||||||
- orderNo 自动生成,格式: WO + yyyyMMdd + 5位序号
|
- orderNo 自动生成,格式: WO + yyyyMMdd + 5位序号
|
||||||
- 状态流转必须通过合法的业务操作
|
- 状态流转必须通过合法的业务操作
|
||||||
- 关闭工单时必须填写处理结果
|
- 关闭工单时必须填写处理结果
|
||||||
|
- **优先级自动判定**(新增):根据工单类型和来源自动设置优先级(如安全类默认HIGH,设备故障默认HIGH)
|
||||||
|
- **设备联动更新**(新增):工单完成时自动更新关联设备状态
|
||||||
|
|
||||||
#### WorkOrderFlow(工单流转记录)
|
#### WorkOrderFlow(工单流转记录)
|
||||||
|
|
||||||
|
|
@ -118,7 +164,7 @@ public class WorkOrderFlow {
|
||||||
// 流转信息
|
// 流转信息
|
||||||
private WorkOrderStatus fromStatus;
|
private WorkOrderStatus fromStatus;
|
||||||
private WorkOrderStatus toStatus;
|
private WorkOrderStatus toStatus;
|
||||||
private String action; // 操作: ASSIGN/ACCEPT/START/COMPLETE/CLOSE
|
private String action; // 操作: ASSIGN/START/COMPLETE/VERIFY/CANCEL
|
||||||
|
|
||||||
// 操作人
|
// 操作人
|
||||||
private UUID operatorId;
|
private UUID operatorId;
|
||||||
|
|
@ -133,24 +179,164 @@ public class WorkOrderFlow {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### WorkOrderItem(工单明细,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_work_order_item")
|
||||||
|
@Data
|
||||||
|
public class WorkOrderItem {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID workOrderId;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String itemName; // 明细名称
|
||||||
|
private String itemType; // 明细类型
|
||||||
|
private String description; // 明细描述
|
||||||
|
private Integer quantity; // 数量
|
||||||
|
private BigDecimal unitPrice; // 单价
|
||||||
|
private BigDecimal amount; // 金额
|
||||||
|
private String remark; // 备注
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MaintenancePlan(维保计划,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_maintenance_plan")
|
||||||
|
@Data
|
||||||
|
public class MaintenancePlan {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
private UUID equipmentId;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private MaintenanceType type; // DAILY/WEEKLY/MONTHLY/QUARTERLY/YEARLY
|
||||||
|
private Integer cycleDays;
|
||||||
|
private String cronExpression;
|
||||||
|
private String content;
|
||||||
|
private String checkItems; // JSON
|
||||||
|
|
||||||
|
private UUID maintainerId;
|
||||||
|
private String maintainerName;
|
||||||
|
private Integer remindDays;
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MaintenanceTask(维保任务,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_maintenance_task")
|
||||||
|
@Data
|
||||||
|
public class MaintenanceTask {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID planId;
|
||||||
|
|
||||||
|
private String taskCode;
|
||||||
|
private MaintenanceTaskStatus status; // PENDING/IN_PROGRESS/COMPLETED/CANCELLED
|
||||||
|
private LocalDate maintenanceDate;
|
||||||
|
private MaintenanceType type;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
private UUID maintainerId;
|
||||||
|
private String maintainerName;
|
||||||
|
private MaintenanceResult result;
|
||||||
|
private String remark;
|
||||||
|
private String images;
|
||||||
|
private String checkResults; // JSON
|
||||||
|
|
||||||
|
private BigDecimal laborCost;
|
||||||
|
private BigDecimal partsCost;
|
||||||
|
private BigDecimal totalCost;
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
private LocalDate nextMaintenanceDate;
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MaintenanceTaskStatus {
|
||||||
|
PENDING, IN_PROGRESS, COMPLETED, CANCELLED;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **维保任务双完成接口**(新增业务规则): 维保任务提供两个完成接口:
|
||||||
|
> 1. `POST /api/wo/maintenance-tasks/{id}/complete` - 维保人员完成
|
||||||
|
> 2. `POST /api/wo/maintenance-tasks/{id}/verify` - 主管验证确认
|
||||||
|
|
||||||
|
#### InspectionTemplate(巡检模板,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_inspection_template")
|
||||||
|
@Data
|
||||||
|
public class InspectionTemplate {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private String category; // 模板分类
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "templateId", fetch = FetchType.LAZY)
|
||||||
|
private List<InspectionItem> items;
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### InspectionItem(巡检项目,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_inspection_item")
|
||||||
|
@Data
|
||||||
|
public class InspectionItem {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID templateId;
|
||||||
|
|
||||||
|
private String itemName; // 检查项名称
|
||||||
|
private String checkStandard; // 检查标准
|
||||||
|
private String checkMethod; // 检查方法
|
||||||
|
private Integer sortOrder; // 排序
|
||||||
|
private Boolean required; // 是否必检
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 2.2 状态机设计
|
### 2.2 状态机设计
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────┐
|
┌─────────────┐
|
||||||
│ CREATED │ ← 创建工单
|
│ PENDING │ ← 创建工单
|
||||||
│ (已创建) │
|
│ (待处理) │
|
||||||
└──────┬──────┘
|
└──────┬──────┘
|
||||||
│ 分配
|
│ 分配
|
||||||
▼
|
▼
|
||||||
┌─────────────┐
|
┌─────────────┐
|
||||||
│ ASSIGNED │ ← 分配给处理人
|
│ ASSIGNED │ ← 分配给处理人
|
||||||
│ (已分配) │
|
│ (已分配) │
|
||||||
└──────┬──────┘
|
|
||||||
│ 接单
|
|
||||||
▼
|
|
||||||
┌─────────────┐
|
|
||||||
│ ACCEPTED │ ← 处理人接单
|
|
||||||
│ (已接单) │
|
|
||||||
└──────┬──────┘
|
└──────┬──────┘
|
||||||
│ 开始处理
|
│ 开始处理
|
||||||
▼
|
▼
|
||||||
|
|
@ -164,30 +350,30 @@ public class WorkOrderFlow {
|
||||||
│ COMPLETED │ ← 处理完成
|
│ COMPLETED │ ← 处理完成
|
||||||
│ (已完成) │
|
│ (已完成) │
|
||||||
└──────┬──────┘
|
└──────┬──────┘
|
||||||
│ 关闭
|
│ 验证
|
||||||
▼
|
▼
|
||||||
┌─────────────┐
|
┌─────────────┐
|
||||||
│ CLOSED │ ← 工单关闭
|
│ VERIFIED │ ← 验证通过
|
||||||
│ (已关闭) │
|
│ (已验证) │
|
||||||
└─────────────┘
|
└─────────────┘
|
||||||
|
|
||||||
特殊状态:
|
特殊状态:
|
||||||
- SUSPENDED (已挂起): 任意状态可转入,可恢复
|
- CANCELLED (已取消): PENDING/ASSIGNED状态可转入
|
||||||
- RETURNED (已退回): ASSIGNED状态可转入,需重新分配
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **TODO**: 当前状态机缺少 SUSPENDED(已挂起) 和 RETURNED(已退回) 状态,需后续补充:
|
||||||
|
> - SUSPENDED: 任意状态可转入,可恢复到原状态
|
||||||
|
> - RETURNED: ASSIGNED状态可转入,需重新分配
|
||||||
|
|
||||||
**状态流转规则**:
|
**状态流转规则**:
|
||||||
|
|
||||||
| 当前状态 | 允许操作 | 下一状态 | 权限 |
|
| 当前状态 | 允许操作 | 下一状态 | 权限 |
|
||||||
|---------|---------|---------|------|
|
|---------|---------|---------|------|
|
||||||
| CREATED | 分配 | ASSIGNED | 管理员/调度员 |
|
| PENDING | 分配 | ASSIGNED | 管理员/调度员 |
|
||||||
| ASSIGNED | 接单 | ACCEPTED | 被指派人 |
|
| ASSIGNED | 开始 | IN_PROGRESS | 被指派人 |
|
||||||
| ASSIGNED | 退回 | RETURNED | 被指派人 |
|
|
||||||
| ACCEPTED | 开始 | IN_PROGRESS | 被指派人 |
|
|
||||||
| IN_PROGRESS | 完成 | COMPLETED | 被指派人 |
|
| IN_PROGRESS | 完成 | COMPLETED | 被指派人 |
|
||||||
| COMPLETED | 关闭 | CLOSED | 管理员/创建人 |
|
| COMPLETED | 验证 | VERIFIED | 管理员/创建人 |
|
||||||
| * | 挂起 | SUSPENDED | 管理员 |
|
| PENDING/ASSIGNED | 取消 | CANCELLED | 管理员 |
|
||||||
| SUSPENDED | 恢复 | 原状态 | 管理员 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -443,9 +629,11 @@ public class WorkOrderStatisticsService {
|
||||||
|
|
||||||
### 5.1 WorkOrder API
|
### 5.1 WorkOrder API
|
||||||
|
|
||||||
|
> **注意**: API路径已从 `/api/v1/ops` 改为 `/api/wo`。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/ops/work-orders")
|
@RequestMapping("/api/wo/work-orders")
|
||||||
@Tag(name = "工单管理")
|
@Tag(name = "工单管理")
|
||||||
public class WorkOrderController {
|
public class WorkOrderController {
|
||||||
|
|
||||||
|
|
@ -458,8 +646,8 @@ public class WorkOrderController {
|
||||||
public Result<WorkOrderVO> getById(@PathVariable UUID id);
|
public Result<WorkOrderVO> getById(@PathVariable UUID id);
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "分页查询工单")
|
@Operation(summary = "查询工单列表")
|
||||||
public Result<Page<WorkOrderVO>> page(WorkOrderQueryRequest request);
|
public Result<List<WorkOrderVO>> list(WorkOrderQueryRequest request);
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@Operation(summary = "更新工单")
|
@Operation(summary = "更新工单")
|
||||||
|
|
@ -476,10 +664,6 @@ public class WorkOrderController {
|
||||||
public Result<WorkOrderVO> assign(@PathVariable UUID id,
|
public Result<WorkOrderVO> assign(@PathVariable UUID id,
|
||||||
@RequestBody @Valid WorkOrderAssignRequest request);
|
@RequestBody @Valid WorkOrderAssignRequest request);
|
||||||
|
|
||||||
@PostMapping("/{id}/accept")
|
|
||||||
@Operation(summary = "接单")
|
|
||||||
public Result<WorkOrderVO> accept(@PathVariable UUID id);
|
|
||||||
|
|
||||||
@PostMapping("/{id}/start")
|
@PostMapping("/{id}/start")
|
||||||
@Operation(summary = "开始处理")
|
@Operation(summary = "开始处理")
|
||||||
public Result<WorkOrderVO> start(@PathVariable UUID id);
|
public Result<WorkOrderVO> start(@PathVariable UUID id);
|
||||||
|
|
@ -489,19 +673,15 @@ public class WorkOrderController {
|
||||||
public Result<WorkOrderVO> complete(@PathVariable UUID id,
|
public Result<WorkOrderVO> complete(@PathVariable UUID id,
|
||||||
@RequestBody @Valid WorkOrderCompleteRequest request);
|
@RequestBody @Valid WorkOrderCompleteRequest request);
|
||||||
|
|
||||||
@PostMapping("/{id}/close")
|
@PostMapping("/{id}/verify")
|
||||||
@Operation(summary = "关闭工单")
|
@Operation(summary = "验证工单")
|
||||||
public Result<WorkOrderVO> close(@PathVariable UUID id,
|
public Result<WorkOrderVO> verify(@PathVariable UUID id,
|
||||||
@RequestBody @Valid WorkOrderCloseRequest request);
|
@RequestBody @Valid WorkOrderVerifyRequest request);
|
||||||
|
|
||||||
@PostMapping("/{id}/suspend")
|
@PostMapping("/{id}/cancel")
|
||||||
@Operation(summary = "挂起工单")
|
@Operation(summary = "取消工单")
|
||||||
public Result<WorkOrderVO> suspend(@PathVariable UUID id,
|
public Result<WorkOrderVO> cancel(@PathVariable UUID id,
|
||||||
@RequestBody WorkOrderSuspendRequest request);
|
@RequestBody WorkOrderCancelRequest request);
|
||||||
|
|
||||||
@PostMapping("/{id}/resume")
|
|
||||||
@Operation(summary = "恢复工单")
|
|
||||||
public Result<WorkOrderVO> resume(@PathVariable UUID id);
|
|
||||||
|
|
||||||
// 流转记录
|
// 流转记录
|
||||||
@GetMapping("/{id}/flows")
|
@GetMapping("/{id}/flows")
|
||||||
|
|
@ -510,11 +690,13 @@ public class WorkOrderController {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **TODO**: 工单列表接口(GET /api/wo/work-orders)当前返回 List,需改为分页查询(返回 Page<WorkOrderVO>)。
|
||||||
|
|
||||||
### 5.2 Notification API
|
### 5.2 Notification API
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/ops/notifications")
|
@RequestMapping("/api/wo/notifications")
|
||||||
@Tag(name = "消息通知")
|
@Tag(name = "消息通知")
|
||||||
public class NotificationController {
|
public class NotificationController {
|
||||||
|
|
||||||
|
|
@ -573,7 +755,7 @@ public class NotificationController {
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/ops/statistics")
|
@RequestMapping("/api/wo/statistics")
|
||||||
@Tag(name = "工单统计")
|
@Tag(name = "工单统计")
|
||||||
public class WorkOrderStatisticsController {
|
public class WorkOrderStatisticsController {
|
||||||
|
|
||||||
|
|
@ -612,34 +794,48 @@ public class WorkOrderStatisticsController {
|
||||||
|
|
||||||
| 功能模块 | 实现状态 | 备注 |
|
| 功能模块 | 实现状态 | 备注 |
|
||||||
|---------|---------|------|
|
|---------|---------|------|
|
||||||
| WorkOrder | 🟢 已实现 | 完整状态机、CRUD |
|
| WorkOrder | 🟢 已实现 | 完整状态机(PENDING→ASSIGNED→IN_PROGRESS→COMPLETED→VERIFIED/CANCELLED) |
|
||||||
|
| WorkOrderItem | 🟢 已实现 | 工单明细 |
|
||||||
| WorkOrderFlow | 🟢 已实现 | 自动记录流转 |
|
| WorkOrderFlow | 🟢 已实现 | 自动记录流转 |
|
||||||
|
| MaintenancePlan | 🟢 已实现 | 维保计划 |
|
||||||
|
| MaintenanceTask | 🟢 已实现 | 维保任务,双完成接口 |
|
||||||
|
| InspectionTemplate | 🟢 已实现 | 巡检模板 |
|
||||||
|
| InspectionItem | 🟢 已实现 | 巡检项目 |
|
||||||
| NotificationChannel | 🟢 已实现 | 基础CRUD |
|
| NotificationChannel | 🟢 已实现 | 基础CRUD |
|
||||||
| NotificationTemplate | 🟢 已实现 | 基础CRUD |
|
| NotificationTemplate | 🟢 已实现 | 基础CRUD |
|
||||||
| NotificationRule | 🟢 已实现 | 基础CRUD |
|
| NotificationRule | 🟢 已实现 | 基础CRUD |
|
||||||
| NotificationHistory | 🟢 已实现 | 基础CRUD |
|
| NotificationHistory | 🟢 已实现 | 基础CRUD |
|
||||||
| 工单与通知集成 | 🟢 已实现 | 事件驱动 |
|
| 工单与通知集成 | 🟢 已实现 | 事件驱动 |
|
||||||
| 工单统计 | 🟢 已实现 | 多维度统计 |
|
| 工单统计 | 🟢 已实现 | 多维度统计 |
|
||||||
|
| 优先级自动判定 | 🟢 已实现 | 根据类型/来源自动设置 |
|
||||||
|
| 设备联动更新 | 🟢 已实现 | 工单完成时更新设备状态 |
|
||||||
| SLA监控 | 🔴 未实现 | 超时预警 |
|
| SLA监控 | 🔴 未实现 | 超时预警 |
|
||||||
| 智能派单 | 🔴 未实现 | 自动分配 |
|
| 智能派单 | 🔴 未实现 | 自动分配 |
|
||||||
|
| SUSPENDED/RETURNED状态 | 🔴 未实现 | TODO: 需增加挂起和退回状态 |
|
||||||
|
| 工单分页查询 | 🔴 未实现 | TODO: 列表接口需改为分页查询 |
|
||||||
|
|
||||||
### 6.2 与设计方案的差异
|
### 6.2 与设计方案的差异
|
||||||
|
|
||||||
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|
||||||
|--------|----------|----------|----------|
|
|--------|----------|----------|----------|
|
||||||
| **工单状态机** | 完整状态流转 | 已实现所有状态 | ✅ 符合设计 |
|
| **工单状态机** | CREATED→ASSIGNED→ACCEPTED→IN_PROGRESS→COMPLETED→CLOSED | PENDING→ASSIGNED→IN_PROGRESS→COMPLETED→VERIFIED/CANCELLED | 已更新:简化流程,增加验证环节 |
|
||||||
| **通知渠道** | 多渠道支持 | 仅站内信实现 | ⚠️ 其他渠道待扩展 |
|
| **工单类型** | REPAIR/COMPLAINT/CLEANING/SECURITY/OTHER | REPAIR/INSPECTION/SECURITY/CLEANING/PROPERTY/CONSULTATION | 已更新:更贴合物业场景 |
|
||||||
| **工单统计** | 多维度分析 | 基本实现 | 🟡 可扩展更多维度 |
|
| **工单来源** | APP/PHONE/INSPECTION/IOT/SYSTEM | OWNER/MAINTENANCE/INSPECTION/FAULT/REGULATORY/MANUAL | 已更新:按业务角色区分 |
|
||||||
| **巡检归属** | ether-ops | ether-mdm | 🟡 领域边界模糊 |
|
| **结果字段** | resultDescription单字段 | result+faultCause+solution三字段 | 已拆分:更细粒度的结果记录 |
|
||||||
|
| **费用字段** | actualCost/materialCost/laborCost | laborCost/partsCost/totalCost | 已更新:更清晰的费用分类 |
|
||||||
|
| **通知渠道** | 多渠道支持 | 仅站内信实现 | 其他渠道待扩展 |
|
||||||
|
| **API路径** | /api/v1/ops | /api/wo | 已更新:路径简化 |
|
||||||
|
| **巡检归属** | ether-ops | ether-mdm | 领域边界模糊 |
|
||||||
|
|
||||||
### 6.3 待改进项
|
### 6.3 待改进项
|
||||||
|
|
||||||
| 优先级 | 改进项 | 说明 |
|
| 优先级 | 改进项 | 说明 |
|
||||||
|--------|--------|------|
|
|--------|--------|------|
|
||||||
| P2 | 实现SLA监控 | 工单超时预警和自动升级 |
|
| P0 | 工单分页查询 | 列表接口需改为分页查询 |
|
||||||
|
| P0 | 增加SUSPENDED/RETURNED状态 | 挂起和退回是常见业务需求 |
|
||||||
|
| P1 | 实现SLA监控 | 工单超时预警和自动升级 |
|
||||||
| P2 | 扩展通知渠道 | 短信、邮件、推送渠道 |
|
| P2 | 扩展通知渠道 | 短信、邮件、推送渠道 |
|
||||||
| P3 | 智能派单算法 | 基于负载、技能、位置的自动分配 |
|
| P3 | 智能派单算法 | 基于负载、技能、位置的自动分配 |
|
||||||
| P3 | 添加工单满意度 | 业主评价机制 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -652,7 +848,7 @@ CREATE TABLE ops_work_order (
|
||||||
project_id UUID NOT NULL,
|
project_id UUID NOT NULL,
|
||||||
order_no VARCHAR(32) NOT NULL,
|
order_no VARCHAR(32) NOT NULL,
|
||||||
order_type VARCHAR(20) NOT NULL,
|
order_type VARCHAR(20) NOT NULL,
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'CREATED',
|
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||||
priority VARCHAR(20) NOT NULL DEFAULT 'MEDIUM',
|
priority VARCHAR(20) NOT NULL DEFAULT 'MEDIUM',
|
||||||
source VARCHAR(20) NOT NULL,
|
source VARCHAR(20) NOT NULL,
|
||||||
title VARCHAR(200) NOT NULL,
|
title VARCHAR(200) NOT NULL,
|
||||||
|
|
@ -663,19 +859,25 @@ CREATE TABLE ops_work_order (
|
||||||
reporter_address VARCHAR(255),
|
reporter_address VARCHAR(255),
|
||||||
space_node_id UUID,
|
space_node_id UUID,
|
||||||
equipment_id UUID,
|
equipment_id UUID,
|
||||||
|
plan_id UUID,
|
||||||
|
trigger_type VARCHAR(20),
|
||||||
assignee_id UUID,
|
assignee_id UUID,
|
||||||
assignee_name VARCHAR(100),
|
assignee_name VARCHAR(100),
|
||||||
|
assigned_vendor UUID,
|
||||||
assigned_at TIMESTAMP,
|
assigned_at TIMESTAMP,
|
||||||
accepted_at TIMESTAMP,
|
accepted_at TIMESTAMP,
|
||||||
started_at TIMESTAMP,
|
started_at TIMESTAMP,
|
||||||
completed_at TIMESTAMP,
|
completed_at TIMESTAMP,
|
||||||
closed_at TIMESTAMP,
|
closed_at TIMESTAMP,
|
||||||
actual_cost NUMERIC(12,2),
|
|
||||||
material_cost NUMERIC(12,2),
|
|
||||||
labor_cost NUMERIC(12,2),
|
labor_cost NUMERIC(12,2),
|
||||||
result_description TEXT,
|
parts_cost NUMERIC(12,2),
|
||||||
|
total_cost NUMERIC(12,2),
|
||||||
|
result TEXT,
|
||||||
|
fault_cause TEXT,
|
||||||
|
solution TEXT,
|
||||||
satisfaction_score INTEGER,
|
satisfaction_score INTEGER,
|
||||||
satisfaction_comment VARCHAR(500),
|
satisfaction_comment VARCHAR(500),
|
||||||
|
signature VARCHAR(255),
|
||||||
images TEXT,
|
images TEXT,
|
||||||
attachments TEXT,
|
attachments TEXT,
|
||||||
attributes JSONB,
|
attributes JSONB,
|
||||||
|
|
@ -686,6 +888,22 @@ CREATE TABLE ops_work_order (
|
||||||
UNIQUE(project_id, order_no)
|
UNIQUE(project_id, order_no)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 工单明细表(新增)
|
||||||
|
CREATE TABLE ops_work_order_item (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
work_order_id UUID NOT NULL REFERENCES ops_work_order(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
item_name VARCHAR(200) NOT NULL,
|
||||||
|
item_type VARCHAR(50),
|
||||||
|
description TEXT,
|
||||||
|
quantity INTEGER,
|
||||||
|
unit_price NUMERIC(12,2),
|
||||||
|
amount NUMERIC(12,2),
|
||||||
|
remark VARCHAR(500),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
-- 工单流转表
|
-- 工单流转表
|
||||||
CREATE TABLE ops_work_order_flow (
|
CREATE TABLE ops_work_order_flow (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
|
|
@ -700,6 +918,76 @@ CREATE TABLE ops_work_order_flow (
|
||||||
images TEXT
|
images TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 维保计划表(新增)
|
||||||
|
CREATE TABLE ops_maintenance_plan (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
equipment_id UUID NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
type VARCHAR(20) NOT NULL,
|
||||||
|
cycle_days INTEGER,
|
||||||
|
cron_expression VARCHAR(50),
|
||||||
|
content TEXT,
|
||||||
|
check_items JSONB,
|
||||||
|
maintainer_id UUID,
|
||||||
|
maintainer_name VARCHAR(100),
|
||||||
|
remind_days INTEGER DEFAULT 3,
|
||||||
|
enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 维保任务表(新增)
|
||||||
|
CREATE TABLE ops_maintenance_task (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
equipment_id UUID NOT NULL,
|
||||||
|
plan_id UUID,
|
||||||
|
task_code VARCHAR(50),
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||||
|
maintenance_date DATE NOT NULL,
|
||||||
|
type VARCHAR(20) NOT NULL,
|
||||||
|
content TEXT,
|
||||||
|
maintainer_id UUID,
|
||||||
|
maintainer_name VARCHAR(100),
|
||||||
|
result VARCHAR(20),
|
||||||
|
remark VARCHAR(500),
|
||||||
|
images TEXT,
|
||||||
|
check_results JSONB,
|
||||||
|
labor_cost NUMERIC(12,2),
|
||||||
|
parts_cost NUMERIC(12,2),
|
||||||
|
total_cost NUMERIC(12,2),
|
||||||
|
signature VARCHAR(255),
|
||||||
|
next_maintenance_date DATE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 巡检模板表(新增)
|
||||||
|
CREATE TABLE ops_inspection_template (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
category VARCHAR(50),
|
||||||
|
enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 巡检项目表(新增)
|
||||||
|
CREATE TABLE ops_inspection_item (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
template_id UUID NOT NULL REFERENCES ops_inspection_template(id),
|
||||||
|
item_name VARCHAR(200) NOT NULL,
|
||||||
|
check_standard TEXT,
|
||||||
|
check_method VARCHAR(100),
|
||||||
|
sort_order INTEGER DEFAULT 0,
|
||||||
|
required BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
-- 通知渠道表
|
-- 通知渠道表
|
||||||
CREATE TABLE ops_notification_channel (
|
CREATE TABLE ops_notification_channel (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
|
|
@ -775,7 +1063,13 @@ CREATE INDEX idx_work_order_project ON ops_work_order(project_id);
|
||||||
CREATE INDEX idx_work_order_status ON ops_work_order(status);
|
CREATE INDEX idx_work_order_status ON ops_work_order(status);
|
||||||
CREATE INDEX idx_work_order_assignee ON ops_work_order(assignee_id);
|
CREATE INDEX idx_work_order_assignee ON ops_work_order(assignee_id);
|
||||||
CREATE INDEX idx_work_order_created ON ops_work_order(created_at);
|
CREATE INDEX idx_work_order_created ON ops_work_order(created_at);
|
||||||
|
CREATE INDEX idx_work_order_type ON ops_work_order(order_type);
|
||||||
CREATE INDEX idx_work_order_flow_order ON ops_work_order_flow(work_order_id);
|
CREATE INDEX idx_work_order_flow_order ON ops_work_order_flow(work_order_id);
|
||||||
|
CREATE INDEX idx_work_order_item_order ON ops_work_order_item(work_order_id);
|
||||||
|
CREATE INDEX idx_maintenance_plan_equipment ON ops_maintenance_plan(equipment_id);
|
||||||
|
CREATE INDEX idx_maintenance_task_equipment ON ops_maintenance_task(equipment_id);
|
||||||
|
CREATE INDEX idx_maintenance_task_status ON ops_maintenance_task(status);
|
||||||
|
CREATE INDEX idx_inspection_item_template ON ops_inspection_item(template_id);
|
||||||
CREATE INDEX idx_notification_history_receiver ON ops_notification_history(receiver_id);
|
CREATE INDEX idx_notification_history_receiver ON ops_notification_history(receiver_id);
|
||||||
CREATE INDEX idx_notification_history_status ON ops_notification_history(status);
|
CREATE INDEX idx_notification_history_status ON ops_notification_history(status);
|
||||||
```
|
```
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
# Ether 空间节点管理架构设计方案
|
# Ether 空间节点管理架构设计方案
|
||||||
|
|
||||||
**版本**: v2.0
|
**版本**: v2.1
|
||||||
**设计日期**: 2026-02-16
|
**设计日期**: 2026-02-16
|
||||||
**设计目标**: 重构空间节点管理,统一空间体系,支持地图服务对接
|
**设计目标**: 重构空间节点管理,统一空间体系,支持地图服务对接
|
||||||
|
**最后更新**: 2026-04-26
|
||||||
|
|
||||||
|
> **更新记录**:
|
||||||
|
> - 2026-04-26: 反向同步实际代码实现到文档。主要变更:树形查询改为按项目维度;类型查询统一为按类型查询;新增删除前检查和级联删除;新增楼层信息查询;新增软删除(isDeleted字段);NodeType增加PUBLIC_ROOM;标注TODO: SpaceNode需增加code字段和唯一约束;标注TODO: 设备扩展字段应统一到Equipment独立表;API路径去掉v1版本号。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -155,7 +159,7 @@ CREATE TABLE mdm_space_node (
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
created_by UUID,
|
created_by UUID,
|
||||||
updated_by UUID,
|
updated_by UUID,
|
||||||
is_deleted BOOLEAN DEFAULT FALSE,
|
is_deleted BOOLEAN DEFAULT FALSE, -- 软删除标记(实际代码使用 isDeleted 字段)
|
||||||
|
|
||||||
-- 约束
|
-- 约束
|
||||||
CONSTRAINT uk_space_node_project_code UNIQUE (project_id, code)
|
CONSTRAINT uk_space_node_project_code UNIQUE (project_id, code)
|
||||||
|
|
@ -193,6 +197,7 @@ public enum SpaceNodeType {
|
||||||
UNIT("单元", SpaceNodeCategory.BUILDING, 2),
|
UNIT("单元", SpaceNodeCategory.BUILDING, 2),
|
||||||
FLOOR("楼层", SpaceNodeCategory.BUILDING, 3),
|
FLOOR("楼层", SpaceNodeCategory.BUILDING, 3),
|
||||||
ROOM("房间", SpaceNodeCategory.BUILDING, 4),
|
ROOM("房间", SpaceNodeCategory.BUILDING, 4),
|
||||||
|
PUBLIC_ROOM("公共房间", SpaceNodeCategory.BUILDING, 4),
|
||||||
SHOP("商铺", SpaceNodeCategory.BUILDING, 2),
|
SHOP("商铺", SpaceNodeCategory.BUILDING, 2),
|
||||||
|
|
||||||
// 停车空间
|
// 停车空间
|
||||||
|
|
@ -311,57 +316,65 @@ GROUP BY p.id, p.code, p.name, p.updated_at;
|
||||||
### 3.1 RESTful API 规范
|
### 3.1 RESTful API 规范
|
||||||
|
|
||||||
```
|
```
|
||||||
基础路径: /api/v1/mdm/space-nodes
|
基础路径: /api/mdm/space-nodes
|
||||||
|
|
||||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ 空间节点 API 接口 │
|
│ 空间节点 API 接口 │
|
||||||
└─────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
1. 基础 CRUD
|
1. 基础 CRUD
|
||||||
POST /api/v1/mdm/space-nodes 创建空间节点
|
POST /api/mdm/space-nodes 创建空间节点
|
||||||
PUT /api/v1/mdm/space-nodes/{id} 更新空间节点
|
PUT /api/mdm/space-nodes/{id} 更新空间节点
|
||||||
DELETE /api/v1/mdm/space-nodes/{id} 删除空间节点
|
DELETE /api/mdm/space-nodes/{id} 删除空间节点(含删除前检查)
|
||||||
GET /api/v1/mdm/space-nodes/{id} 查询空间节点详情
|
GET /api/mdm/space-nodes/{id} 查询空间节点详情
|
||||||
|
|
||||||
2. 树形结构
|
2. 树形结构(按项目维度查询)
|
||||||
GET /api/v1/mdm/space-nodes/tree 获取完整树形结构
|
GET /api/mdm/space-nodes/tree?projectId={id} 获取项目空间树
|
||||||
GET /api/v1/mdm/space-nodes/roots 获取根节点列表
|
GET /api/mdm/space-nodes/roots?projectId={id} 获取项目根节点列表
|
||||||
GET /api/v1/mdm/space-nodes/{id}/children 获取子节点列表
|
GET /api/mdm/space-nodes/{id}/children 获取子节点列表
|
||||||
GET /api/v1/mdm/space-nodes/{id}/ancestors 获取祖先节点链
|
GET /api/mdm/space-nodes/{id}/ancestors 获取祖先节点链
|
||||||
GET /api/v1/mdm/space-nodes/{id}/descendants 获取所有子孙节点
|
GET /api/mdm/space-nodes/{id}/descendants 获取所有子孙节点
|
||||||
PUT /api/v1/mdm/space-nodes/{id}/move 移动节点到新父节点
|
PUT /api/mdm/space-nodes/{id}/move 移动节点到新父节点
|
||||||
|
|
||||||
3. 类型查询
|
3. 类型查询(统一为按类型查询)
|
||||||
GET /api/v1/mdm/space-nodes/buildings 获取楼栋列表
|
GET /api/mdm/space-nodes?type={nodeType} 按类型查询空间节点
|
||||||
GET /api/v1/mdm/space-nodes/rooms 获取房间列表
|
|
||||||
GET /api/v1/mdm/space-nodes/parking-spaces 获取车位列表
|
|
||||||
GET /api/v1/mdm/space-nodes/shops 获取商铺列表
|
|
||||||
|
|
||||||
4. 批量操作
|
4. 楼层信息查询(新增)
|
||||||
POST /api/v1/mdm/space-nodes/batch 批量创建
|
GET /api/mdm/space-nodes/{id}/floors 获取楼栋下的楼层列表
|
||||||
PUT /api/v1/mdm/space-nodes/batch 批量更新
|
|
||||||
DELETE /api/v1/mdm/space-nodes/batch 批量删除
|
|
||||||
POST /api/v1/mdm/space-nodes/import Excel导入
|
|
||||||
GET /api/v1/mdm/space-nodes/export/template 下载导入模板
|
|
||||||
GET /api/v1/mdm/space-nodes/export 导出数据
|
|
||||||
|
|
||||||
5. 地图相关
|
5. 批量操作
|
||||||
GET /api/v1/mdm/space-nodes/map/markers 获取地图标注点
|
POST /api/mdm/space-nodes/batch 批量创建
|
||||||
GET /api/v1/mdm/space-nodes/map/boundaries 获取区域边界
|
PUT /api/mdm/space-nodes/batch 批量更新
|
||||||
PUT /api/v1/mdm/space-nodes/{id}/location 更新位置信息
|
DELETE /api/mdm/space-nodes/batch 批量删除
|
||||||
PUT /api/v1/mdm/space-nodes/{id}/boundary 更新区域边界
|
POST /api/mdm/space-nodes/import Excel导入
|
||||||
|
GET /api/mdm/space-nodes/export/template 下载导入模板
|
||||||
|
GET /api/mdm/space-nodes/export 导出数据
|
||||||
|
|
||||||
6. 统计分析
|
6. 地图相关
|
||||||
GET /api/v1/mdm/space-nodes/statistics 空间统计数据
|
GET /api/mdm/space-nodes/map/markers 获取地图标注点
|
||||||
GET /api/v1/mdm/space-nodes/statistics/by-type 按类型统计
|
GET /api/mdm/space-nodes/map/boundaries 获取区域边界
|
||||||
GET /api/v1/mdm/space-nodes/statistics/by-status 按状态统计
|
PUT /api/mdm/space-nodes/{id}/location 更新位置信息
|
||||||
|
PUT /api/mdm/space-nodes/{id}/boundary 更新区域边界
|
||||||
|
|
||||||
|
7. 统计分析
|
||||||
|
GET /api/mdm/space-nodes/statistics 空间统计数据
|
||||||
|
GET /api/mdm/space-nodes/statistics/by-type 按类型统计
|
||||||
|
GET /api/mdm/space-nodes/statistics/by-status 按状态统计
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **变更说明**:
|
||||||
|
> - API路径已去掉v1版本号,统一为 `/api/mdm/space-nodes`
|
||||||
|
> - 树形查询改为按项目维度(`tree?projectId={id}`),不再返回全量树
|
||||||
|
> - 类型查询统一为 `?type={nodeType}` 参数化查询,替代原有的 `/buildings`、`/rooms` 等独立端点
|
||||||
|
> - 新增删除前检查:删除节点前检查是否有子节点和关联业务数据
|
||||||
|
> - 新增级联删除:支持级联删除子节点
|
||||||
|
> - 新增楼层信息查询:`GET /{id}/floors`
|
||||||
|
|
||||||
### 3.2 请求/响应示例
|
### 3.2 请求/响应示例
|
||||||
|
|
||||||
**创建楼栋**:
|
**创建楼栋**:
|
||||||
```json
|
```json
|
||||||
// POST /api/v1/mdm/space-nodes
|
// POST /api/mdm/space-nodes
|
||||||
{
|
{
|
||||||
"projectId": "uuid",
|
"projectId": "uuid",
|
||||||
"code": "B001",
|
"code": "B001",
|
||||||
|
|
@ -401,7 +414,7 @@ GROUP BY p.id, p.code, p.name, p.updated_at;
|
||||||
|
|
||||||
**批量创建房间**:
|
**批量创建房间**:
|
||||||
```json
|
```json
|
||||||
// POST /api/v1/mdm/space-nodes/batch
|
// POST /api/mdm/space-nodes/batch
|
||||||
{
|
{
|
||||||
"parentId": "unit-uuid",
|
"parentId": "unit-uuid",
|
||||||
"nodeType": "ROOM",
|
"nodeType": "ROOM",
|
||||||
|
|
@ -790,17 +803,23 @@ function saveLocation() {
|
||||||
|
|
||||||
| 接口 | 方法 | 路径 | 说明 |
|
| 接口 | 方法 | 路径 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| 获取空间树 | GET | /api/v1/mdm/space-nodes/tree | 项目空间树 |
|
| 获取空间树 | GET | /api/mdm/space-nodes/tree | 项目空间树(按项目维度) |
|
||||||
| 获取根节点 | GET | /api/v1/mdm/space-nodes/roots | 项目下根节点 |
|
| 获取根节点 | GET | /api/mdm/space-nodes/roots | 项目下根节点 |
|
||||||
| 获取节点详情 | GET | /api/v1/mdm/space-nodes/{id} | 节点详情 |
|
| 获取节点详情 | GET | /api/mdm/space-nodes/{id} | 节点详情 |
|
||||||
| 创建节点 | POST | /api/v1/mdm/space-nodes | 创建空间节点 |
|
| 创建节点 | POST | /api/mdm/space-nodes | 创建空间节点 |
|
||||||
| 更新节点 | PUT | /api/v1/mdm/space-nodes/{id} | 更新节点 |
|
| 更新节点 | PUT | /api/mdm/space-nodes/{id} | 更新节点 |
|
||||||
| 删除节点 | DELETE | /api/v1/mdm/space-nodes/{id} | 删除节点 |
|
| 删除节点 | DELETE | /api/mdm/space-nodes/{id} | 删除节点(含删除前检查和级联删除) |
|
||||||
| 获取子节点 | GET | /api/v1/mdm/space-nodes/{id}/children | 子节点列表 |
|
| 获取子节点 | GET | /api/mdm/space-nodes/{id}/children | 子节点列表 |
|
||||||
| 批量创建 | POST | /api/v1/mdm/space-nodes/batch | 批量创建 |
|
| 获取楼层列表 | GET | /api/mdm/space-nodes/{id}/floors | 楼栋下楼层列表(新增) |
|
||||||
| 批量导入 | POST | /api/v1/mdm/space-nodes/import | Excel导入 |
|
| 按类型查询 | GET | /api/mdm/space-nodes?type={type} | 按类型查询(统一接口) |
|
||||||
|
| 批量创建 | POST | /api/mdm/space-nodes/batch | 批量创建 |
|
||||||
|
| 批量导入 | POST | /api/mdm/space-nodes/import | Excel导入 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**文档版本**: v2.0
|
**文档版本**: v2.1
|
||||||
**最后更新**: 2026-03-23
|
**最后更新**: 2026-04-26
|
||||||
|
|
||||||
|
> **TODO 项**:
|
||||||
|
> - SpaceNode 需增加 code 字段和唯一约束(当前代码中 code 字段可能缺失或不唯一)
|
||||||
|
> - 设备扩展字段(attributes JSONB 中的设备信息)应统一到 Equipment 独立表管理,避免数据冗余
|
||||||
|
|
@ -0,0 +1,815 @@
|
||||||
|
# 设施设备领域技术方案
|
||||||
|
|
||||||
|
**领域编号**: 4.3
|
||||||
|
**微服务**: ether-asset
|
||||||
|
**最后更新**: 2026-04-26
|
||||||
|
|
||||||
|
> **更新记录**:
|
||||||
|
> - 2026-04-26: 反向同步实际代码实现到文档。主要变更:设备类型枚举更新为10种(新增ENERGY_METER/LANDSCAPE/KITCHEN,AC→HVAC,FIRE_FIGHTING→FIRE_PROTECTION,POWER_SUPPLY→ELECTRICAL,删除PARKING);设备状态枚举更新(NORMAL→ACTIVE,FAULT→INACTIVE,MAINTAINING→MAINTENANCE);新增4张专业扩展表(Elevator/Hvac/Fire/Energy);新增系统类型(SystemType 9种);新增归属管理(OwnershipType+OwnershipEntity);新增健康评分算法(MTBF/MTTR),标注为Beta;新增故障历史(EquipmentFailureHistory);新增设备照片/文档(JSONB内嵌);新增Excel导入导出;新增特种设备管理;新增能耗标准字段;字段名变更(code→equipmentCode, name→equipmentName, locationDesc→installationLocation);维保计划迁移至独立API;维保记录升级为维保工单;标注TODO: 维保完成率计算暂返回1.0。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、领域概述
|
||||||
|
|
||||||
|
### 1.1 领域职责
|
||||||
|
|
||||||
|
设施设备领域负责管理物业资产全生命周期:
|
||||||
|
|
||||||
|
- 设备台账管理(电梯、空调、消防、给排水、供配电等)
|
||||||
|
- 设备维保计划与记录
|
||||||
|
- 设备故障管理
|
||||||
|
- 设备与工单联动
|
||||||
|
|
||||||
|
### 1.2 核心概念
|
||||||
|
|
||||||
|
| 概念 | 说明 | 对应实体 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **设备台账** | 设备基础信息和运行状态 | Equipment |
|
||||||
|
| **电梯扩展** | 电梯专业参数(载重/速度/注册号等) | EquipmentElevator |
|
||||||
|
| **暖通扩展** | 暖通空调专业参数(制冷量/制热量/能效比等) | EquipmentHvac |
|
||||||
|
| **消防扩展** | 消防设备专业参数(探测范围/联动/分区等) | EquipmentFire |
|
||||||
|
| **能源计量扩展** | 能源表计专业参数(表常数/精度/通讯等) | EquipmentEnergy |
|
||||||
|
| **健康评分** | 设备健康度评估结果 | EquipmentHealthScore |
|
||||||
|
| **故障历史** | 设备故障与维修记录 | EquipmentFailureHistory |
|
||||||
|
| **归属主体** | 设备归属方信息 | OwnershipEntity |
|
||||||
|
| **维保计划** | 定期保养计划(已迁移至独立API) | MaintenancePlan |
|
||||||
|
| **维保工单** | 维保执行工单(原维保记录升级) | MaintenanceTask |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、领域模型
|
||||||
|
|
||||||
|
### 2.1 聚合根设计
|
||||||
|
|
||||||
|
#### Equipment(设备台账)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment")
|
||||||
|
@Data
|
||||||
|
public class Equipment {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String equipmentCode; // 设备编码(原code)
|
||||||
|
private String equipmentName; // 设备名称(原name)
|
||||||
|
|
||||||
|
// 分类
|
||||||
|
private EquipmentType type; // 10种设备类型
|
||||||
|
private EquipmentStatus status; // ACTIVE/INACTIVE/MAINTENANCE/SCRAPPED
|
||||||
|
|
||||||
|
// 系统类型(新增)
|
||||||
|
private SystemType systemType; // 所属系统类型(9种)
|
||||||
|
|
||||||
|
// 品牌型号
|
||||||
|
private String brand;
|
||||||
|
private String model;
|
||||||
|
private String specifications;
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
// 厂商信息
|
||||||
|
private String manufacturer;
|
||||||
|
private String supplier;
|
||||||
|
private String supplierPhone;
|
||||||
|
|
||||||
|
// 位置
|
||||||
|
private UUID spaceNodeId; // 关联空间节点
|
||||||
|
private String installationLocation; // 安装位置(原locationDesc)
|
||||||
|
|
||||||
|
// 时间
|
||||||
|
private LocalDate purchaseDate;
|
||||||
|
private LocalDate installDate;
|
||||||
|
private LocalDate warrantyDate;
|
||||||
|
private Integer lifespanYears; // 设计寿命(年)
|
||||||
|
|
||||||
|
// 维保
|
||||||
|
private Integer maintenanceCycle; // 保养周期(天)
|
||||||
|
private LocalDate lastMaintenanceDate;
|
||||||
|
private LocalDate nextMaintenanceDate;
|
||||||
|
|
||||||
|
// 负责人
|
||||||
|
private UUID managerId;
|
||||||
|
private String managerName;
|
||||||
|
private String contactPhone;
|
||||||
|
|
||||||
|
// 归属管理(新增)
|
||||||
|
private OwnershipType ownershipType; // SELF(自持)/DEVELOPER(开发商)/OWNER(业主)/VENDOR(厂商)
|
||||||
|
private UUID ownershipEntityId; // 归属主体ID
|
||||||
|
|
||||||
|
// 特种设备管理(新增)
|
||||||
|
private Boolean isSpecialEquipment; // 是否特种设备
|
||||||
|
private String specialEquipmentCode; // 特种设备使用登记证编号
|
||||||
|
private String specialEquipmentType; // 特种设备类型
|
||||||
|
private LocalDate specialInspectionDate; // 特种设备检验日期
|
||||||
|
private LocalDate nextSpecialInspectionDate; // 下次检验日期
|
||||||
|
|
||||||
|
// 能耗标准(新增)
|
||||||
|
private String energyStandard; // 能效标准
|
||||||
|
private BigDecimal ratedPower; // 额定功率(kW)
|
||||||
|
private BigDecimal energyEfficiencyRating; // 能效等级
|
||||||
|
|
||||||
|
// 二维码
|
||||||
|
private String qrCode;
|
||||||
|
|
||||||
|
// 设备照片/文档(新增,JSONB内嵌)
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(columnDefinition = "jsonb")
|
||||||
|
private List<AttachmentDTO> photos; // 设备照片列表
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(columnDefinition = "jsonb")
|
||||||
|
private List<AttachmentDTO> documents; // 设备文档列表
|
||||||
|
|
||||||
|
// 扩展属性(JSONB)
|
||||||
|
private String attributes;
|
||||||
|
|
||||||
|
// 审计字段
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**系统类型枚举(新增)**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum SystemType {
|
||||||
|
ELEVATOR_SYSTEM("电梯系统"),
|
||||||
|
HVAC_SYSTEM("暖通系统"),
|
||||||
|
FIRE_SYSTEM("消防系统"),
|
||||||
|
PLUMBING_SYSTEM("给排水系统"),
|
||||||
|
ELECTRICAL_SYSTEM("供配电系统"),
|
||||||
|
SECURITY_SYSTEM("安防系统"),
|
||||||
|
LANDSCAPE_SYSTEM("园林系统"),
|
||||||
|
DRAINAGE_SYSTEM("排污系统"),
|
||||||
|
OTHER("其他");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**归属类型枚举(新增)**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum OwnershipType {
|
||||||
|
SELF("自持"),
|
||||||
|
DEVELOPER("开发商"),
|
||||||
|
OWNER("业主"),
|
||||||
|
VENDOR("厂商");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**附件DTO**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class AttachmentDTO {
|
||||||
|
private String name; // 文件名
|
||||||
|
private String url; // 文件URL
|
||||||
|
private String type; // 文件类型
|
||||||
|
private Long size; // 文件大小
|
||||||
|
private LocalDateTime uploadedAt; // 上传时间
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设备类型枚举**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 设备类型枚举(10种)
|
||||||
|
public enum EquipmentType {
|
||||||
|
ELEVATOR("电梯"),
|
||||||
|
HVAC("暖通空调"),
|
||||||
|
FIRE_PROTECTION("消防设备"),
|
||||||
|
PLUMBING("给排水"),
|
||||||
|
ELECTRICAL("供配电"),
|
||||||
|
ENERGY_METER("能源计量"),
|
||||||
|
SECURITY("安防设备"),
|
||||||
|
LANDSCAPE("园林景观"),
|
||||||
|
KITCHEN("厨房设备"),
|
||||||
|
OTHER("其他设备");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### EquipmentElevator(电梯扩展表,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment_elevator")
|
||||||
|
@Data
|
||||||
|
public class EquipmentElevator {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId; // 关联设备ID
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private Integer loadCapacity; // 载重(kg)
|
||||||
|
private BigDecimal speed; // 速度(m/s)
|
||||||
|
private Integer floors; // 楼层数
|
||||||
|
private Integer stops; // 停站数
|
||||||
|
private String doorType; // 门类型
|
||||||
|
private String driveType; // 驱动方式
|
||||||
|
private String registrationCode; // 使用登记证编号
|
||||||
|
private LocalDate inspectionExpiryDate; // 检验有效期
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### EquipmentHvac(暖通扩展表,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment_hvac")
|
||||||
|
@Data
|
||||||
|
public class EquipmentHvac {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private BigDecimal coolingCapacity; // 制冷量(kW)
|
||||||
|
private BigDecimal heatingCapacity; // 制热量(kW)
|
||||||
|
private BigDecimal cop; // 能效比
|
||||||
|
private String refrigerant; // 制冷剂
|
||||||
|
private BigDecimal airflow; // 风量(m³/h)
|
||||||
|
private String pipeType; // 管道类型
|
||||||
|
private BigDecimal pipeLength; // 管道长度(m)
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### EquipmentFire(消防扩展表,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment_fire")
|
||||||
|
@Data
|
||||||
|
public class EquipmentFire {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String detectionRange; // 探测范围
|
||||||
|
private Boolean hasLinkage; // 是否联动
|
||||||
|
private String fireZone; // 防火分区
|
||||||
|
private String extinguishingAgent; // 灭火剂类型
|
||||||
|
private BigDecimal agentCapacity; // 灭火剂容量
|
||||||
|
private LocalDate lastTestDate; // 上次测试日期
|
||||||
|
private LocalDate nextTestDate; // 下次测试日期
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### EquipmentEnergy(能源计量扩展表,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment_energy")
|
||||||
|
@Data
|
||||||
|
public class EquipmentEnergy {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String meterConstant; // 表常数
|
||||||
|
private String accuracyClass; // 精度等级
|
||||||
|
private String communicationProtocol; // 通讯协议
|
||||||
|
private String meterCategory; // 表计类别
|
||||||
|
private BigDecimal ctRatio; // CT变比
|
||||||
|
private BigDecimal ptRatio; // PT变比
|
||||||
|
private LocalDate calibrationDate; // 校验日期
|
||||||
|
private LocalDate nextCalibrationDate; // 下次校验日期
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### EquipmentHealthScore(健康评分,新增,Beta)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment_health_score")
|
||||||
|
@Data
|
||||||
|
public class EquipmentHealthScore {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private BigDecimal score; // 健康评分(0-100)
|
||||||
|
private String level; // 健康等级: EXCELLENT/GOOD/FAIR/POOR
|
||||||
|
private BigDecimal mtbf; // 平均故障间隔时间(MTBF, 小时)
|
||||||
|
private BigDecimal mttr; // 平均修复时间(MTTR, 小时)
|
||||||
|
private Integer totalFailures; // 总故障次数
|
||||||
|
private Integer totalMaintenance; // 总维保次数
|
||||||
|
private BigDecimal availability; // 可用率(%)
|
||||||
|
private LocalDateTime calculatedAt; // 计算时间
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Beta标注**: 健康评分算法基于MTBF/MTTR计算,当前为Beta版本,评分模型和权重参数需根据实际运行数据持续优化。
|
||||||
|
|
||||||
|
#### EquipmentFailureHistory(故障历史,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_equipment_failure_history")
|
||||||
|
@Data
|
||||||
|
public class EquipmentFailureHistory {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private LocalDateTime failureTime; // 故障时间
|
||||||
|
private String failureType; // 故障类型
|
||||||
|
private String failureDescription; // 故障描述
|
||||||
|
private String faultCause; // 故障原因
|
||||||
|
private String solution; // 解决方案
|
||||||
|
private UUID repairWorkOrderId; // 关联维修工单
|
||||||
|
private LocalDateTime resolvedTime; // 修复时间
|
||||||
|
private Long downtimeMinutes; // 停机时长(分钟)
|
||||||
|
private String reportedBy; // 报告人
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OwnershipEntity(归属主体,新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_ownership_entity")
|
||||||
|
@Data
|
||||||
|
public class OwnershipEntity {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private OwnershipType type; // SELF/DEVELOPER/OWNER/VENDOR
|
||||||
|
private String name; // 归属方名称
|
||||||
|
private String contactPerson; // 联系人
|
||||||
|
private String contactPhone; // 联系电话
|
||||||
|
private String address; // 地址
|
||||||
|
private String description; // 描述
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MaintenancePlan(维保计划)
|
||||||
|
|
||||||
|
> **注意**: 维保计划已迁移至独立API(`/api/asset/maintenance-plans`),不再嵌套在设备管理接口下。
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_maintenance_plan")
|
||||||
|
@Data
|
||||||
|
public class MaintenancePlan {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
private UUID equipmentId; // 关联设备
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private MaintenanceType type; // DAILY/WEEKLY/MONTHLY/QUARTERLY/YEARLY
|
||||||
|
|
||||||
|
// 周期
|
||||||
|
private Integer cycleDays; // 周期天数
|
||||||
|
private String cronExpression; // Cron表达式
|
||||||
|
|
||||||
|
// 内容
|
||||||
|
private String content; // 保养内容描述
|
||||||
|
private String checkItems; // 检查项清单(JSON)
|
||||||
|
|
||||||
|
// 负责人
|
||||||
|
private UUID maintainerId;
|
||||||
|
private String maintainerName;
|
||||||
|
|
||||||
|
// 提醒
|
||||||
|
private Integer remindDays; // 提前提醒天数
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
// 审计字段
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MaintenanceTask(维保工单,原MaintenanceRecord升级)
|
||||||
|
|
||||||
|
> **注意**: 原MaintenanceRecord已升级为MaintenanceTask(维保工单),增加了工单状态流转、费用记录、签名确认等字段。
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "asset_maintenance_task")
|
||||||
|
@Data
|
||||||
|
public class MaintenanceTask {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID equipmentId;
|
||||||
|
private UUID planId; // 关联计划
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
// 工单信息
|
||||||
|
private String taskCode; // 工单编号
|
||||||
|
private MaintenanceTaskStatus status; // PENDING/IN_PROGRESS/COMPLETED/CANCELLED
|
||||||
|
|
||||||
|
// 维保信息
|
||||||
|
private LocalDate maintenanceDate;
|
||||||
|
private MaintenanceType type;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
// 执行人
|
||||||
|
private UUID maintainerId;
|
||||||
|
private String maintainerName;
|
||||||
|
|
||||||
|
// 结果
|
||||||
|
private MaintenanceResult result; // NORMAL/ABNORMAL
|
||||||
|
private String remark;
|
||||||
|
private String images;
|
||||||
|
|
||||||
|
// 检查项结果
|
||||||
|
private String checkResults; // [{"item":"检查A","result":"PASS"}]
|
||||||
|
|
||||||
|
// 费用(新增)
|
||||||
|
private BigDecimal laborCost; // 人工费
|
||||||
|
private BigDecimal partsCost; // 材料费
|
||||||
|
private BigDecimal totalCost; // 总费用
|
||||||
|
|
||||||
|
// 签名确认(新增)
|
||||||
|
private String signature; // 签名图片URL
|
||||||
|
|
||||||
|
// 关联工单
|
||||||
|
private UUID workOrderId; // 异常时关联的维修工单
|
||||||
|
|
||||||
|
// 下次维保
|
||||||
|
private LocalDate nextMaintenanceDate;
|
||||||
|
|
||||||
|
// 审计字段
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MaintenanceTaskStatus {
|
||||||
|
PENDING("待执行"),
|
||||||
|
IN_PROGRESS("执行中"),
|
||||||
|
COMPLETED("已完成"),
|
||||||
|
CANCELLED("已取消");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、设备与工单联动
|
||||||
|
|
||||||
|
### 3.1 故障自动报修
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class EquipmentFaultHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WorkOrderService workOrderService;
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void onEquipmentFault(EquipmentFaultEvent event) {
|
||||||
|
Equipment equipment = event.getEquipment();
|
||||||
|
|
||||||
|
// 创建设备维修工单
|
||||||
|
WorkOrder workOrder = new WorkOrder();
|
||||||
|
workOrder.setOrderType(WorkOrderType.REPAIR);
|
||||||
|
workOrder.setTitle("设备故障: " + equipment.getEquipmentName());
|
||||||
|
workOrder.setDescription(event.getFaultDescription());
|
||||||
|
workOrder.setEquipmentId(equipment.getId());
|
||||||
|
workOrder.setSpaceNodeId(equipment.getSpaceNodeId());
|
||||||
|
workOrder.setPriority(WorkOrderPriority.HIGH);
|
||||||
|
workOrder.setSource(WorkOrderSource.FAULT);
|
||||||
|
|
||||||
|
workOrderService.create(workOrder);
|
||||||
|
|
||||||
|
// 更新设备状态
|
||||||
|
equipment.setStatus(EquipmentStatus.INACTIVE);
|
||||||
|
equipmentRepository.save(equipment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 维保到期提醒
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class MaintenanceReminderJob {
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行
|
||||||
|
public void remindUpcomingMaintenance() {
|
||||||
|
// 查询7天内到期的维保计划
|
||||||
|
LocalDate targetDate = LocalDate.now().plusDays(7);
|
||||||
|
|
||||||
|
List<Equipment> equipments = equipmentRepository
|
||||||
|
.findByNextMaintenanceDateBeforeAndStatus(targetDate, EquipmentStatus.ACTIVE);
|
||||||
|
|
||||||
|
for (Equipment equipment : equipments) {
|
||||||
|
notificationService.sendMaintenanceReminder(equipment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、实现状态与差异
|
||||||
|
|
||||||
|
### 4.1 实现状态
|
||||||
|
|
||||||
|
| 功能模块 | 实现状态 | 备注 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| Equipment | 🟢 已实现 | 基础CRUD,字段名已更新(equipmentCode/equipmentName/installationLocation) |
|
||||||
|
| EquipmentElevator | 🟢 已实现 | 电梯专业扩展表 |
|
||||||
|
| EquipmentHvac | 🟢 已实现 | 暖通专业扩展表 |
|
||||||
|
| EquipmentFire | 🟢 已实现 | 消防专业扩展表 |
|
||||||
|
| EquipmentEnergy | 🟢 已实现 | 能源计量扩展表 |
|
||||||
|
| EquipmentHealthScore | 🟢 已实现(Beta) | 健康评分,MTBF/MTTR算法,Beta版本 |
|
||||||
|
| EquipmentFailureHistory | 🟢 已实现 | 故障历史记录 |
|
||||||
|
| OwnershipEntity | 🟢 已实现 | 归属主体管理 |
|
||||||
|
| MaintenancePlan | 🟢 已实现 | 已迁移至独立API |
|
||||||
|
| MaintenanceTask | 🟢 已实现 | 原MaintenanceRecord升级为维保工单 |
|
||||||
|
| 设备二维码 | 🟢 已实现 | 支持扫码查看设备信息 |
|
||||||
|
| Excel导入导出 | 🟢 已实现 | 设备台账Excel导入导出 |
|
||||||
|
| 特种设备管理 | 🟢 已实现 | 特种设备登记证/检验日期 |
|
||||||
|
| 能耗标准字段 | 🟢 已实现 | 能效标准/额定功率/能效等级 |
|
||||||
|
| 设备照片/文档 | 🟢 已实现 | JSONB内嵌存储 |
|
||||||
|
| 故障自动工单 | 🟡 部分实现 | 设备状态变更可关联工单 |
|
||||||
|
| 维保完成率计算 | 🟡 部分实现 | TODO: 当前暂返回1.0,需实现真实计算 |
|
||||||
|
| IoT集成 | 🔴 未实现 | 待开发 |
|
||||||
|
|
||||||
|
### 4.2 与设计方案的差异
|
||||||
|
|
||||||
|
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|
||||||
|
|--------|----------|----------|----------|
|
||||||
|
| **维保归属** | ether-asset | ether-asset | 已修正:维保实体已统一在 ether-asset |
|
||||||
|
| **设备管理** | ether-asset | ether-asset | 已修正:设备实体已统一在 ether-asset |
|
||||||
|
| **设备联动** | 故障自动工单 | 部分实现 | 功能完善中 |
|
||||||
|
| **设备类型** | 8种枚举 | 10种枚举 | 已扩展:新增ENERGY_METER/LANDSCAPE/KITCHEN |
|
||||||
|
| **设备状态** | NORMAL/FAULT | ACTIVE/INACTIVE | 已修正:语义更清晰 |
|
||||||
|
| **专业扩展** | 无 | 4张扩展表 | 已新增:Elevator/Hvac/Fire/Energy |
|
||||||
|
| **健康评分** | 无 | Beta实现 | 已新增:基于MTBF/MTTR |
|
||||||
|
| **归属管理** | 无 | OwnershipEntity | 已新增:4种归属类型 |
|
||||||
|
| **维保记录** | MaintenanceRecord | MaintenanceTask | 已升级:增加工单状态/费用/签名 |
|
||||||
|
| **字段命名** | code/name/locationDesc | equipmentCode/equipmentName/installationLocation | 已修正:命名更规范 |
|
||||||
|
|
||||||
|
### 4.3 改进计划
|
||||||
|
|
||||||
|
| 优先级 | 改进项 | 说明 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| P0 | 维保完成率计算 | 当前暂返回1.0,需实现真实计算逻辑 |
|
||||||
|
| P1 | 健康评分优化 | Beta版评分模型需根据实际运行数据持续优化 |
|
||||||
|
| P1 | 完善故障自动工单 | 设备状态变更时自动创建维修工单 |
|
||||||
|
| P2 | 完善设备二维码功能 | 支持扫码报修 |
|
||||||
|
| P3 | IoT传感器接入 | 对接传感器数据 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库表结构
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 设备台账表
|
||||||
|
CREATE TABLE asset_equipment (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
equipment_code VARCHAR(50) NOT NULL,
|
||||||
|
equipment_name VARCHAR(100) NOT NULL,
|
||||||
|
type VARCHAR(20) NOT NULL,
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
system_type VARCHAR(30),
|
||||||
|
brand VARCHAR(100),
|
||||||
|
model VARCHAR(100),
|
||||||
|
specifications VARCHAR(500),
|
||||||
|
serial_number VARCHAR(100),
|
||||||
|
manufacturer VARCHAR(100),
|
||||||
|
supplier VARCHAR(100),
|
||||||
|
supplier_phone VARCHAR(20),
|
||||||
|
space_node_id UUID,
|
||||||
|
installation_location VARCHAR(255),
|
||||||
|
purchase_date DATE,
|
||||||
|
install_date DATE,
|
||||||
|
warranty_date DATE,
|
||||||
|
lifespan_years INTEGER,
|
||||||
|
maintenance_cycle INTEGER,
|
||||||
|
last_maintenance_date DATE,
|
||||||
|
next_maintenance_date DATE,
|
||||||
|
manager_id UUID,
|
||||||
|
manager_name VARCHAR(100),
|
||||||
|
contact_phone VARCHAR(20),
|
||||||
|
ownership_type VARCHAR(20),
|
||||||
|
ownership_entity_id UUID,
|
||||||
|
is_special_equipment BOOLEAN DEFAULT FALSE,
|
||||||
|
special_equipment_code VARCHAR(100),
|
||||||
|
special_equipment_type VARCHAR(50),
|
||||||
|
special_inspection_date DATE,
|
||||||
|
next_special_inspection_date DATE,
|
||||||
|
energy_standard VARCHAR(100),
|
||||||
|
rated_power DECIMAL(10,2),
|
||||||
|
energy_efficiency_rating DECIMAL(3,1),
|
||||||
|
qr_code VARCHAR(255),
|
||||||
|
photos JSONB,
|
||||||
|
documents JSONB,
|
||||||
|
attributes JSONB,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(project_id, equipment_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 电梯扩展表
|
||||||
|
CREATE TABLE asset_equipment_elevator (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL REFERENCES asset_equipment(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
load_capacity INTEGER,
|
||||||
|
speed DECIMAL(5,2),
|
||||||
|
floors INTEGER,
|
||||||
|
stops INTEGER,
|
||||||
|
door_type VARCHAR(50),
|
||||||
|
drive_type VARCHAR(50),
|
||||||
|
registration_code VARCHAR(100),
|
||||||
|
inspection_expiry_date DATE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 暖通扩展表
|
||||||
|
CREATE TABLE asset_equipment_hvac (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL REFERENCES asset_equipment(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
cooling_capacity DECIMAL(10,2),
|
||||||
|
heating_capacity DECIMAL(10,2),
|
||||||
|
cop DECIMAL(5,2),
|
||||||
|
refrigerant VARCHAR(50),
|
||||||
|
airflow DECIMAL(10,2),
|
||||||
|
pipe_type VARCHAR(50),
|
||||||
|
pipe_length DECIMAL(10,2),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 消防扩展表
|
||||||
|
CREATE TABLE asset_equipment_fire (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL REFERENCES asset_equipment(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
detection_range VARCHAR(100),
|
||||||
|
has_linkage BOOLEAN,
|
||||||
|
fire_zone VARCHAR(100),
|
||||||
|
extinguishing_agent VARCHAR(50),
|
||||||
|
agent_capacity DECIMAL(10,2),
|
||||||
|
last_test_date DATE,
|
||||||
|
next_test_date DATE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 能源计量扩展表
|
||||||
|
CREATE TABLE asset_equipment_energy (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL REFERENCES asset_equipment(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
meter_constant VARCHAR(50),
|
||||||
|
accuracy_class VARCHAR(20),
|
||||||
|
communication_protocol VARCHAR(50),
|
||||||
|
meter_category VARCHAR(50),
|
||||||
|
ct_ratio DECIMAL(10,2),
|
||||||
|
pt_ratio DECIMAL(10,2),
|
||||||
|
calibration_date DATE,
|
||||||
|
next_calibration_date DATE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 健康评分表
|
||||||
|
CREATE TABLE asset_equipment_health_score (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL REFERENCES asset_equipment(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
score DECIMAL(5,2),
|
||||||
|
level VARCHAR(20),
|
||||||
|
mtbf DECIMAL(12,2),
|
||||||
|
mttr DECIMAL(12,2),
|
||||||
|
total_failures INTEGER DEFAULT 0,
|
||||||
|
total_maintenance INTEGER DEFAULT 0,
|
||||||
|
availability DECIMAL(5,2),
|
||||||
|
calculated_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 故障历史表
|
||||||
|
CREATE TABLE asset_equipment_failure_history (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL REFERENCES asset_equipment(id),
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
failure_time TIMESTAMP NOT NULL,
|
||||||
|
failure_type VARCHAR(50),
|
||||||
|
failure_description TEXT,
|
||||||
|
fault_cause TEXT,
|
||||||
|
solution TEXT,
|
||||||
|
repair_work_order_id UUID,
|
||||||
|
resolved_time TIMESTAMP,
|
||||||
|
downtime_minutes BIGINT,
|
||||||
|
reported_by VARCHAR(100),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 归属主体表
|
||||||
|
CREATE TABLE asset_ownership_entity (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
type VARCHAR(20) NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
contact_person VARCHAR(100),
|
||||||
|
contact_phone VARCHAR(20),
|
||||||
|
address VARCHAR(255),
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 维保计划表
|
||||||
|
CREATE TABLE asset_maintenance_plan (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
equipment_id UUID NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
type VARCHAR(20) NOT NULL,
|
||||||
|
cycle_days INTEGER,
|
||||||
|
cron_expression VARCHAR(50),
|
||||||
|
content TEXT,
|
||||||
|
check_items JSONB,
|
||||||
|
maintainer_id UUID,
|
||||||
|
maintainer_name VARCHAR(100),
|
||||||
|
remind_days INTEGER DEFAULT 3,
|
||||||
|
enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 维保工单表(原维保记录表升级)
|
||||||
|
CREATE TABLE asset_maintenance_task (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
equipment_id UUID NOT NULL,
|
||||||
|
plan_id UUID,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
task_code VARCHAR(50),
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||||
|
maintenance_date DATE NOT NULL,
|
||||||
|
type VARCHAR(20) NOT NULL,
|
||||||
|
content TEXT,
|
||||||
|
maintainer_id UUID,
|
||||||
|
maintainer_name VARCHAR(100),
|
||||||
|
result VARCHAR(20),
|
||||||
|
remark VARCHAR(500),
|
||||||
|
images TEXT,
|
||||||
|
check_results JSONB,
|
||||||
|
labor_cost DECIMAL(10,2),
|
||||||
|
parts_cost DECIMAL(10,2),
|
||||||
|
total_cost DECIMAL(10,2),
|
||||||
|
signature VARCHAR(255),
|
||||||
|
work_order_id UUID,
|
||||||
|
next_maintenance_date DATE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 创建索引
|
||||||
|
CREATE INDEX idx_equipment_project ON asset_equipment(project_id);
|
||||||
|
CREATE INDEX idx_equipment_type ON asset_equipment(type);
|
||||||
|
CREATE INDEX idx_equipment_status ON asset_equipment(status);
|
||||||
|
CREATE INDEX idx_equipment_space ON asset_equipment(space_node_id);
|
||||||
|
CREATE INDEX idx_equipment_next_maintenance ON asset_equipment(next_maintenance_date);
|
||||||
|
CREATE INDEX idx_equipment_system_type ON asset_equipment(system_type);
|
||||||
|
CREATE INDEX idx_equipment_ownership ON asset_equipment(ownership_entity_id);
|
||||||
|
CREATE INDEX idx_equipment_special ON asset_equipment(is_special_equipment) WHERE is_special_equipment = TRUE;
|
||||||
|
CREATE INDEX idx_maintenance_plan_equipment ON asset_maintenance_plan(equipment_id);
|
||||||
|
CREATE INDEX idx_maintenance_task_equipment ON asset_maintenance_task(equipment_id);
|
||||||
|
CREATE INDEX idx_maintenance_task_status ON asset_maintenance_task(status);
|
||||||
|
CREATE INDEX idx_failure_history_equipment ON asset_equipment_failure_history(equipment_id);
|
||||||
|
CREATE INDEX idx_health_score_equipment ON asset_equipment_health_score(equipment_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档维护**: 本领域技术方案由 ether-asset 服务负责人维护
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
**领域编号**: 4.4
|
**领域编号**: 4.4
|
||||||
**微服务**: ether-finance
|
**微服务**: ether-finance
|
||||||
**最后更新**: 2026-02-10
|
**最后更新**: 2026-04-26
|
||||||
|
|
||||||
|
> **更新记录**:
|
||||||
|
> - 2026-04-26: 反向同步实际代码实现到文档。主要变更:修正 FeeItem/FeeBill/FeePayment 的实现状态标注为"未实现"(原标注为"已实现"与实际代码不符);修正费用催缴/滞纳金的实现状态标注为"未实现";新增能耗计量管理章节(EnergyMeter/EnergyConsumption,当前在 module-mdm 中实现);新增能源类型枚举(EnergyType);新增记录方式枚举(RecordMethod);明确能耗管理与财务计费的边界。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -19,12 +22,14 @@
|
||||||
|
|
||||||
### 1.2 核心概念
|
### 1.2 核心概念
|
||||||
|
|
||||||
| 概念 | 说明 | 对应实体 |
|
| 概念 | 说明 | 对应实体 | 实现状态 |
|
||||||
|------|------|----------|
|
|------|------|----------|----------|
|
||||||
| **收费项目** | 可收费的项目定义 | FeeItem |
|
| **收费项目** | 可收费的项目定义 | FeeItem | 未实现 |
|
||||||
| **账单** | 应收费用单据 | FeeBill |
|
| **账单** | 应收费用单据 | FeeBill | 未实现 |
|
||||||
| **支付记录** | 支付流水记录 | FeePayment |
|
| **支付记录** | 支付流水记录 | FeePayment | 未实现 |
|
||||||
| **退款** | 退款申请与处理 | FeeRefund |
|
| **退款** | 退款申请与处理 | FeeRefund | 未实现 |
|
||||||
|
| **能源计量点** | 能耗数据采集点 | EnergyMeter | 已实现(module-mdm) |
|
||||||
|
| **能耗记录** | 抄表数据与费用计算 | EnergyConsumption | 已实现(module-mdm) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -308,41 +313,193 @@ public class FeeReminderJob {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 四、实现状态与差异
|
## 四、能耗计量管理(已实现,当前在 module-mdm 中)
|
||||||
|
|
||||||
### 4.1 实现状态
|
> **边界说明**: 能耗计量管理当前在 `module-mdm` 中实现,属于基础数据管理(MDM)范畴。能耗数据采集和简单费用计算是已实现功能,但正式的账单生成、支付处理等财务功能属于本领域(module-finance)的待实现功能。两者通过 EnergyConsumption.amount 对接:能耗模块提供用量和计算值,财务模块将其作为按用量计费的账单数据来源。
|
||||||
|
|
||||||
|
### 4.1 EnergyMeter(能源计量点)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_energy_meter")
|
||||||
|
@Data
|
||||||
|
public class EnergyMeter {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String meterCode; // 计量点编码(自动生成:EM + yyyyMMddHHmmss)
|
||||||
|
private String meterName; // 计量点名称
|
||||||
|
private EnergyType energyType; // 能源类型枚举
|
||||||
|
private UUID spaceNodeId; // 关联空间节点
|
||||||
|
private String installationLocation;// 安装位置
|
||||||
|
private BigDecimal ratedCapacity; // 额定容量
|
||||||
|
private BigDecimal unitPrice; // 单价(精度4位小数,用于费用计算)
|
||||||
|
private Status status; // ACTIVE/INACTIVE
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**能源类型枚举**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum EnergyType {
|
||||||
|
LIGHTING("照明插座用电"),
|
||||||
|
HVAC("空调用电"),
|
||||||
|
POWER("动力用电"),
|
||||||
|
SPECIAL("特殊用电"),
|
||||||
|
WATER("给排水"),
|
||||||
|
GAS("燃气");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **已知问题**: 前后端能源类型枚举不一致。后端为 LIGHTING/HVAC/POWER/SPECIAL/WATER/GAS,前端为 ELECTRICITY/WATER/GAS/CENTRAL_HEATING/CENTRAL_COOLING,需对齐。
|
||||||
|
|
||||||
|
### 4.2 EnergyConsumption(能耗记录)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Entity
|
||||||
|
@Table(name = "ops_energy_consumption")
|
||||||
|
@Data
|
||||||
|
public class EnergyConsumption {
|
||||||
|
@Id
|
||||||
|
private UUID id;
|
||||||
|
private UUID projectId;
|
||||||
|
private UUID meterId; // 关联计量点
|
||||||
|
|
||||||
|
private LocalDate consumptionDate; // 记录日期
|
||||||
|
private BigDecimal previousReading; // 上次读数
|
||||||
|
private BigDecimal currentReading; // 当前读数
|
||||||
|
private BigDecimal consumption; // 消耗量 = current - previous
|
||||||
|
private BigDecimal amount; // 费用 = consumption x unitPrice
|
||||||
|
private UUID recordedBy; // 记录人
|
||||||
|
private RecordMethod recordMethod; // 记录方式
|
||||||
|
private String remarks; // 备注
|
||||||
|
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**记录方式枚举**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum RecordMethod {
|
||||||
|
MANUAL("手动录入"),
|
||||||
|
IOT("IoT自动采集");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 能耗业务规则
|
||||||
|
|
||||||
|
| 规则 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 编码自动生成 | 格式:EM + yyyyMMddHHmmss,冲突时追加后缀 |
|
||||||
|
| 仪表状态校验 | 只能对 ACTIVE 状态的仪表进行抄表 |
|
||||||
|
| 读数递增校验 | 当前读数不能小于上次读数 |
|
||||||
|
| 自动获取上次读数 | 从最近一条记录获取 previousReading,首次为 0 |
|
||||||
|
| 自动计算消耗量 | consumption = currentReading - previousReading |
|
||||||
|
| 自动计算费用 | amount = consumption x meter.unitPrice(单价为空时为 0) |
|
||||||
|
| 默认手动录入 | recordMethod 默认设为 MANUAL |
|
||||||
|
| 软删除 | 删除操作将状态设为 INACTIVE |
|
||||||
|
|
||||||
|
### 4.4 能耗 API 接口
|
||||||
|
|
||||||
|
```
|
||||||
|
基础路径: /api/ops/energy
|
||||||
|
|
||||||
|
计量点管理:
|
||||||
|
POST /meters 创建计量点
|
||||||
|
GET /meters 查询计量点列表(projectId必填,energyType可选)
|
||||||
|
GET /meters/{id} 获取计量点详情
|
||||||
|
PUT /meters/{id} 更新计量点
|
||||||
|
DELETE /meters/{id} 删除计量点(软删除)
|
||||||
|
|
||||||
|
能耗记录:
|
||||||
|
POST /consumption 录入能耗数据
|
||||||
|
GET /consumption/{meterId} 查询能耗记录(startDate/endDate可选)
|
||||||
|
|
||||||
|
能耗统计:
|
||||||
|
GET /statistics/by-type 按类型统计消耗
|
||||||
|
GET /statistics/unit-consumption 单方能耗
|
||||||
|
```
|
||||||
|
|
||||||
|
> **已知缺陷**: `getConsumptionByType()` 当前将项目总消耗全部归入 LIGHTING 类型,未按 meter.energyType 真正分项汇总,需修复。
|
||||||
|
|
||||||
|
### 4.5 能耗与财务的边界
|
||||||
|
|
||||||
|
```
|
||||||
|
EnergyMeter (module-mdm) FeeItem (module-finance,待实现)
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
EnergyConsumption --费用计算--> FeeBill (自动/手动生成,待实现)
|
||||||
|
(抄表数据) |
|
||||||
|
v
|
||||||
|
FeePayment (支付记录,待实现)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
FeeRefund (退款记录,待实现)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **能耗模块(module-mdm)职责**: 计量点管理、抄表录入、消耗量计算、简单费用计算(consumption x unitPrice)
|
||||||
|
- **财务模块(module-finance,待实现)职责**: 收费项目管理、账单生成、支付处理、催缴通知、滞纳金计算、退款处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、实现状态与差异
|
||||||
|
|
||||||
|
### 5.1 实现状态
|
||||||
|
|
||||||
| 功能模块 | 实现状态 | 备注 |
|
| 功能模块 | 实现状态 | 备注 |
|
||||||
|---------|---------|------|
|
|---------|---------|------|
|
||||||
| FeeItem | 🟢 已实现 | 基础CRUD |
|
| FeeItem | 🔴 未实现 | 无 module-finance 模块,无 fin_* 表 |
|
||||||
| FeeBill | 🟢 已实现 | 基础CRUD |
|
| FeeBill | 🔴 未实现 | 无 FeeBill 实体/服务/控制器 |
|
||||||
| FeePayment | 🟢 已实现 | 基础CRUD |
|
| FeePayment | 🔴 未实现 | 无 FeePayment 实体/服务/控制器 |
|
||||||
| 费用催缴 | 🟢 已实现 | 定时任务 |
|
| FeeRefund | 🔴 未实现 | 原设计即标注未实现 |
|
||||||
| 滞纳金计算 | 🟢 已实现 | 自动计算 |
|
| 费用催缴 | 🔴 未实现 | 无 FeeReminderJob 定时任务 |
|
||||||
|
| 滞纳金计算 | 🔴 未实现 | 无计算逻辑 |
|
||||||
|
| EnergyMeter | 🟢 已实现 | 在 module-mdm 中实现 |
|
||||||
|
| EnergyConsumption | 🟢 已实现 | 在 module-mdm 中实现,含简单费用计算 |
|
||||||
|
| 能耗统计分析 | 🟢 已实现(有缺陷) | 按类型统计未真正分项汇总 |
|
||||||
| 退款功能 | 🔴 未实现 | 待开发 |
|
| 退款功能 | 🔴 未实现 | 待开发 |
|
||||||
| 财务报表 | 🔴 未实现 | 待开发 |
|
| 财务报表 | 🔴 未实现 | 待开发 |
|
||||||
| 支付网关 | 🔴 未实现 | 待对接 |
|
| 支付网关 | 🔴 未实现 | 待对接 |
|
||||||
|
|
||||||
### 4.2 与设计方案的差异
|
### 5.2 与设计方案的差异
|
||||||
|
|
||||||
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|
||||||
|--------|----------|----------|----------|
|
|--------|----------|----------|----------|
|
||||||
| **实体归属** | ether-finance | ether-finance + ether-mdm重复 | ❌ 存在重复实体 |
|
| **FeeItem** | 完整实体定义 | 不存在 | 完全缺失,需新建 module-finance |
|
||||||
| **支付网关** | 对接微信/支付宝 | 未对接 | 🔴 功能缺失 |
|
| **FeeBill** | 完整实体定义 | 不存在 | 完全缺失 |
|
||||||
| **退款** | 退款流程 | 未实现 | 🔴 功能缺失 |
|
| **FeePayment** | 完整实体定义 | 不存在 | 完全缺失 |
|
||||||
|
| **FeeRefund** | 完整实体定义 | 不存在 | 完全缺失 |
|
||||||
|
| **费用催缴** | FeeReminderJob | 不存在 | 完全缺失 |
|
||||||
|
| **滞纳金** | 自动计算逻辑 | 不存在 | 完全缺失 |
|
||||||
|
| **能耗管理** | 未在设计文档中 | module-mdm中已实现 | 设计文档遗漏,需补充 |
|
||||||
|
| **能源类型** | FeeType(8种) | EnergyType(6种) | 实际更细分(按用电分项),但缺少供暖/物业费 |
|
||||||
|
| **前后端枚举** | 统一 | 不一致 | 后端LIGHTING/HVAC/POWER/SPECIAL/WATER/GAS vs 前端ELECTRICITY/WATER/GAS/CENTRAL_HEATING/CENTRAL_COOLING |
|
||||||
|
|
||||||
### 4.3 改进计划
|
### 5.3 改进计划
|
||||||
|
|
||||||
| 优先级 | 改进项 | 说明 |
|
| 优先级 | 改进项 | 说明 |
|
||||||
|--------|--------|------|
|
|--------|--------|------|
|
||||||
| P1 | 清理重复实体 | 删除ether-mdm中的Fee相关实体 |
|
| P0 | 创建 module-finance 模块 | 独立财务模块,包含 entity/service/controller/repository |
|
||||||
| P2 | 实现退款功能 | 退款申请、审批、执行流程 |
|
| P0 | 实现 FeeItem 收费项目 | 含完整字段和 CRUD API |
|
||||||
| P2 | 对接支付网关 | 集成微信支付、支付宝SDK |
|
| P0 | 实现 FeeBill 账单 | 含账单自动生成和状态流转 |
|
||||||
|
| P0 | 实现 FeePayment 支付记录 | 线下收款登记、支付记录查询 |
|
||||||
|
| P0 | 能耗数据与账单对接 | 将 EnergyConsumption 的 amount 作为按用量计费的账单数据来源 |
|
||||||
|
| P1 | 修复能耗按类型统计 | getConsumptionByType() 应按 meter.energyType 真正分项汇总 |
|
||||||
|
| P1 | 统一前后端能源类型枚举 | 对齐后端和前端的 EnergyType 定义 |
|
||||||
|
| P1 | 实现费用催缴定时任务 | 到期提醒、逾期催缴、周汇总 |
|
||||||
|
| P1 | 实现滞纳金自动计算 | 逾期天数 x 日利率,不超过上限 |
|
||||||
|
| P1 | 实现退款功能 | 退款申请、审批、执行流程 |
|
||||||
|
| P2 | 对接支付网关 | 集成微信支付、支付宝 SDK |
|
||||||
| P2 | 财务报表 | 收费统计、欠费分析、收入趋势 |
|
| P2 | 财务报表 | 收费统计、欠费分析、收入趋势 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 五、数据库表结构
|
## 六、数据库表结构
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- 收费项目表
|
-- 收费项目表
|
||||||
|
|
@ -436,6 +593,49 @@ CREATE INDEX idx_fee_bill_status ON fin_fee_bill(status);
|
||||||
CREATE INDEX idx_fee_bill_owner ON fin_fee_bill(owner_id);
|
CREATE INDEX idx_fee_bill_owner ON fin_fee_bill(owner_id);
|
||||||
CREATE INDEX idx_fee_bill_due_date ON fin_fee_bill(due_date);
|
CREATE INDEX idx_fee_bill_due_date ON fin_fee_bill(due_date);
|
||||||
CREATE INDEX idx_fee_payment_bill ON fin_fee_payment(bill_id);
|
CREATE INDEX idx_fee_payment_bill ON fin_fee_payment(bill_id);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 以下为已实现的能耗计量相关表(在 module-mdm 中)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 能源计量点表(已实现)
|
||||||
|
CREATE TABLE ops_energy_meter (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
meter_code VARCHAR(50) NOT NULL,
|
||||||
|
meter_name VARCHAR(100) NOT NULL,
|
||||||
|
energy_type VARCHAR(20) NOT NULL,
|
||||||
|
space_node_id UUID,
|
||||||
|
installation_location VARCHAR(255),
|
||||||
|
rated_capacity NUMERIC(10,2),
|
||||||
|
unit_price NUMERIC(10,4),
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(project_id, meter_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 能耗记录表(已实现)
|
||||||
|
CREATE TABLE ops_energy_consumption (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL,
|
||||||
|
meter_id UUID NOT NULL REFERENCES ops_energy_meter(id),
|
||||||
|
consumption_date DATE NOT NULL,
|
||||||
|
previous_reading NUMERIC(12,2),
|
||||||
|
current_reading NUMERIC(12,2),
|
||||||
|
consumption NUMERIC(12,2) NOT NULL,
|
||||||
|
amount NUMERIC(10,2),
|
||||||
|
recorded_by UUID,
|
||||||
|
record_method VARCHAR(20) DEFAULT 'MANUAL',
|
||||||
|
remarks VARCHAR(1000),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 能耗表索引
|
||||||
|
CREATE INDEX idx_energy_meter_project ON ops_energy_meter(project_id);
|
||||||
|
CREATE INDEX idx_energy_meter_status ON ops_energy_meter(status);
|
||||||
|
CREATE INDEX idx_ec_meter_date ON ops_energy_consumption(meter_id, consumption_date);
|
||||||
|
CREATE INDEX idx_ec_project_date ON ops_energy_consumption(project_id, consumption_date);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
**领域编号**: 4.5
|
**领域编号**: 4.5
|
||||||
**微服务**: ether-auth
|
**微服务**: ether-auth
|
||||||
**最后更新**: 2026-02-27
|
**最后更新**: 2026-04-26
|
||||||
|
|
||||||
|
> **更新记录**:
|
||||||
|
> - 2026-04-26: 反向同步实际代码实现到文档。主要变更:Role.type 改为 SYSTEM/PROJECT/DEPARTMENT 三级分类;Role.status 改为 RoleStatus 枚举;Permission 结构简化(parentCode 替代 parentId);UserRole 简化为 M2M 中间表;OperationLog 更名为 AuditLog(表名和字段结构变更);新增实体(EnterpriseUser/ProjectStaff/ProjectStaffRole/Resident/ResidentSpace/Space/UserProject/DataAccess/Dept/SysConfig);新增 API(部门管理/项目成员管理/数据访问授权/审计日志/系统配置/企业员工/用户项目关联);新增业务规则(登录失败锁定/密码强度校验/审计日志增强/项目成员角色/部门类型/住户认证);角色体系改为数据库动态管理;API路径去掉v1版本号。标注 TODO 项:Permission 需增加菜单路由属性(path/component/icon)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -23,11 +26,20 @@
|
||||||
|
|
||||||
| 概念 | 说明 | 对应实体 |
|
| 概念 | 说明 | 对应实体 |
|
||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| **用户** | 系统登录账户 | User |
|
| **用户** | 系统登录账户,支持四种用户类型 | User |
|
||||||
| **角色** | 权限集合 | Role |
|
| **企业员工** | 企业类型用户的扩展信息 | EnterpriseUser |
|
||||||
| **权限** | 功能访问控制点 | Permission |
|
| **项目员工** | 项目类型员工的扩展信息,含班次、岗位状态 | ProjectStaff |
|
||||||
| **项目** | 多租户隔离单位 | Project |
|
| **住户** | 业主/家属/租户,含认证流程 | Resident |
|
||||||
| **访客凭证** | 临时访问授权 | VisitorCredential |
|
| **角色** | 权限集合,支持系统级/项目级/部门级 | Role |
|
||||||
|
| **权限** | 功能访问控制点,支持菜单/按钮/API三种类型 | Permission |
|
||||||
|
| **部门** | 组织架构节点,支持树形结构 | Dept |
|
||||||
|
| **用户-项目关联** | 用户参与项目的多对多关系 | UserProject |
|
||||||
|
| **项目员工角色** | 项目员工在项目中的角色分配 | ProjectStaffRole |
|
||||||
|
| **数据访问授权** | 细粒度数据访问控制记录(接口预留) | DataAccess |
|
||||||
|
| **审计日志** | 操作审计记录 | AuditLog |
|
||||||
|
| **房屋空间** | 项目下的房屋/空间信息 | Space |
|
||||||
|
| **住户-房屋关联** | 住户与房屋的绑定关系 | ResidentSpace |
|
||||||
|
| **系统配置** | 键值对形式的系统参数 | SysConfig |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -119,8 +131,8 @@ public class User {
|
||||||
private UUID id;
|
private UUID id;
|
||||||
|
|
||||||
private String username; // 登录账号
|
private String username; // 登录账号
|
||||||
private String password; // 加密密码
|
private String password; // BCrypt加密密码
|
||||||
private String salt; // 密码盐值
|
private String salt; // 密码盐值(BCrypt模式下冗余)
|
||||||
|
|
||||||
private String realName; // 真实姓名
|
private String realName; // 真实姓名
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
@ -128,6 +140,9 @@ public class User {
|
||||||
private String avatar; // 头像URL
|
private String avatar; // 头像URL
|
||||||
|
|
||||||
private UserStatus status; // ACTIVE/LOCKED/DISABLED
|
private UserStatus status; // ACTIVE/LOCKED/DISABLED
|
||||||
|
private UserType userType; // ENTERPRISE/PROJECT_STAFF/RESIDENT/CUSTOMER
|
||||||
|
private UUID deptId; // 所属部门ID
|
||||||
|
|
||||||
private LocalDateTime lastLoginTime;
|
private LocalDateTime lastLoginTime;
|
||||||
private String lastLoginIp;
|
private String lastLoginIp;
|
||||||
|
|
||||||
|
|
@ -149,6 +164,13 @@ public enum UserStatus {
|
||||||
LOCKED("锁定"),
|
LOCKED("锁定"),
|
||||||
DISABLED("禁用");
|
DISABLED("禁用");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum UserType {
|
||||||
|
ENTERPRISE("企业员工"),
|
||||||
|
PROJECT_STAFF("项目员工"),
|
||||||
|
RESIDENT("住户"),
|
||||||
|
CUSTOMER("客户");
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Role(角色)
|
#### Role(角色)
|
||||||
|
|
@ -166,15 +188,13 @@ public class Role {
|
||||||
private String code; // 角色编码
|
private String code; // 角色编码
|
||||||
private String description; // 角色描述
|
private String description; // 角色描述
|
||||||
|
|
||||||
private RoleType type; // SYSTEM(系统预设)/CUSTOM(自定义)
|
private RoleType type; // SYSTEM(系统级)/PROJECT(项目级)/DEPARTMENT(部门级)
|
||||||
|
|
||||||
// 数据权限范围(四级)
|
// 数据权限范围(四级)
|
||||||
private DataScope dataScope; // ALL/PROJECT/DEPARTMENT/SELF
|
private DataScope dataScope; // ALL/PROJECT/DEPARTMENT/SELF
|
||||||
|
|
||||||
// 新增:业务属性
|
// 角色状态(枚举替代布尔值)
|
||||||
private BusinessType businessType; // 业务类型
|
private RoleStatus status; // ENABLED(启用)/DISABLED(禁用)
|
||||||
private TerminalType terminalType; // 终端类型限制
|
|
||||||
private Boolean isFrontline; // 是否一线执行人员
|
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.LAZY)
|
@ManyToMany(fetch = FetchType.LAZY)
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
|
|
@ -184,14 +204,19 @@ public class Role {
|
||||||
)
|
)
|
||||||
private List<Permission> permissions;
|
private List<Permission> permissions;
|
||||||
|
|
||||||
private Boolean enabled;
|
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RoleType {
|
public enum RoleType {
|
||||||
SYSTEM("系统预设"),
|
SYSTEM("系统级"),
|
||||||
CUSTOM("自定义");
|
PROJECT("项目级"),
|
||||||
|
DEPARTMENT("部门级");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RoleStatus {
|
||||||
|
ENABLED("启用"),
|
||||||
|
DISABLED("禁用");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据权限范围枚举(四级)
|
// 数据权限范围枚举(四级)
|
||||||
|
|
@ -201,26 +226,10 @@ public enum DataScope {
|
||||||
DEPARTMENT("本部门数据"),
|
DEPARTMENT("本部门数据"),
|
||||||
SELF("仅本人数据");
|
SELF("仅本人数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 业务类型枚举
|
|
||||||
public enum BusinessType {
|
|
||||||
MANAGEMENT("管理类"),
|
|
||||||
ENGINEERING("工程类"),
|
|
||||||
SECURITY("安保类"),
|
|
||||||
CLEANING("保洁类"),
|
|
||||||
CUSTOMER_SERVICE("客服类"),
|
|
||||||
FINANCE("财务类");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 终端类型枚举
|
|
||||||
public enum TerminalType {
|
|
||||||
ALL("全终端"),
|
|
||||||
ADMIN_ONLY("仅管理后台"),
|
|
||||||
MOBILE_ONLY("仅移动端"),
|
|
||||||
OWNER_APP("业主端");
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **注意**: 实际代码中角色通过数据库动态管理,无硬编码预定义角色列表。原设计中的 businessType/terminalType/isFrontline 业务属性字段未实现,如需使用需后续补充。
|
||||||
|
|
||||||
#### Permission(权限)
|
#### Permission(权限)
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
@ -230,62 +239,40 @@ public enum TerminalType {
|
||||||
public class Permission {
|
public class Permission {
|
||||||
@Id
|
@Id
|
||||||
private UUID id;
|
private UUID id;
|
||||||
private UUID projectId; // 项目隔离(NULL表示系统级)
|
|
||||||
|
|
||||||
private String name; // 权限名称
|
private String name; // 权限名称
|
||||||
private String code; // 权限编码: module:resource:action
|
private String code; // 权限编码: module:resource:action
|
||||||
private PermissionType type; // MENU/BUTTON/API
|
private String type; // 权限类型: MENU/BUTTON/API(String类型)
|
||||||
|
|
||||||
// 模块和资源信息
|
// 资源信息
|
||||||
private String module; // 模块标识: system/mdm/ops/finance
|
private String resource; // 资源路径(API类型)
|
||||||
private String resource; // 资源类型: user/role/work_order
|
private String method; // HTTP方法(API类型)
|
||||||
private String action; // 操作类型: view/create/edit/delete/assign
|
|
||||||
|
|
||||||
// 菜单属性
|
private String description; // 权限描述
|
||||||
private String path; // 路由路径
|
|
||||||
private String component; // 组件路径
|
|
||||||
private String icon; // 图标
|
|
||||||
private Integer sortOrder; // 排序
|
|
||||||
|
|
||||||
// 树形结构
|
// 树形结构(简化:parentCode替代parentId)
|
||||||
private UUID parentId; // 父权限ID
|
private String parentCode; // 父权限代码(替代原 parentId UUID引用)
|
||||||
private Integer level; // 层级
|
private Integer sortOrder; // 排序序号
|
||||||
|
|
||||||
// API属性
|
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "permissions")
|
||||||
private String apiMethod; // GET/POST/PUT/DELETE
|
private List<Role> roles;
|
||||||
private String apiPath; // API路径
|
|
||||||
|
|
||||||
private Boolean enabled;
|
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PermissionType {
|
|
||||||
MENU("菜单"),
|
|
||||||
BUTTON("按钮"),
|
|
||||||
API("接口");
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **TODO**: Permission 当前实现缺少菜单路由属性(path/component/icon),以及模块/资源/操作拆分字段(module/resource/action),需后续补充以支持前端菜单渲染和权限树展示。
|
||||||
|
|
||||||
### 3.2 关联实体
|
### 3.2 关联实体
|
||||||
|
|
||||||
#### UserRole(用户角色关联)
|
#### UserRole(用户角色关联)
|
||||||
|
|
||||||
|
> **注意**: 实际代码中 UserRole 已简化为 JPA 自动管理的 M2M 中间表 `auth_user_role`,仅包含 user_id 和 role_id 两个外键列,去掉了原设计中的 projectId 和 isDefault 字段。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Entity
|
// auth_user_role 关联表(JPA自动管理,无独立实体)
|
||||||
@Table(name = "auth_user_role")
|
// 字段: user_id (FK → auth_user), role_id (FK → auth_role)
|
||||||
@Data
|
|
||||||
public class UserRole {
|
|
||||||
@Id
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
private UUID userId;
|
|
||||||
private UUID roleId;
|
|
||||||
private UUID projectId; // 项目隔离
|
|
||||||
private Boolean isDefault; // 是否默认角色
|
|
||||||
|
|
||||||
private LocalDateTime createdAt;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### RolePermission(角色权限关联)
|
#### RolePermission(角色权限关联)
|
||||||
|
|
@ -788,117 +775,89 @@ public class VisitorCredentialService {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 九、操作日志审计
|
## 九、审计日志
|
||||||
|
|
||||||
### 9.1 操作日志设计
|
### 9.1 审计日志设计
|
||||||
|
|
||||||
|
> **注意**: 实际代码中操作日志已更名为审计日志(AuditLog),表名从 `auth_operation_log` 改为 `sys_audit_log`,字段结构也有较大变更,增加了目标追踪(targetType/targetId)、操作内容(content)、操作结果(result)、租户ID(tenantId)等字段,并采用异步持久化。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "auth_operation_log")
|
@Table(name = "sys_audit_log")
|
||||||
@Data
|
@Data
|
||||||
public class OperationLog {
|
public class AuditLog {
|
||||||
@Id
|
@Id
|
||||||
private UUID id;
|
private UUID id;
|
||||||
private UUID projectId;
|
|
||||||
|
|
||||||
private UUID userId;
|
private UUID userId;
|
||||||
private String username;
|
private String username; // 操作用户名
|
||||||
private String realName;
|
private String operation; // 操作描述
|
||||||
|
private String module; // 模块标识
|
||||||
|
private ActionType action; // 操作类型枚举
|
||||||
|
|
||||||
private String module;
|
// 目标追踪(新增)
|
||||||
private String operation;
|
private String targetType; // 目标类型
|
||||||
private String description;
|
private String targetId; // 目标ID
|
||||||
|
|
||||||
private String requestMethod;
|
// 操作详情(替代原 requestBody/responseBody)
|
||||||
private String requestUrl;
|
private String content; // 操作内容
|
||||||
private String requestParams;
|
private String params; // 请求参数
|
||||||
private String requestBody;
|
private String result; // 操作结果
|
||||||
|
|
||||||
private Integer responseStatus;
|
|
||||||
private String responseBody;
|
|
||||||
|
|
||||||
private Long executionTime;
|
|
||||||
private String ipAddress;
|
private String ipAddress;
|
||||||
private String userAgent;
|
private String userAgent;
|
||||||
|
private String requestUrl;
|
||||||
|
private String requestMethod;
|
||||||
|
private Integer executionTimeMs; // 执行耗时(ms)
|
||||||
|
|
||||||
private Boolean hasError;
|
private AuditStatus status; // SUCCESS/FAIL
|
||||||
private String errorMessage;
|
private String errorMsg; // 错误信息
|
||||||
|
|
||||||
|
private UUID tenantId; // 租户ID(新增)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ActionType {
|
||||||
|
CREATE, UPDATE, DELETE, QUERY, VIEW,
|
||||||
|
LOGIN, LOGOUT, EXPORT, IMPORT, ASSIGN, REVOKE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AuditStatus {
|
||||||
|
SUCCESS("成功"),
|
||||||
|
FAIL("失败");
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9.2 日志记录切面
|
### 9.2 日志记录注解
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Aspect
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class OperationLogAspect {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private OperationLogRepository operationLogRepository;
|
|
||||||
|
|
||||||
@Around("@annotation(operationLog)")
|
|
||||||
public Object around(ProceedingJoinPoint point, LogOperation operationLog) throws Throwable {
|
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
|
||||||
|
|
||||||
UUID userId = SecurityUtils.getCurrentUserId();
|
|
||||||
String username = SecurityUtils.getCurrentUsername();
|
|
||||||
|
|
||||||
OperationLog logEntry = new OperationLog();
|
|
||||||
logEntry.setId(UUID.randomUUID());
|
|
||||||
logEntry.setProjectId(ProjectContextHolder.getCurrentProjectId());
|
|
||||||
logEntry.setUserId(userId);
|
|
||||||
logEntry.setUsername(username);
|
|
||||||
logEntry.setModule(operationLog.module());
|
|
||||||
logEntry.setOperation(operationLog.operation());
|
|
||||||
logEntry.setDescription(operationLog.description());
|
|
||||||
logEntry.setRequestMethod(request.getMethod());
|
|
||||||
logEntry.setRequestUrl(request.getRequestURI());
|
|
||||||
logEntry.setIpAddress(getClientIp(request));
|
|
||||||
logEntry.setUserAgent(request.getHeader("User-Agent"));
|
|
||||||
logEntry.setCreatedAt(LocalDateTime.now());
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Object result = point.proceed();
|
|
||||||
|
|
||||||
logEntry.setResponseStatus(200);
|
|
||||||
logEntry.setHasError(false);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
logEntry.setHasError(true);
|
|
||||||
logEntry.setErrorMessage(e.getMessage());
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
logEntry.setExecutionTime(System.currentTimeMillis() - startTime);
|
|
||||||
operationLogRepository.save(logEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface LogOperation {
|
public @interface OperationLog {
|
||||||
String module();
|
|
||||||
String operation();
|
String operation();
|
||||||
String description();
|
String module();
|
||||||
String businessType() default "";
|
AuditLog.ActionType action();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **业务规则**:
|
||||||
|
> - 审计日志通过 `@OperationLog` 注解 + AOP 切面自动记录
|
||||||
|
> - 采用异步持久化(`auditLogExecutor` 线程池),避免影响业务性能
|
||||||
|
> - 查询窗口强制限制30天,超过30天的查询自动截断起始时间
|
||||||
|
> - 归档策略:90天以上日志归档(当前实现为直接删除,TODO: 导出至对象存储)
|
||||||
|
> - 记录模块:USER / ROLE / PERMISSION / PROJECT / AUTH / DEPT / PROJECT_MEMBER / SYSTEM
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 十、API 接口
|
## 十、API 接口
|
||||||
|
|
||||||
|
> **注意**: 实际代码中 API 路径已去掉 v1 版本号,统一为 `/api/auth` 前缀。
|
||||||
|
|
||||||
### 10.1 认证接口
|
### 10.1 认证接口
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/auth")
|
@RequestMapping("/api/auth")
|
||||||
@Tag(name = "认证管理")
|
@Tag(name = "认证管理")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
|
|
@ -910,29 +869,30 @@ public class AuthController {
|
||||||
@Operation(summary = "用户登出")
|
@Operation(summary = "用户登出")
|
||||||
public Result<Void> logout();
|
public Result<Void> logout();
|
||||||
|
|
||||||
@PostMapping("/refresh-token")
|
@PostMapping("/refresh")
|
||||||
@Operation(summary = "刷新Token")
|
@Operation(summary = "刷新Token")
|
||||||
public Result<TokenResponse> refreshToken(@RequestBody RefreshTokenRequest request);
|
public Result<TokenResponse> refreshToken();
|
||||||
|
|
||||||
@GetMapping("/current-user")
|
@GetMapping("/me")
|
||||||
@Operation(summary = "获取当前用户信息")
|
@Operation(summary = "获取当前用户信息")
|
||||||
public Result<UserVO> getCurrentUser();
|
public Result<UserVO> getCurrentUser();
|
||||||
|
|
||||||
@PostMapping("/change-password")
|
|
||||||
@Operation(summary = "修改密码")
|
|
||||||
public Result<Void> changePassword(@RequestBody @Valid ChangePasswordRequest request);
|
|
||||||
|
|
||||||
@GetMapping("/user-permissions")
|
|
||||||
@Operation(summary = "获取用户权限信息")
|
|
||||||
public Result<UserPermissionsResponse> getUserPermissions();
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **登录流程增强**(实际代码新增):
|
||||||
|
> 1. 检查登录失败锁定(Redis,5次失败锁定10分钟)
|
||||||
|
> 2. 查询用户(含角色)
|
||||||
|
> 3. 验证密码(BCrypt)
|
||||||
|
> 4. 检查用户状态(LOCKED/DISABLED拒绝)
|
||||||
|
> 5. 收集所有角色(用户直接角色 + 项目员工角色)
|
||||||
|
> 6. 生成JWT Token(含userId、username、roles claims)
|
||||||
|
> 7. 返回Token和用户基本信息
|
||||||
|
|
||||||
### 10.2 用户管理接口
|
### 10.2 用户管理接口
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/auth/users")
|
@RequestMapping("/api/auth/users")
|
||||||
@Tag(name = "用户管理")
|
@Tag(name = "用户管理")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
|
|
@ -956,63 +916,264 @@ public class UserController {
|
||||||
@Operation(summary = "删除用户")
|
@Operation(summary = "删除用户")
|
||||||
public Result<Void> delete(@PathVariable UUID id);
|
public Result<Void> delete(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@PutMapping("/{id}/password")
|
||||||
|
@Operation(summary = "修改密码")
|
||||||
|
public Result<Void> changePassword(@PathVariable UUID id, @RequestBody @Valid ChangePasswordRequest request);
|
||||||
|
|
||||||
@PostMapping("/{id}/roles")
|
@PostMapping("/{id}/roles")
|
||||||
@Operation(summary = "分配角色")
|
@Operation(summary = "分配角色")
|
||||||
public Result<Void> assignRoles(@PathVariable UUID id, @RequestBody UserRoleAssignRequest request);
|
public Result<Void> assignRoles(@PathVariable UUID id, @RequestBody UserRoleAssignRequest request);
|
||||||
|
|
||||||
@PostMapping("/{id}/status")
|
@GetMapping("/{id}/projects")
|
||||||
@Operation(summary = "修改用户状态")
|
@Operation(summary = "获取用户项目列表")
|
||||||
public Result<Void> changeStatus(@PathVariable UUID id, @RequestBody UserStatusChangeRequest request);
|
public Result<List<UserProjectVO>> getUserProjects(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@PostMapping("/{id}/projects")
|
||||||
|
@Operation(summary = "添加用户到项目")
|
||||||
|
public Result<Void> addUserToProject(@PathVariable UUID id, @RequestBody UserProjectRequest request);
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}/projects/{projectId}")
|
||||||
|
@Operation(summary = "从项目移除用户")
|
||||||
|
public Result<Void> removeUserFromProject(@PathVariable UUID id, @PathVariable UUID projectId);
|
||||||
|
|
||||||
|
@GetMapping("/enterprise")
|
||||||
|
@Operation(summary = "获取企业员工列表")
|
||||||
|
public Result<List<EnterpriseUserVO>> getEnterpriseUsers();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **密码修改规则**(实际代码新增):
|
||||||
|
> - 需验证原密码
|
||||||
|
> - 新密码需通过强度校验(8-20位,大小写+数字+特殊字符)
|
||||||
|
> - 弱密码检测(黑名单:password、123456、admin等)
|
||||||
|
|
||||||
### 10.3 角色权限接口
|
### 10.3 角色权限接口
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/auth")
|
@RequestMapping("/api/auth/roles")
|
||||||
@Tag(name = "角色权限管理")
|
@Tag(name = "角色管理")
|
||||||
public class RolePermissionController {
|
public class RoleController {
|
||||||
|
|
||||||
@PostMapping("/roles")
|
@PostMapping
|
||||||
@Operation(summary = "创建角色")
|
@Operation(summary = "创建角色")
|
||||||
public Result<RoleVO> createRole(@RequestBody @Valid RoleCreateRequest request);
|
public Result<RoleVO> createRole(@RequestBody @Valid RoleCreateRequest request);
|
||||||
|
|
||||||
@GetMapping("/roles")
|
@GetMapping
|
||||||
@Operation(summary = "查询角色列表")
|
@Operation(summary = "分页查询角色列表")
|
||||||
public Result<List<RoleVO>> listRoles();
|
public Result<Page<RoleVO>> listRoles();
|
||||||
|
|
||||||
@PutMapping("/roles/{id}")
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "获取角色详情")
|
||||||
|
public Result<RoleVO> getRoleById(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@GetMapping("/project/{projectId}")
|
||||||
|
@Operation(summary = "根据项目ID查询角色")
|
||||||
|
public Result<List<RoleVO>> getRolesByProject(@PathVariable UUID projectId);
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
@Operation(summary = "更新角色")
|
@Operation(summary = "更新角色")
|
||||||
public Result<RoleVO> updateRole(@PathVariable UUID id, @RequestBody @Valid RoleUpdateRequest request);
|
public Result<RoleVO> updateRole(@PathVariable UUID id, @RequestBody @Valid RoleUpdateRequest request);
|
||||||
|
|
||||||
@DeleteMapping("/roles/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@Operation(summary = "删除角色")
|
@Operation(summary = "删除角色")
|
||||||
public Result<Void> deleteRole(@PathVariable UUID id);
|
public Result<Void> deleteRole(@PathVariable UUID id);
|
||||||
|
|
||||||
@PostMapping("/roles/{id}/permissions")
|
@PostMapping("/{id}/permissions")
|
||||||
@Operation(summary = "分配权限")
|
@Operation(summary = "分配权限")
|
||||||
public Result<Void> assignPermissions(@PathVariable UUID id, @RequestBody RolePermissionAssignRequest request);
|
public Result<Void> assignPermissions(@PathVariable UUID id, @RequestBody RolePermissionAssignRequest request);
|
||||||
|
|
||||||
@GetMapping("/permissions")
|
@GetMapping("/{id}/permissions")
|
||||||
@Operation(summary = "查询权限列表")
|
@Operation(summary = "获取角色权限列表")
|
||||||
public Result<List<PermissionVO>> listPermissions();
|
public Result<List<PermissionVO>> getRolePermissions(@PathVariable UUID id);
|
||||||
|
|
||||||
@GetMapping("/permissions/tree")
|
@GetMapping("/{id}/users")
|
||||||
@Operation(summary = "获取权限树")
|
@Operation(summary = "获取拥有某角色的用户")
|
||||||
public Result<List<PermissionTreeVO>> getPermissionTree();
|
public Result<List<UserVO>> getRoleUsers(@PathVariable UUID id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
@GetMapping("/users/{id}/permissions")
|
### 10.4 权限管理接口
|
||||||
@Operation(summary = "获取用户权限")
|
|
||||||
public Result<List<PermissionVO>> getUserPermissions(@PathVariable UUID id);
|
|
||||||
|
|
||||||
@GetMapping("/users/{id}/menus")
|
```java
|
||||||
@Operation(summary = "获取用户菜单")
|
@RestController
|
||||||
public Result<List<MenuVO>> getUserMenus(@PathVariable UUID id);
|
@RequestMapping("/api/auth/permissions")
|
||||||
|
@Tag(name = "权限管理")
|
||||||
|
public class PermissionController {
|
||||||
|
|
||||||
@PostMapping("/check")
|
@PostMapping
|
||||||
@Operation(summary = "校验权限")
|
@Operation(summary = "创建权限")
|
||||||
public Result<Boolean> checkPermission(@RequestBody PermissionCheckRequest request);
|
public Result<PermissionVO> createPermission(@RequestBody @Valid PermissionCreateRequest request);
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "分页查询权限列表")
|
||||||
|
public Result<Page<PermissionVO>> listPermissions();
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "获取权限详情")
|
||||||
|
public Result<PermissionVO> getPermissionById(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@GetMapping("/type/{type}")
|
||||||
|
@Operation(summary = "根据类型查询权限")
|
||||||
|
public Result<List<PermissionVO>> getPermissionsByType(@PathVariable String type);
|
||||||
|
|
||||||
|
@GetMapping("/menus")
|
||||||
|
@Operation(summary = "查询所有菜单权限")
|
||||||
|
public Result<List<PermissionVO>> listMenuPermissions();
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@Operation(summary = "更新权限")
|
||||||
|
public Result<PermissionVO> updatePermission(@PathVariable UUID id, @RequestBody @Valid PermissionUpdateRequest request);
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "删除权限")
|
||||||
|
public Result<Void> deletePermission(@PathVariable UUID id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.5 部门管理接口(新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth/depts")
|
||||||
|
@Tag(name = "部门管理")
|
||||||
|
public class DeptController {
|
||||||
|
|
||||||
|
@GetMapping("/tree")
|
||||||
|
@Operation(summary = "获取部门树")
|
||||||
|
public Result<List<DeptTreeVO>> getDeptTree();
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "获取所有启用部门")
|
||||||
|
public Result<List<DeptVO>> listActiveDepts();
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "获取部门详情")
|
||||||
|
public Result<DeptVO> getById(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "创建部门")
|
||||||
|
public Result<DeptVO> create(@RequestBody @Valid DeptCreateRequest request);
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@Operation(summary = "更新部门")
|
||||||
|
public Result<DeptVO> update(@PathVariable UUID id, @RequestBody @Valid DeptUpdateRequest request);
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "删除部门")
|
||||||
|
public Result<Void> delete(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@GetMapping("/{deptId}/members")
|
||||||
|
@Operation(summary = "获取部门成员")
|
||||||
|
public Result<List<UserVO>> getDeptMembers(@PathVariable UUID deptId);
|
||||||
|
|
||||||
|
@GetMapping("/by-type/{deptType}")
|
||||||
|
@Operation(summary = "根据类型查询部门")
|
||||||
|
public Result<List<DeptVO>> getDeptsByType(@PathVariable String deptType);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **部门类型**: ADMIN(行政管理) / ENGINEERING(工程部) / SECURITY(安保部) / CS(客服部) / CLEANING(保洁部)
|
||||||
|
|
||||||
|
### 10.6 项目成员管理接口(新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth/projects")
|
||||||
|
@Tag(name = "项目成员管理")
|
||||||
|
public class ProjectMemberController {
|
||||||
|
|
||||||
|
@GetMapping("/{projectId}/members")
|
||||||
|
@Operation(summary = "查询项目成员列表(分页)")
|
||||||
|
public Result<Page<ProjectStaffVO>> listMembers(@PathVariable UUID projectId);
|
||||||
|
|
||||||
|
@GetMapping("/{projectId}/available-members")
|
||||||
|
@Operation(summary = "获取可添加成员(企业员工)")
|
||||||
|
public Result<List<EnterpriseUserVO>> getAvailableMembers(@PathVariable UUID projectId);
|
||||||
|
|
||||||
|
@PostMapping("/{projectId}/members")
|
||||||
|
@Operation(summary = "添加项目成员")
|
||||||
|
public Result<Void> addMember(@PathVariable UUID projectId, @RequestBody AddMemberRequest request);
|
||||||
|
|
||||||
|
@DeleteMapping("/{projectId}/members/{userId}")
|
||||||
|
@Operation(summary = "移除项目成员")
|
||||||
|
public Result<Void> removeMember(@PathVariable UUID projectId, @PathVariable UUID userId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.7 数据访问授权接口(新增,接口预留)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/data-access")
|
||||||
|
@Tag(name = "数据访问授权")
|
||||||
|
public class DataAccessController {
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "授予数据访问权限")
|
||||||
|
public Result<DataAccessVO> grantAccess(@RequestBody @Valid DataAccessGrantRequest request);
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "撤销数据访问权限")
|
||||||
|
public Result<Void> revokeAccess(@PathVariable UUID id);
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "查询数据访问记录")
|
||||||
|
public Result<List<DataAccessVO>> queryAccess(@RequestParam String dataType, @RequestParam UUID dataId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.8 审计日志接口(新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/audit-logs")
|
||||||
|
@Tag(name = "审计日志")
|
||||||
|
public class AuditLogController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "分页查询审计日志")
|
||||||
|
public Result<Page<AuditLogVO>> page(AuditLogQueryRequest request);
|
||||||
|
|
||||||
|
@GetMapping("/modules")
|
||||||
|
@Operation(summary = "获取模块列表")
|
||||||
|
public Result<List<String>> getModules();
|
||||||
|
|
||||||
|
@GetMapping("/actions")
|
||||||
|
@Operation(summary = "获取操作类型列表")
|
||||||
|
public Result<List<String>> getActions();
|
||||||
|
|
||||||
|
@GetMapping("/stats")
|
||||||
|
@Operation(summary = "获取最近30天日志统计")
|
||||||
|
public Result<AuditStatsVO> getStats();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **查询约束**: 强制限制查询范围不超过30天,防止大量数据查询。
|
||||||
|
|
||||||
|
### 10.9 系统配置接口(新增)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/config")
|
||||||
|
@Tag(name = "系统配置")
|
||||||
|
public class SysConfigController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "获取所有配置项")
|
||||||
|
public Result<List<SysConfigVO>> listConfigs();
|
||||||
|
|
||||||
|
@GetMapping("/{configKey}")
|
||||||
|
@Operation(summary = "根据键获取配置")
|
||||||
|
public Result<SysConfigVO> getConfig(@PathVariable String configKey);
|
||||||
|
|
||||||
|
@PutMapping("/{configKey}")
|
||||||
|
@Operation(summary = "更新单个配置")
|
||||||
|
public Result<Void> updateConfig(@PathVariable String configKey, @RequestBody String configValue);
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "批量更新配置")
|
||||||
|
public Result<Void> batchUpdateConfigs(@RequestBody List<SysConfigUpdateRequest> requests);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1024,28 +1185,48 @@ public class RolePermissionController {
|
||||||
|
|
||||||
| 功能模块 | 实现状态 | 备注 |
|
| 功能模块 | 实现状态 | 备注 |
|
||||||
|---------|---------|------|
|
|---------|---------|------|
|
||||||
| User | 🟢 已实现 | 基础CRUD |
|
| User | 🟢 已实现 | 基础CRUD,含 userType/deptId 扩展字段 |
|
||||||
| Role | 🟢 已实现 | 基础CRUD |
|
| Role | 🟢 已实现 | 基础CRUD,三级分类(SYSTEM/PROJECT/DEPARTMENT),RoleStatus枚举 |
|
||||||
| Permission | 🟢 已实现 | 基础CRUD |
|
| Permission | 🟢 已实现 | 基础CRUD,简化结构(parentCode替代parentId) |
|
||||||
| JWT认证 | 🟢 已实现 | Token生成/验证 |
|
| JWT认证 | 🟢 已实现 | Token生成/验证,含登录失败锁定(Redis) |
|
||||||
| 项目上下文 | 🟢 已实现 | 项目隔离 |
|
| 项目上下文 | 🟢 已实现 | 项目隔离 |
|
||||||
| 访客凭证 | 🟢 已实现 | 二维码生成/验证 |
|
| 审计日志 | 🟢 已实现 | AuditLog(原OperationLog),异步持久化,30天查询窗口 |
|
||||||
| 数据权限 | 🟡 部分实现 | 框架已搭建,需补充PROJECT级别 |
|
| 部门管理 | 🟢 已实现 | 树形结构,5种部门类型 |
|
||||||
| 操作日志 | 🟢 已实现 | AOP切面记录 |
|
| 项目成员管理 | 🟢 已实现 | ProjectStaff + ProjectStaffRole |
|
||||||
|
| 企业员工 | 🟢 已实现 | EnterpriseUser扩展实体 |
|
||||||
|
| 住户管理 | 🟢 已实现 | Resident + ResidentSpace + Space |
|
||||||
|
| 用户项目关联 | 🟢 已实现 | UserProject多对多 |
|
||||||
|
| 系统配置 | 🟢 已实现 | SysConfig键值对 |
|
||||||
|
| 数据访问授权 | 🟢 已实现 | DataAccess接口预留 |
|
||||||
|
| 登录失败锁定 | 🟢 已实现 | Redis实现,5次失败锁10分钟 |
|
||||||
|
| 密码强度校验 | 🟢 已实现 | 8-20位+大小写数字特殊字符+弱密码检测 |
|
||||||
|
| 数据权限 | 🟡 部分实现 | DataScopeService提供判断方法,未实现SQL自动注入 |
|
||||||
| 按钮级权限 | ⏳ 待实现 | 细粒度操作权限 |
|
| 按钮级权限 | ⏳ 待实现 | 细粒度操作权限 |
|
||||||
| 状态驱动权限 | ⏳ 待实现 | 根据业务状态控制操作 |
|
| 状态驱动权限 | ⏳ 待实现 | 根据业务状态控制操作 |
|
||||||
| 字段脱敏 | ⏳ 待实现 | 敏感字段脱敏 |
|
| 字段脱敏 | ⏳ 待实现 | 敏感字段脱敏 |
|
||||||
|
| 访客凭证 | 🔴 未实现 | 原设计有但未在认证模块实现 |
|
||||||
|
| 角色业务属性 | 🔴 未实现 | businessType/terminalType/isFrontline未实现 |
|
||||||
|
| 权限树端点 | 🔴 未实现 | GET /permissions/tree |
|
||||||
|
| 权限校验端点 | 🔴 未实现 | POST /check |
|
||||||
|
| 用户菜单端点 | 🔴 未实现 | GET /users/{id}/menus |
|
||||||
|
| 用户权限查询端点 | 🔴 未实现 | GET /users/{id}/permissions |
|
||||||
|
| Permission菜单路由属性 | 🔴 未实现 | path/component/icon字段缺失 |
|
||||||
|
|
||||||
### 11.2 改进计划
|
### 11.2 改进计划
|
||||||
|
|
||||||
| 优先级 | 改进项 | 说明 |
|
| 优先级 | 改进项 | 说明 |
|
||||||
|--------|--------|------|
|
|--------|--------|------|
|
||||||
| P0 | 按钮级权限控制 | 新增操作按钮权限编码 |
|
| P0 | Permission菜单路由属性 | 增加 path/component/icon/module/action 字段,支持前端菜单渲染 |
|
||||||
| P0 | 状态驱动权限 | 工单状态与操作联动 |
|
| P0 | 权限树端点 | 实现 GET /permissions/tree |
|
||||||
| P1 | 数据权限完善 | 补充PROJECT级别 |
|
| P0 | 用户菜单端点 | 实现 GET /users/{id}/menus |
|
||||||
| P1 | 扫码强制机制 | 巡检扫码策略配置 |
|
| P0 | 权限校验端点 | 实现 POST /check |
|
||||||
|
| P1 | 数据权限完善 | 实现SQL自动注入,补充PROJECT级别 |
|
||||||
|
| P1 | 角色业务属性 | 新增 businessType/terminalType/isFrontline 字段 |
|
||||||
|
| P1 | 按钮级权限控制 | 新增操作按钮权限编码 |
|
||||||
|
| P1 | 状态驱动权限 | 工单状态与操作联动 |
|
||||||
| P2 | 字段脱敏 | 敏感字段脱敏 |
|
| P2 | 字段脱敏 | 敏感字段脱敏 |
|
||||||
| P2 | 角色业务属性 | 新增业务类型、终端类型字段 |
|
| P2 | 访客凭证 | 在认证模块实现访客凭证管理 |
|
||||||
|
| P2 | 审计日志归档 | 导出至对象存储替代直接删除 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -1227,6 +1408,7 @@ CREATE INDEX idx_operation_log_created ON auth_operation_log(created_at);
|
||||||
| 2026-02-27 | 新增数据权限四级范围 | - |
|
| 2026-02-27 | 新增数据权限四级范围 | - |
|
||||||
| 2026-02-27 | 新增状态驱动权限设计 | - |
|
| 2026-02-27 | 新增状态驱动权限设计 | - |
|
||||||
| 2026-02-27 | 新增角色业务属性字段 | - |
|
| 2026-02-27 | 新增角色业务属性字段 | - |
|
||||||
|
| 2026-04-26 | 反向同步实际代码实现:Role.type改为SYSTEM/PROJECT/DEPARTMENT三级分类;Role.status改为RoleStatus枚举;Permission结构简化(parentCode替代parentId);UserRole简化为M2M中间表;OperationLog更名为AuditLog;新增实体(EnterpriseUser/ProjectStaff/ProjectStaffRole/Resident/ResidentSpace/Space/UserProject/DataAccess/Dept/SysConfig);新增API(部门管理/项目成员管理/数据访问授权/审计日志/系统配置/企业员工/用户项目关联);新增业务规则(登录失败锁定/密码强度校验/审计日志增强/项目成员角色/部门类型/住户认证);角色体系改为数据库动态管理;API路径去掉v1版本号;标注TODO: Permission需增加菜单路由属性 | - |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue