From 361e9233963bc0e233c0309bacf471df680927b7 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Thu, 20 Aug 2026 20:36:33 +0800 Subject: [PATCH] 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 --- .../2026-08-20-duty-shift-dict-time-design.md | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-20-duty-shift-dict-time-design.md diff --git a/docs/superpowers/specs/2026-08-20-duty-shift-dict-time-design.md b/docs/superpowers/specs/2026-08-20-duty-shift-dict-time-design.md new file mode 100644 index 000000000..1135a5cff --- /dev/null +++ b/docs/superpowers/specs/2026-08-20-duty-shift-dict-time-design.md @@ -0,0 +1,187 @@ +# 值班排班 · 自定义类型 / 工作安排字典 / 值班时段 — 设计文档 + +> **日期**: 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`