From 543fddbc1e2d1e8165c76ff48971eec47daa778d Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 9 Jul 2026 11:44:24 +0800 Subject: [PATCH] docs: bed & locker management design spec --- ...2026-07-09-bed-locker-management-design.md | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-09-bed-locker-management-design.md diff --git a/docs/superpowers/specs/2026-07-09-bed-locker-management-design.md b/docs/superpowers/specs/2026-07-09-bed-locker-management-design.md new file mode 100644 index 0000000..16dac71 --- /dev/null +++ b/docs/superpowers/specs/2026-07-09-bed-locker-management-design.md @@ -0,0 +1,256 @@ +# 床位管理 & 柜子管理 — 设计规约 + +> 日期:2026-07-09 +> 状态:待评审 +> 关联 PRD:模块 7 宿舍管理 + +--- + +## 1. 概述 + +在现有宿舍管理(Rooms)基础上,新增**床位管理**和**柜子管理**两个子资源。床位与入住流程强绑定(入住必选床位),柜子可选分配。两者均有独立生命周期(可单独标记维修),不随学生入住/退宿自动创建或删除。 + +### 1.1 核心设计决策 + +| 决策 | 选择 | +|------|------| +| 床位/柜子与房间的关系 | Room 1:N Bed / Room 1:N Locker,子资源挂在房间下 | +| 床位与入住的关系 | 入住必选床位,occupancies 新增 bed_id / locker_id | +| 前端交互方式 | 房间详情 Drawer 内嵌子 Tab(床位/柜子),不改动侧边栏 | +| 入住流程改造 | 选房间 → 自动加载可用床位 → 必选床位 + 可选柜子 | +| 权限 | 沿用 room:view / room:edit,不新增独立权限 | + +--- + +## 2. 数据模型 + +### 2.1 新增表 + +```sql +CREATE TABLE beds ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + room_id INTEGER NOT NULL REFERENCES rooms(id), + bed_number VARCHAR(20) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'available', -- available / occupied / maintenance + notes TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(room_id, bed_number) +); + +CREATE TABLE lockers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + room_id INTEGER NOT NULL REFERENCES rooms(id), + locker_number VARCHAR(20) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'available', -- available / occupied / maintenance + notes TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(room_id, locker_number) +); +``` + +### 2.2 现有表变更 + +```sql +ALTER TABLE occupancies ADD COLUMN bed_id INTEGER REFERENCES beds(id); +ALTER TABLE occupancies ADD COLUMN locker_id INTEGER REFERENCES lockers(id); +``` + +- 两个字段均为 **nullable**,历史数据留空 +- 新建入住时 bed_id 必填,locker_id 可选 + +### 2.3 状态机 + +``` +available ──入住──▶ occupied ──退宿──▶ available + │ │ + └──手动维修──▶ maintenance ──手动恢复──▶ available +``` + +- `occupied` → `maintenance`:**禁止**,必须先退宿 +- `maintenance` 的床位不出现在入住选择列表中 + +--- + +## 3. 后端 API + +### 3.1 床位 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/rooms/:roomId/beds` | 房间下所有床位列表 | +| GET | `/rooms/:roomId/beds/available` | 仅返回 available 状态床位(入住下拉用) | +| POST | `/rooms/:roomId/beds` | 新增床位 | +| PUT | `/rooms/:roomId/beds/:id` | 编辑床位(编号/状态/备注) | +| DELETE | `/rooms/:roomId/beds/:id` | 删除床位(仅当未被占用时) | +| POST | `/rooms/:roomId/beds/batch` | 批量创建床位(如"一键生成4张床") | + +### 3.2 柜子 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/rooms/:roomId/lockers` | 房间下所有柜子列表 | +| GET | `/rooms/:roomId/lockers/available` | 仅返回 available 状态柜子 | +| POST | `/rooms/:roomId/lockers` | 新增柜子 | +| PUT | `/rooms/:roomId/lockers/:id` | 编辑柜子 | +| DELETE | `/rooms/:roomId/lockers/:id` | 删除柜子(仅当未被占用时) | +| POST | `/rooms/:roomId/lockers/batch` | 批量创建柜子 | + +### 3.3 入住 API 变更 + +- `POST /occupancies` 请求体新增 `bedId`(必填)、`lockerId`(可选) +- 创建时校验:`bedId` 对应的床位 status 必须为 `available` 且属于指定 roomId +- 成功后后端事务:创建 occupancy + 更新 bed.status = 'occupied'(+ locker 同理) +- `PUT /occupancies/:id`(换房/换床场景)同理校验 +- 退宿时(check-out):恢复 bed/locker 状态为 `available` + +### 3.4 房间删除/归档 + +- 归档房间时,不自动归档床位/柜子(保持数据完整性) +- 删除床位/柜子的 DELETE 为硬删除,仅在未被占用时允许 +- 若房间已被归档,其床位/柜子的新增/编辑操作被拒绝 + +--- + +## 4. 前端 UI + +### 4.1 房间详情 Drawer(核心变更) + +**位置**:`apps/admin/src/pages/Rooms/index.tsx` + +- 将现有的简单 Modal 替换为 Ant Design `` +- Drawer 内含 ``,三个面板: + +| Tab | 内容 | +|-----|------| +| 基本信息 | 现有房间字段展示 + 编辑表单(房号、楼栋、楼层、容量、类型、性别、租赁类别、月租金)| +| 床位管理 | ``:编号、状态 Tag、备注、操作(编辑/删除);顶部工具栏:新增 + 批量生成 | +| 柜子管理 | `
`:编号、状态 Tag、备注、操作(编辑/删除);顶部工具栏:新增 + 批量生成 | + +- 床位/柜子操作均为就地弹窗(Modal),不跳页 +- 已归档房间:隐藏床位/柜子的新增按钮,操作列仅显示查看 + +### 4.2 入住登记改造 + +**位置**:`apps/admin/src/pages/Occupancies/index.tsx` + +现有"入住登记"弹窗改为**分步表单**或**分组表单**: + +``` +┌─ 基本信息 ────────────────────────┐ +│ 学生选择 [Select 搜索] │ +│ 房间选择 [Select 搜索] │ +│ 入住日期 [DatePicker] │ +│ 计费起始 [DatePicker] │ +│ 租赁类型 [长租/短租] │ +│ 租赁方 [Select] │ +└──────────────────────────────────┘ +┌─ 床位/柜子分配 ───────────────────┐ +│ 床位 * [Select] 空闲 3/4 │ +│ 柜子 [Select] 空闲 3/4 (可选)│ +└──────────────────────────────────┘ +``` + +- 选择房间后,自动请求 `/rooms/:id/beds/available` 填充床位下拉 +- 床位下拉旁显示"空闲 X / 总共 Y"提示 +- 柜子下拉从 `/rooms/:id/lockers/available` 加载 +- 入住列表表格新增"床位号""柜子号"两列 +- 换房时也允许重新选择床位/柜子 + +### 4.3 宿舍总览(RoomVisual) + +**位置**:`apps/admin/src/pages/RoomVisual/index.tsx` + +- 房间卡片底部增加床位占用信息:「🛏 2/4 床已占用」 +- 统计栏:总床位数、可用床位数(替代或补充现有"可用床位"统计,当前是基于 capacity 计算的) +- 点击卡片进入房间详情时,直接定位到"床位管理" Tab + +### 4.4 侧边栏 & 路由 + +**不变。** 不新增菜单项,不新增路由。所有操作在现有页面内完成。 + +--- + +## 5. 迁移策略 + +### 5.1 数据库迁移 + +1. 创建 `beds` 和 `lockers` 表 +2. ALTER TABLE `occupancies` 添加 `bed_id`、`locker_id` 列(nullable) +3. 根据现有 `rooms.capacity`,为每个房间自动生成 capacity 张默认床位(编号 "1号床"~"N号床",status = 'available') +4. 不自动生成柜子(柜子数量无法从现有数据推断) + +### 5.2 数据兼容 + +- 历史入住记录 bed_id/locker_id 为 NULL,前端展示为 "-" +- 现有入住功能不受影响(入住弹窗中床位默认可选第一张可用床,或留空允许不选 — 取决于业务要求) + +--- + +## 6. 边界情况 & 约束 + +| 场景 | 处理 | +|------|------| +| 床位被占用时标记为维修 | 禁止,提示"请先退宿" | +| 删除已被占用的床位 | 禁止,提示"该床位有人入住" | +| 房间已满,但还有空床位(数据不一致)| 允许入住,以床位为准;rooms.capacity 降级为展示用途 | +| 入住时选择房间后无可选床位 | 提示"该房间暂无可用床位",阻止提交 | +| 批量导入入住名单 | 模板新增床号列;导入时自动查找或创建床位 | +| 导出 | 床位/柜子数据包含在房间导出中,作为子 sheet | + +--- + +## 7. 实现范围 + +### 包含 + +- beds + lockers 实体、迁移、CRUD API +- occupancies 表新增 bed_id/locker_id +- 入住/退宿/换房时床柜状态联动 +- 房间详情 Drawer + 床/柜子 Tab +- 入住登记改造(床位必选) +- 宿舍总览卡片的床位统计 + +### 不包含 + +- 柜子钥匙管理(后续独立需求) +- 床位/柜子独立的全局列表页(预留 API,不做前端) +- 床位与学生的独立关联表(通过 occupancy 关联即可) +- 床位/柜子的操作日志(后续统一做时纳入) + +--- + +## 8. 文件清单 + +### 后端(NestJS) + +| 文件 | 操作 | +|------|------| +| `apps/server/src/entities/bed.entity.ts` | 新增 | +| `apps/server/src/entities/locker.entity.ts` | 新增 | +| `apps/server/src/entities/occupancy.entity.ts` | 修改(加字段)| +| `apps/server/src/rooms/rooms.module.ts` | 修改(注册 Bed/Locker)| +| `apps/server/src/rooms/rooms.controller.ts` | 修改(加床位/柜子路由)| +| `apps/server/src/rooms/rooms.service.ts` | 修改(加床柜 CRUD)| +| `apps/server/src/occupancies/occupancies.service.ts` | 修改(入住/退宿联动床柜状态)| +| `apps/server/src/occupancies/occupancies.controller.ts` | 修改(接收 bedId/lockerId)| +| Migration 脚本 | 新增 | + +### 前端(React) + +| 文件 | 操作 | +|------|------| +| `apps/admin/src/pages/Rooms/index.tsx` | 重写 Detail Modal → Drawer + Tabs | +| `apps/admin/src/pages/Occupancies/index.tsx` | 改造入住登记 + 新增列 | +| `apps/admin/src/pages/RoomVisual/index.tsx` | 卡片底部加床位统计 | + +--- + +## 9. 自我审查 + +- [x] 无 TBD / 占位符 +- [x] 数据模型与 API 一致 +- [x] 边界情况已覆盖(维修冲突、空床位、历史数据兼容) +- [x] 范围明确(包含/不包含) +- [x] 权限策略清晰(沿用 room:*)