9.5 KiB
9.5 KiB
床位管理 & 柜子管理 — 设计规约
日期: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 新增表
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 现有表变更
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> - Drawer 内含
<Tabs>,三个面板:
| Tab | 内容 |
|---|---|
| 基本信息 | 现有房间字段展示 + 编辑表单(房号、楼栋、楼层、容量、类型、性别、租赁类别、月租金) |
| 床位管理 | <Table>:编号、状态 Tag、备注、操作(编辑/删除);顶部工具栏:新增 + 批量生成 |
| 柜子管理 | <Table>:编号、状态 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 数据库迁移
- 创建
beds和lockers表 - ALTER TABLE
occupancies添加bed_id、locker_id列(nullable) - 根据现有
rooms.capacity,为每个房间自动生成 capacity 张默认床位(编号 "1号床"~"N号床",status = 'available') - 不自动生成柜子(柜子数量无法从现有数据推断)
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. 自我审查
- 无 TBD / 占位符
- 数据模型与 API 一致
- 边界情况已覆盖(维修冲突、空床位、历史数据兼容)
- 范围明确(包含/不包含)
- 权限策略清晰(沿用 room:*)