Capture confirmed approach for customizable shift types and work plans via sys_dict, slot time fields with morning/evening defaults, migration, and public H5 sync. Co-authored-by: Cursor <cursoragent@cursor.com>
9.4 KiB
值班排班 · 自定义类型 / 工作安排字典 / 值班时段 — 设计文档
日期: 2026-08-20
状态: 已确认
仓库: shanxia2.0(backend/+web-admin/)
方案: 方案 1 — 复用sys_dict+duty_slot扩字段
前置:docs/superpowers/specs/2026-08-14-duty-schedule-design.md(V1 排班)及后续值班人员角色、月历筛选等增量
影响页面: 管理端/#/duty/schedule;公开移动端/#/public/duty/{token}
背景
值班排班 V1 将班次类型写死为 morning / evening / duty(早班 / 晚班 / 值班),工作安排落在 duty_slot.remark 自由文本,且无值班时间段。业务需要:
- 值班类型可自定义:输入新类型后写入字典,下次可选;新建某日默认仍带出三种预置类型,同日可追加其它类型。
- 工作安排改为多选下拉:预置如配酸、装卸;可新建并入库;与备注分离。
- 增加值班时间段:可改;早班默认 08:00–18:00,晚班默认 18:00–次日 07:00;「值班」暂不设默认。
- 公开抢班 H5 同步展示类型名、时段、工作安排、备注。
已确认决策
| # | 决策 |
|---|---|
| D1 | 采用方案 1:复用系统字典 sys_dict,两类字典;新建值写入 sys_dict_data |
| D2 | 新建某日默认三条预置类型;同日支持追加多个其它类型;同日同类型最多一条 slot(与 V1 一致) |
| D3 | 工作安排 多选;存逗号分隔的 dict_value;每条 slot 另有独立 备注 |
| D4 | 早班默认 08:00–18:00(可改);晚班默认 18:00–次日 07:00(可改);值班及其他自定义类型默认时段为空 |
| D5 | shift_type 继续存 dict_value(预置保持 morning/evening/duty),不改写历史编码 |
| D6 | 字典类型用系统字典页维护即可;排班页 allow-create 只追加 dict_data |
| D7 | 管理端与公开移动端同一数据源,H5 必须同步改展示与抢班校验 |
| D8 | 月历统计:预置三列保留;自定义类型计入「其他」(轻量) |
方案选择
采用方案 1(已确认):字典编码稳定 + duty_slot 扩字段。
未采用:业务表只存中文标签(历史编码难兼容);工作安排 JSON 数组(与仓库逗号分隔习惯不一致)。
架构与边界
sys_dict_type / sys_dict_data
duty_shift_type → 值班类型选项
duty_work_plan → 工作安排选项
duty_day
└── duty_slot(shift_type = dict_value,同日唯一)
├── work_plans / remark / start_time / end_time / cross_day
└── duty_assignment × M
| 端 | 职责 |
|---|---|
| web-admin 排班 | 配置日、字典下拉+创建、时段、多选工作安排、备注;月历/筛选/统计适配 |
| 公开 H5 | 展示 label / 时段 / 工作安排 / 备注;按 shiftType 抢班 |
| backend | slot 字段读写;保存时 upsert 字典项;公开 API 返回展示字段;校验放宽为「当日存在该 slot」 |
| 系统字典页 | 可启停、改 label;不必新建独立「值班类型」业务页 |
数据模型
字典种子
| dict_type | dict_label | dict_value | 说明 |
|---|---|---|---|
duty_shift_type |
早班 | morning |
默认时段 08:00–18:00,cross_day=0 |
duty_shift_type |
晚班 | evening |
默认时段 18:00–07:00,cross_day=1 |
duty_shift_type |
值班 | duty |
无默认时段 |
duty_work_plan |
配酸 | peisuan |
|
duty_work_plan |
装卸 | zhuangxie |
新建自定义类型 / 工作安排时:dict_label 为用户输入文案(去首尾空白);dict_value 优先用拼音小写(非法字符剔除),冲突则追加短后缀(如 _ + 4 位 hex),租户内同 dict_type 下 dict_value 唯一;写入当前 tenant_no。若已存在相同 dict_label(未删除),复用该条,不新建。
duty_slot 变更
| 字段 | 变更 | 说明 |
|---|---|---|
shift_type |
语义放宽 | 存 dict_value;不再限死三枚举;同日唯一(未删除) |
work_plans |
新增 | varchar(500),多选 dict_value 逗号分隔,如 peisuan,zhuangxie |
remark |
语义变更 | 自由备注;不再表示工作安排 |
start_time |
新增 | TIME,可空 |
end_time |
新增 | TIME,可空 |
cross_day |
新增 | smallint,默认 0;1 表示结束时间在次日 |
字段顺序遵守:id → 业务字段 → 公共字段。SQL 变更同步 docs/sql/ 与 backend/scripts/。
API 载荷(增量)
Slot 读写字段(camelCase):
shiftType、shiftLabel(只读展示)workPlans:string[](API 进出均为数组;库内逗号拼接 dict_value)workPlanLabels:string[]只读,按字典解析;缺字典时回退为 value 本身remark: 备注startTime/endTime:"HH:mm"或nullcrossDay:boolean(库内 0/1)timeLabel: 只读,如08:00–18:00、18:00–次日07:00;无时段时为null
公开:
GET /api/public/duty/{token}:slots 含上述展示字段POST .../grab:shiftType改为任意非空字符串;校验当日存在该 slot、开链、未满员(不再in SHIFT_LABELS)
管理端保存:若传入的类型 label/工作安排值在字典中不存在,则 upsert sys_dict_data 后再落 slot。
复用:fetchDictDataOptions('duty_shift_type' | 'duty_work_plan')。
Web 管理端
配置某日弹窗
- 未配置日打开:默认三条(早/晚/值班),早/晚带默认时段,值班时段空;均可取消启用。
- 「添加班次」:从
duty_shift_type选择(filterable + allow-create);新建写入字典;已占用类型不可再选。 - 每条启用的 slot:名额、时段(起止;
cross_day可由结束<开始推断或显式)、工作安排多选(allow-create 入库)、备注、值班安排(选人,规则不变)。 - 校验:同日同
shiftType唯一;缩容 / 有人不可删班次等规则保持 V1。
月历 / 筛选 / 统计
- 图例与筛选读字典(预置优先);自定义类型用中性色点。
- 按人统计:预置 morning/evening/duty 三列 +「其他」。
视觉:Fresh Mint;label-position="top";弹窗无背景模糊。
公开移动端 H5(必须同步)
路径:/#/public/duty/{token}(web-admin/src/views/public/duty/index.vue)。
| 区域 | 行为 |
|---|---|
| 班次标题 | shiftLabel;未知类型中性样式 |
| 时段 | 有则展示 timeLabel;无则隐藏 |
| 工作安排 | workPlanLabels,顿号或 chip;无则隐藏 |
| 备注 | 有 remark 时展示「备注:…」 |
| 抢班 | body 仍传该 slot 的 shiftType |
| 成功页 | 建议带时段与工作安排摘要 |
视觉:三墙 Token(#0a9f6d),不与管理端色值混用。
存量迁移
上线顺序:
- 插入
sys_dict_type+ 预置sys_dict_data(两类)。 ALTER TABLE duty_slot增加work_plans、start_time、end_time、cross_day。- 数据回填:
remark去空白后整段等于「配酸」→work_plans=peisuan,清空remark;等于「装卸」→zhuangxie;若同时需支持历史里逗号拼接的「配酸,装卸」可按分隔拆分映射。- 其它非空
remark→ 保留为备注(不迁入 work_plans)。 shift_type=morning→start_time=08:00、end_time=18:00、cross_day=0。shift_type=evening→18:00、07:00、cross_day=1。duty与未知类型 → 时间字段保持 NULL。
- 发布后端,再发布 web-admin(排班 + 公开 H5)。
- 回归:旧 token 可打开;历史早/晚显示默认时段;曾填配酸/装卸显示为工作安排标签。
access_token 不变;不改写历史 shift_type 编码。
错误处理
| 场景 | 行为 |
|---|---|
| 同日重复 shiftType | 保存拒绝,明确文案 |
| 字典写入重名 / 冲突 | 提示失败;优先复用已有同 label 项 |
| 缩容过大 / 删仍有人的班次 | 保持 V1 |
| H5 抢不存在的类型 | 拒绝并刷新 |
| 字典项停用 | 历史按存库 value 解析 label(或缺省显示 value);新排班下拉仅启用项 |
测试要点(验收)
- 新建日默认三类型;可追加自定义类型且入库,下次可选。
- 工作安排多选 + 新建入库;与备注互不覆盖。
- 早/晚默认时段可改;值班可空。
- 同日同类型不可重复。
- 月历/筛选/统计不因自定义类型报错。
- 公开链接展示类型、时段、工作安排、备注,并可抢班成功。
- 迁移后历史展示正确,token 不变,租户隔离仍成立。
明确不做(本迭代)
- 同日同类型多条 slot
- 独立「值班类型」业务维护页(用系统字典)
- 改写历史
morning/evening/duty编码 - 批量按日期范围建排班、员工自助取消、重置 token(仍按 V1)
相关文件(实现时)
- 模型 / 服务 / API:
backend/app/models/duty_models.py、services/duty_schedule.py、schemas/duty.py、api/duty.py、api/public_duty.py - 管理端:
web-admin/src/views/duty/schedule/index.vue、web-admin/src/api/duty.ts - 公开 H5:
web-admin/src/views/public/duty/index.vue、web-admin/src/api/public-duty.ts - 字典:现有
sys_dict_*CRUD 与fetchDictDataOptions - 单测:
backend/tests/test_duty_schedule.py