# ISSUE-01-05 宿舍房间与楼宇基础数据维护开发验收文档 ## 1. 任务概述与环境基线 - **任务 ID:** `ISSUE-01-05` - **任务名称:** 宿舍房间与楼宇基础数据维护 - **所属 Epic:** `EPIC-01 基础主数据必要集合` - **优先级:** P0 - **工作区目录:** `/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-01-05-dormitory` - **当前分支:** `feat/issue-01-05-dormitory` - **正确基线 Commit:** `4682df03c566776562b693ed5cb48fd98cda2472` (`integration/wave1a-foundation`) - 基线祖先校验状态:`PASS`(当前分支直接派生自该正式基线) - **API 契约基准:** `docs/api-v2/02_module_a_base_resource_api.md` 4.4 节、`docs/api-v2/06_dto_vo_catalog.md`、`docs/api-v2/10_permission_and_error_codes.md` - **数据库基线:** Database V2 (`train_dormitory_info`) - **Generator 冻结策略:** `YES (单表结构)`(依据 `docs/issues-v1/02_p0_issue_backlog.md` 与 `docs/issues-v1/05_agent_assignment.md`) - **本轮 Generator 实际执行:** `Generator actually executed = NO`(骨架已在此前生成,本次按 V2 契约与手写逻辑完成重构与对齐,未重新执行 Generator) - **核心业务目标:** 建立宿舍物理房间主数据台账(楼栋、楼层、房间号、核定容量、适用性别、门锁SN),提供同楼栋房号查重与防重、实时在住人数与剩余床位只读动态聚合、当前入住学员列表回显以及楼栋-楼层-房间三级结构化级联下拉选项。 --- ## 2. HTTP Endpoint 最终收口与物理映射 严格依照冻结契约收口 Controller 公开接口,**非冻结接口全部从 Controller 移除,不再对外公开**: - 彻底移除历史 `/train/dorminfo` 路由及 Alias; - 彻底移除 `PUT /train/dormitory`、`DELETE /train/dormitory/{dormIds}`、`POST /train/dormitory/export` 等非 P0 接口。 ### 2.1 最终公开物理 Endpoint 完整列表(共且仅有 4 个正式 P0) | 优先级 | HTTP Method | URI 路径 | Controller 方法 | 权限字符 | 适用角色 | 功能说明与契约对齐 | |:---|:---|:---|:---|:---|:---|:---| | **P0** | `GET` | `/train/dormitory/list` | `list(TrainDormitoryInfo)` | `train:dormitory:list` | 管理员, 宿管 | 分页查询宿舍列表,附带 capacity、usedCount、remainingBeds | | **P0** | `GET` | `/train/dormitory/{dormId}` | `getInfo(@PathVariable)` | `train:dormitory:list` | 管理员, 宿管 | 获取指定房间详情及当前入住人员列表 (occupants) | | **P0** | `POST` | `/train/dormitory` | `add(@Validated @RequestBody)` | `train:dormitory:edit` | 管理员, 宿管 | 新增房间(前置查重与底层唯一约束冲突均转换为 40006) | | **P0** | `GET` | `/train/dormitory/options` | `options()` | `train:dormitory:list` | 管理员, 宿管 | 获取结构化的楼栋 -> 楼层 -> 房间层级树(分房弹窗必需) | - **Contract-extra Endpoint 数量:** 0 个。 --- ## 3. DTO 与 VO 设计规范 ### 3.1 `DormitoryCreateDTO` - `dormBuilding` (String, `@NotBlank`, `@Size(max = 50)`): 宿舍楼栋 - `dormFloor` (String, `@NotBlank`, `@Size(max = 20)`): 所在楼层 - `dormRoom` (String, `@NotBlank`, `@Size(max = 30)`): 房间号 - `capacity` (Integer, `@NotNull`, `@Min(1)`, `@Max(8)`): 核定床位容量 (1~8) - `genderType` (Integer, `@NotNull`): 适用性别 (0:不限, 1:男, 2:女) - `lockDeviceSn` (String, `@Size(max = 64)`): 智能门锁设备序列号 (可选) - `remark` (String, `@Size(max = 500)`): 备注 (可选) ### 3.2 `DormitoryItemVO` - `dormId` (String): 宿舍ID主键 - `dormBuilding` (String): 宿舍楼栋 - `dormFloor` (String): 所在楼层 - `dormRoom` (String): 房间号 - `capacity` (Integer): 核定床位容量 - `usedCount` (Integer): 已住人数 (动态聚合自 `train_accommodation`) - `remainingBeds` (Integer): 剩余床位 (`capacity - usedCount`) - `genderType` (Integer): 适用性别 (0:不限, 1:男, 2:女) - `lockDeviceSn` (String): 智能门锁SN - `dormStatus` (Integer): 状态 (0:禁用, 1:可用) - `remark` (String): 备注 - `createTime` (Date): 创建时间 ### 3.3 `DormitoryOccupantVO` - `enrollmentId` (Long): 参训档案ID - `studentId` (String): 学员主档ID - `studentName` (String): 学员姓名 - `gender` (String): 性别 - `orgName` (String): 所属单位 - `bedNo` (String): 分配床位号 (可选) - `assignedTime` (Date): 分配入住时间 - `accommodationStatus` (Integer): 住宿状态 (0:预分配, 1:已入住) ### 3.4 `DormitoryDetailVO` - 包含房间基础信息与动态统计 (`dormId`, `dormBuilding`, `dormFloor`, `dormRoom`, `capacity`, `usedCount`, `remainingBeds`, `genderType`, `lockDeviceSn`, `dormStatus`, `remark`, `createTime`); - `occupants` (`List`): 当前入住学员列表(空房时返回 `[]`,严禁返回 `null`)。 ### 3.5 `DormOptionsVO` - 楼栋 -> 楼层 -> 房间三级树节点,包含 `value`, `label`, `type` ("building"/"floor"/"room"), `children`; - 叶子节点(房间)挂载 `capacity`, `usedCount`, `remainingBeds`, `genderType`, `lockDeviceSn` 元数据,方便前端分房弹窗直接消费。 --- ## 4. 业务规则与边界约束 ### 4.1 容量与性别规则 - **容量范围:** 严格限定在 $1 \le \text{capacity} \le 8$ 之间,非正数或大于 8 抛出业务校验异常。 - **适用性别:** 必须为合法枚举值(0: 不限, 1: 男, 2: 女)。 ### 4.2 动态房态聚合口径 - **依据:** `RuoYi-Vue/sql/train/v2/007_dicts.sql` 中 `train_accommodation_status` 字典定义: - `0`: 预分配 (报到前系统预先分配) - `1`: 已入住 (报到确认并已入住) - `2`: 已退宿 (培训结束已离宿) - **实际占用定义:** 仅处于“预分配”与“已入住”状态的记录占用床位,退宿后释放。 - **实际 SQL 过滤条件:** ```sql SELECT dorm_id, COUNT(1) AS used_count FROM train_accommodation WHERE accommodation_status IN (0, 1) GROUP BY dorm_id ``` `remaining_beds = (capacity - COALESCE(acc.used_count, 0))`。 ### 4.3 UNIQUE 并发语义保证 - **数据库约束:** `uq_dorm_building_room UNIQUE (dorm_building, dorm_room)`。 - **并发保护机制:** 1. 应用层前置查重:查询 `train_dormitory_info` 是否存在同楼栋同房号未删除记录,存在即抛出 `ServiceException("同一楼栋下该宿舍房号已存在", 40006)`; 2. 底层并发冲突兜底:高并发场景下两请求同时绕过前置查重时,底层 INSERT 触发唯一约束冲突抛出 `DuplicateKeyException`,Service 层显式捕获并转换为 40006 业务异常,杜绝 500 裸异常泄漏。 ### 4.4 业务边界铁律 - **Dormitory 仅代表物理房间基础资源:** 负责房间基础资产的维护与只读聚合。 - **无独立 DormBed 实体:** Phase 1 阶段严禁引入 `train_dorm_bed` 表;`bed_no` 仅为 `train_accommodation` 的可空业务字段。 - **禁止 Accommodation 写操作:** 本 Issue 严禁分房、调房、退宿、门禁下发与同步、报到入住办理以及级联物理删除。 --- ## 5. 验证结果与质量达标记录 1. **单元测试:** - 执行测试:`DormitoryServiceTest` 包含 6 个测试用例: - `testAddAndDuplicateInterception`: 前置防重与 40006 拦截 - `testConcurrentInsertDuplicateKeyConversion`: 高并发 `DuplicateKeyException` 转换为 40006 - `testCapacityAndGenderValidation`: 容量 (1~8) 与性别枚举合法性 - `testList`: 动态聚合 `usedCount` 与 `remainingBeds` - `testGet`: 房间详情与 `occupants` 列表回显 - `testOptions`: 楼栋-楼层-房间三级树结构组装 - 运行结果:`Tests run: 6, Failures: 0, Errors: 0, Skipped: 0` 全部通过。 2. **模块依赖测试:** - 命令:`mvn test -pl ruoyi-train -am -f RuoYi-Vue/pom.xml` - 运行结果:`Tests run: 20, Failures: 0, Errors: 0, Skipped: 0` 全部通过。 3. **后端 Reactor 编译:** - 命令:`mvn compile -DskipTests -f RuoYi-Vue/pom.xml` - 运行结果:8 个子模块全部 `SUCCESS`。 4. **前端 ESLint 检查:** - 命令:`./node_modules/.bin/eslint --no-ignore src/api/train/dormitory.js src/views/train/dormitory/index.vue` - 运行结果:0 errors, 0 warnings。 5. **前端生产打包 (build:prod):** - 命令:`npm run build:prod` - 运行结果:`Build complete. The dist directory is ready to be deployed.` 构建成功。构建完成后已彻底清理临时产物与软链接。 6. **格式检查 (git diff --check):** - 命令:`git diff --check` - 运行结果:无任何 trailing whitespace,洁净通过。 7. **Commit 状态:** - 本地暂存区与工作区保留全部修改,**未执行 git commit**,等待评审。