Files
shanxia2.0/docs/superpowers/specs/2026-08-20-duty-shift-dict-time-design.md
T
zlwuandCursor 361e923396 docs: add duty shift dict, work plan, and time range design
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>
2026-08-20 20:36:33 +08:00

9.4 KiB
Raw Blame History

值班排班 · 自定义类型 / 工作安排字典 / 值班时段 — 设计文档

日期: 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 自由文本,且无值班时间段。业务需要:

  1. 值班类型可自定义:输入新类型后写入字典,下次可选;新建某日默认仍带出三种预置类型,同日可追加其它类型。
  2. 工作安排改为多选下拉:预置如配酸、装卸;可新建并入库;与备注分离。
  3. 增加值班时间段:可改;早班默认 08:00–18:00,晚班默认 18:00–次日 07:00;「值班」暂不设默认。
  4. 公开抢班 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" 或 null
  • crossDay: 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),不与管理端色值混用。

存量迁移

上线顺序:

  1. 插入 sys_dict_type + 预置 sys_dict_data(两类)。
  2. ALTER TABLE duty_slot 增加 work_plans、start_time、end_time、cross_day。
  3. 数据回填:
    • 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。
  4. 发布后端,再发布 web-admin(排班 + 公开 H5)。
  5. 回归:旧 token 可打开;历史早/晚显示默认时段;曾填配酸/装卸显示为工作安排标签。

access_token 不变;不改写历史 shift_type 编码。

错误处理

场景 行为
同日重复 shiftType 保存拒绝,明确文案
字典写入重名 / 冲突 提示失败;优先复用已有同 label 项
缩容过大 / 删仍有人的班次 保持 V1
H5 抢不存在的类型 拒绝并刷新
字典项停用 历史按存库 value 解析 label(或缺省显示 value);新排班下拉仅启用项

测试要点(验收)

  1. 新建日默认三类型;可追加自定义类型且入库,下次可选。
  2. 工作安排多选 + 新建入库;与备注互不覆盖。
  3. 早/晚默认时段可改;值班可空。
  4. 同日同类型不可重复。
  5. 月历/筛选/统计不因自定义类型报错。
  6. 公开链接展示类型、时段、工作安排、备注,并可抢班成功。
  7. 迁移后历史展示正确,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