Files
gongxue-base/docs/superpowers/specs/2026-07-09-bed-locker-management-design.md

9.5 KiB
Raw Blame History

床位管理 & 柜子管理 — 设计规约

日期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
  • occupiedmaintenance禁止,必须先退宿
  • 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 数据库迁移

  1. 创建 bedslockers
  2. ALTER TABLE occupancies 添加 bed_idlocker_idnullable
  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. 自我审查

  • 无 TBD / 占位符
  • 数据模型与 API 一致
  • 边界情况已覆盖(维修冲突、空床位、历史数据兼容)
  • 范围明确(包含/不包含)
  • 权限策略清晰(沿用 room:*