|
|
@@ -0,0 +1,113 @@
|
|
|
+# 值班排班 ·「值班人员」角色筛选 — 设计文档
|
|
|
+
|
|
|
+> **日期**: 2026-08-17
|
|
|
+> **状态**: 已确认
|
|
|
+> **仓库**: shanxia2.0(`backend/` + `web-admin/`)
|
|
|
+> **前置**: `docs/superpowers/specs/2026-08-14-duty-schedule-design.md`(已实现)
|
|
|
+> **方案**: 方案 1 — 固定「值班人员」角色 + 值班专用人员 options 接口
|
|
|
+
|
|
|
+## 背景
|
|
|
+
|
|
|
+值班排班配置弹窗与公开抢班链接的人员下拉,当前返回本租户全部启用系统用户。业务需要收窄为:仅「值班人员」角色下的用户可被新指定 / 新抢班。
|
|
|
+
|
|
|
+## 已确认决策
|
|
|
+
|
|
|
+| # | 决策 |
|
|
|
+|---|------|
|
|
|
+| D1 | 新建专用角色,展示名 **「值班人员」** |
|
|
|
+| D2 | 角色不挂任何菜单,仅作「可排班名单」标签;在用户管理中给用户勾选即可 |
|
|
|
+| D3 | 管理端「直接安排」与公开页「选择姓名」均只展示该角色下的启用用户 |
|
|
|
+| D4 | 历史 assignment 仍展示;不强制清理未赋角色的历史人员 |
|
|
|
+| D5 | 新指定 / 新抢班在后端强制校验角色,防止绕过下拉 |
|
|
|
+| D6 | 不改全局 `/web/admin/users/options`;值班模块使用专用 options 接口 |
|
|
|
+| D7 | 不自动给现有用户赋该角色;不做可配置「值班角色」设置页 |
|
|
|
+
|
|
|
+## 方案选择
|
|
|
+
|
|
|
+**采用方案 1(已确认)**:迁移创建固定 UUID 的「值班人员」角色;`GET /web/admin/duty/users/options` 供配置弹窗;公开页 `_list_public_users` 与 `grab` / `save_day` 共用同一角色判定。
|
|
|
+
|
|
|
+未采用:给全局用户 options 加 `roleName` 参数(业务耦合到通用接口);可配置值班角色设置页(当前无需求)。
|
|
|
+
|
|
|
+## 角色与数据
|
|
|
+
|
|
|
+### 迁移
|
|
|
+
|
|
|
+- 脚本:`backend/scripts/migrate_20260817_duty_staff_role.sql`(幂等插入)
|
|
|
+- 角色:
|
|
|
+ - `id`:固定 UUID,例如 `R65CAAE057914CCCAE2A81BE627A5101`
|
|
|
+ - `role_name`:`值班人员`
|
|
|
+ - `status`:`1`(启用)
|
|
|
+ - `data_scope`:`4`(仅本人即可;本角色不用于数据权限)
|
|
|
+ - `tenant_no`:`shanxia2.0`(与现有业务角色一致;多租户若后续需要再按租户补插)
|
|
|
+ - **不**写入 `sys_role_menu`
|
|
|
+ - **不**预绑定任何 `sys_user_role`
|
|
|
+- SQL 快照:按仓库约定同步维护 `docs/sql/`(或至少保证迁移脚本可在目标库执行)
|
|
|
+
|
|
|
+### 代码识别
|
|
|
+
|
|
|
+- 常量:`DUTY_STAFF_ROLE_ID`(固定 id)
|
|
|
+- 解析顺序:先按 id + 租户 + 未删除查找角色;找不到再按 `role_name == '值班人员'` 兜底(同租户)
|
|
|
+- 用户是否可排班:本租户 + `SysUser.del_flag=0` + `status=1` + 存在有效 `sys_user_role` 指向该角色
|
|
|
+
|
|
|
+## 接口与校验
|
|
|
+
|
|
|
+### 管理端
|
|
|
+
|
|
|
+| 能力 | 说明 |
|
|
|
+|------|------|
|
|
|
+| `GET /web/admin/duty/users/options` | 返回可排班用户:`[{ label, value }]`(label=昵称优先,否则账号;value=用户 id) |
|
|
|
+| `POST /web/admin/duty/day` | 保存时对每个 `source=assign` 且带 `userId` 的项校验「值班人员」角色;失败返回明确文案(如「仅可安排值班人员角色的用户」) |
|
|
|
+
|
|
|
+配置页人员下拉改调值班专用 options;其它业务继续用全局 `/web/admin/users/options`。
|
|
|
+
|
|
|
+### 公开(免登录)
|
|
|
+
|
|
|
+| 能力 | 说明 |
|
|
|
+|------|------|
|
|
|
+| `GET /api/public/duty/{token}` | 响应中 `users` 仅含「值班人员」角色用户 |
|
|
|
+| `POST /api/public/duty/{token}/grab` | 所选 `userId` 必须具备该角色,否则拒绝 |
|
|
|
+
|
|
|
+### 历史兼容
|
|
|
+
|
|
|
+- 月历摘要、日详情、公开页已报名列表:**照常展示**已有 assignment(含当时无该角色的人)
|
|
|
+- 仅限制新写入:管理端新指定、公开新抢班
|
|
|
+
|
|
|
+## 前端
|
|
|
+
|
|
|
+| 位置 | 改动 |
|
|
|
+|------|------|
|
|
|
+| `web-admin/src/views/duty/schedule/index.vue` | 加载人员 options 改为值班专用 API;文案可注明仅显示「值班人员」 |
|
|
|
+| `web-admin/src/api/duty.ts` | 新增 `fetchDutyUserOptions` |
|
|
|
+| `web-admin/src/views/public/duty/index.vue` | 交互不变;依赖后端 `users` 过滤 |
|
|
|
+| 用户管理 | 不改;管理员在角色勾选中赋「值班人员」 |
|
|
|
+
|
|
|
+## 错误处理
|
|
|
+
|
|
|
+| 场景 | 行为 |
|
|
|
+|------|------|
|
|
|
+| 指定非值班人员用户 | 保存失败,提示需具备「值班人员」角色 |
|
|
|
+| 公开抢班选非值班人员(或伪造 id) | 抢班失败,提示用户无效或不具备值班资格 |
|
|
|
+| 租户内尚无该角色记录 | options 返回空列表;保存/抢班校验失败并提示角色未配置 |
|
|
|
+
|
|
|
+## 测试要点
|
|
|
+
|
|
|
+1. 迁移后角色列表出现「值班人员」,无菜单权限。
|
|
|
+2. 未赋角色用户:配置下拉与公开页下拉均不可见。
|
|
|
+3. 赋角色后:两边可选,指定与抢班成功。
|
|
|
+4. 伪造无角色 `userId`:管理端保存与公开抢班均失败。
|
|
|
+5. 历史上无角色人员仍显示在日历/详情中。
|
|
|
+6. 其它页面全局用户下拉不受影响。
|
|
|
+
|
|
|
+## 不在范围内
|
|
|
+
|
|
|
+- 不自动给现有用户赋「值班人员」角色
|
|
|
+- 不修改全局 `/web/admin/users/options` 行为
|
|
|
+- 不做系统设置里可配置「值班角色」
|
|
|
+- 不清理历史 assignment
|
|
|
+
|
|
|
+## 实现落点(摘要)
|
|
|
+
|
|
|
+- Backend:`duty_schedule.py` 抽取 `list_duty_staff_users` / `assert_duty_staff`;`duty.py` 注册 options;公开路径复用同一过滤
|
|
|
+- Frontend:排班页改用 `fetchDutyUserOptions`
|
|
|
+- SQL:`migrate_20260817_duty_staff_role.sql`
|
|
|
+- 单测:扩展 `backend/tests/test_duty_schedule.py`(无角色拒绝抢班/指定;有角色可通过列表过滤)
|