docs: add OA confirmation kit (转正套件) design spec.

Capture product decisions for user hire/probation fields, kit composition, auto confirmation date, and post-approval writeback to initiator.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-30 17:06:21 +08:00
1 parent 310b2efe8a
commit c31ef0e809
1 file changed
+367
@@ -0,0 +1,367 @@
# OA 表单设计 · 转正套件(Confirmation Kit)设计文档
**日期**: 2026-07-30
**状态**: 待实现
**范围**: `web-admin` 用户管理主数据补齐;审批设计器「套件」Tab 转正套件;发起/详情运行时默认与联动;`backend` 审批通过后回写发起人 `sys_user`
**界面模式**: 对齐请假套件与 FormCreate 控件库;视觉 Fresh Mint
**前置**:
- `docs/superpowers/specs/2026-07-30-oa-leave-kit-design.md`(套件组装体模式)
- `docs/superpowers/specs/2026-07-30-oa-post-select-design.md`(职位组件)
- `docs/superpowers/specs/2026-07-27-oa-dept-select-design.md`(部门组件)
- `docs/superpowers/specs/2026-07-30-oa-owner-fields-design.md`(发起人默认值模式可参考)
---
## 1. 背景与目标
在「2 表单设计」套件 Tab 增加 **转正套件**:一键插入转正申请常用字段;发起时自动带出申请人主数据;审批全部通过后将约定字段回写发起人用户主数据。
### 1.1 目标
- `sys_user` 补齐 **入职日期**、**试用期(月数)**;用户管理可编辑。
- 套件 Tab 可拖入 **转正套件**(容器 + 基础/业务子字段)。
- 发起时默认填入申请人:入职日期、试用期、姓名、邮箱、部门、手机号;**职位不默认**。
- 转正日期可由「入职日期 + 试用期月数」自动建议,可改;手改后断开联动(同请假时长)。
- 子字段可在设计器配置 **显示/隐藏**;隐藏字段仍可带默认值,通过后仍可回写。
- 审批 **全部通过** 后回写发起人主数据(见 §2)。
### 1.2 非目标(首期)
- 职位、岗位职级、转正日期回写用户表。
- 转正通过后的合同、账号状态、人事档案等副作用。
- 代他人发起并回写他人主数据。
- 一体复合控件(单 type 包对象值)——否决,采用与请假相同的组装体方案。
---
## 2. 产品决策(已确认)
| 项 | 决策 |
|----|------|
| 实现方案 | 方案 1:套件容器 + 子字段拼装(非一体值对象) |
| 容器类型 | `confirmationKit` |
| 回写时机 | 审批 **全部通过** 后再回写 |
| 回写对象 | 始终 **发起人** 的 `sys_user` |
| 回写字段 | 入职日期、试用期月数、姓名、邮箱、部门、手机号 |
| 不回写 | 职位、岗位职级、转正日期(仅审批单) |
| 试用期存储 | 整数月数(如 `3`),界面展示「3 个月」 |
| 转正日期 | 有入职日 + 试用期时自动建议,仍可改 |
| 转正日联动 | 未手改则随入职/试用期重算;手改后断开;可「恢复自动计算」 |
| 职位默认 | **不默认**,发起人自选 |
| 职位选择 | 保持现有职位组件 **多选** |
| 部门控件 | 现有「部门」组件,值 `{ id, name }` |
| 显示/隐藏 | 设计器按子字段配置;隐藏不展示、不前端必填 |
| 隐藏与回写 | 隐藏字段若有默认/提交值,通过后 **仍回写**(设计器弱提示) |
| 回写空值 | 某字段为空则 **跳过该列**,不清空主数据 |
| 回写失败 | 校验失败 **不回滚** 审批通过状态,记日志/告警 |
---
## 3. 信息模型
### 3.1 心智模型
```
┌─ 转正套件(confirmationKit / 容器)────────────────────┐
│ 套件属性:子字段显示/隐藏 │
│ │
│ · datePicker → 入职日期 ← 默认申请人 │
│ · inputNumber → 试用期(月) ← 默认申请人 │
│ · datePicker → 转正日期 ← 自动建议,可改 │
│ · post → 职位 ← 不默认,多选 │
│ · input → 岗位职级 ← 空 │
│ · input → 姓名 ← 默认申请人 │
│ · input → 邮箱 ← 默认申请人 │
│ · dept → 部门 ← 默认主部门 │
│ · input → 手机号 ← 默认申请人 │
└─────────────────────────────────────────────────────────┘
```
- 选中 **套件容器**:配置显示字段等套件属性;运行时/发起页 **不** 额外渲染套件边框或标题。
- 选中 **子字段**:标题、必填、校验等与普通组件相同。
### 3.2 子字段约定
| 语义 key | 建议 title | type | 必填默认 | 回写 |
|----------|------------|------|----------|------|
| `hireDate` | 入职日期 | `datePicker`(仅日期,`YYYY-MM-DD`) | 是 | ✓ → `hire_date` |
| `probationMonths` | 试用期(月) | `inputNumber`,整数 ≥0 | 是 | ✓ → `probation_months` |
| `confirmationDate` | 转正日期 | `datePicker`(仅日期) | 否 | ✗ |
| `post` | 职位 | `post`(现有,多选) | 否 | ✗ |
| `jobLevel` | 岗位职级 | `input` | 否 | ✗ |
| `name` | 姓名 | `input` | 是 | ✓ → `nick_name` |
| `email` | 邮箱 | `input` | 否 | ✓ → `email` |
| `dept` | 部门 | `dept`(现有,建议单选) | 是 | ✓ → `dept_id` ← `id` |
| `phone` | 手机号 | `input` | 否 | ✓ → `phone_no` |
插入时子字段 `field` 生成唯一 ID(同表单可多套,自动去重后缀)。语义识别优先稳定标记(如 `props._fc_kit_field: 'hireDate'`),避免仅依赖 title。
### 3.3 Schema 结构(示意)
```json
{
"type": "confirmationKit",
"props": {
"visibleFields": {
"hireDate": true,
"probationMonths": true,
"confirmationDate": true,
"post": true,
"jobLevel": true,
"name": true,
"email": true,
"dept": true,
"phone": true
}
},
"children": [
{
"type": "datePicker",
"field": "hireDate_xxxx",
"title": "入职日期",
"props": {
"_fc_kit_field": "hireDate",
"type": "date",
"valueFormat": "YYYY-MM-DD"
},
"$required": true
},
{
"type": "inputNumber",
"field": "probationMonths_xxxx",
"title": "试用期(月)",
"props": { "_fc_kit_field": "probationMonths", "min": 0, "precision": 0 },
"$required": true
},
{
"type": "datePicker",
"field": "confirmationDate_xxxx",
"title": "转正日期",
"props": {
"_fc_kit_field": "confirmationDate",
"type": "date",
"valueFormat": "YYYY-MM-DD",
"confirmationDateManual": false
},
"$required": false
},
{
"type": "post",
"field": "post_xxxx",
"title": "职位",
"props": { "_fc_kit_field": "post", "multiple": true }
},
{
"type": "input",
"field": "jobLevel_xxxx",
"title": "岗位职级",
"props": { "_fc_kit_field": "jobLevel" }
},
{
"type": "input",
"field": "name_xxxx",
"title": "姓名",
"props": { "_fc_kit_field": "name" },
"$required": true
},
{
"type": "input",
"field": "email_xxxx",
"title": "邮箱",
"props": { "_fc_kit_field": "email" }
},
{
"type": "dept",
"field": "dept_xxxx",
"title": "部门",
"props": { "_fc_kit_field": "dept", "multiple": false },
"$required": true
},
{
"type": "input",
"field": "phone_xxxx",
"title": "手机号",
"props": { "_fc_kit_field": "phone" }
}
]
}
```
容器为布局类,不计可填字段数;`visibleFields` 与子字段 `hidden` 保持同步(实现时二选一为主、另一派生即可,文档以「隐藏不展示」为准)。
### 3.4 值格式
| 字段 | 格式 |
|------|------|
| 日期 | `YYYY-MM-DD` |
| 试用期 | 整数 |
| 部门 | `{ id, name }`(全路径名称优先) |
| 职位 | 与现有 `post` 一致(多选为数组) |
| 姓名/邮箱/手机号/职级 | 字符串 |
---
## 4. 用户主数据
### 4.1 `sys_user` 增补
| 列 | 类型 | 说明 |
|----|------|------|
| `hire_date` | `DATE` NULL | 入职日期 |
| `probation_months` | `INT` NULL | 试用期月数(≥0) |
同步范围:
- SQLAlchemy `SysUser`、CRUD `_row_to_dict`、Create/Update Body
- 用户管理新增/编辑弹窗可编辑;查看详情展示
- `userInfo` / `_build_user_info_payload` 带出 `hireDate`、`probationMonths`
- `docs/sql/` 增量脚本;字段一致性检查
既有已具备且用户管理可编辑:`nick_name`(姓名)、`email`、`dept_id`、`phone_no`。
### 4.2 列表展示
- 编辑弹窗:**必须**可维护入职日期、试用期。
- 列表列:可选窄列或仅详情展示(实现时最小为弹窗 + 详情即可)。
---
## 5. 运行时
### 5.1 发起默认值
仅对 **空字段** 写入(不覆盖用户已填):
| 语义 | 来源 |
|------|------|
| hireDate | `userInfo.hireDate` |
| probationMonths | `userInfo.probationMonths` |
| confirmationDate | 入职 + 试用期可算则写入建议值;否则空 |
| post | **不填** |
| jobLevel | 空 |
| name | 显示名 / `nickName` |
| email | `email` |
| dept | 主部门 `{ id, name }`(全路径优先) |
| phone | `phoneNo` |
- **隐藏字段**:仍按上表写入默认到 `formData`(便于通过后回写)。
- **详情 / 已发起单**:只展示已存 `form_data`,不按当前查看者覆盖。
- 设计器画布:不依赖设计者登录身份作为业务默认。
### 5.2 转正日期联动
- 算法:`confirmationDate = hireDate` 按自然月加 `probationMonths`(实现时与前端/后端同一工具函数,可单测)。
- `confirmationDateManual === false`:入职日或试用期变化时重算并写回转正日。
- 用户手改转正日 → `confirmationDateManual = true`,断开联动。
- 提供「恢复自动计算」:清手动标记并立即重算。
### 5.3 显示 / 隐藏
- 隐藏:发起/详情不渲染;前端不校验必填。
- 默认值仍可写入 `formData`。
- 通过后:若该语义在回写名单且值非空,照常回写。
- 设计器弱提示:「隐藏且属于回写字段时,审批通过仍可能更新用户主数据。」
### 5.4 提交校验(发起)
- 仅校验 **可见** 且必填的子字段。
- 隐藏字段不拦必填。
- 发布白名单纳入 `confirmationKit`;套件子节点缺失 → **警告可发**(对齐请假)。
---
## 6. 审批通过后回写
### 6.1 触发
实例状态变为 **全部通过** 时调用:
`apply_confirmation_kit_user_sync(db, instance)`
挂在现有「通过」后置点,避免散落多 API。
### 6.2 流程
```
实例 → 全部通过
→ 扫描 form schema 中 confirmationKit
→ 解析 children 语义标记 → 从 form_data 取值
→ 目标用户 = 发起人 user_id
→ 有值则更新对应列;空则跳过
→ 格式校验失败:记日志,不回滚通过状态
```
### 6.3 映射
| 语义 | `sys_user` 列 |
|------|----------------|
| hireDate | `hire_date` |
| probationMonths | `probation_months` |
| name | `nick_name` |
| email | `email` |
| dept | `dept_id`(取 `id`) |
| phone | `phone_no` |
不写:`post`、`jobLevel`、`confirmationDate`。
### 6.4 幂等与安全
- 有值才更新,避免空值清空。
- 租户隔离:仅更新本租户发起人。
- 不信任客户端另传「回写目标用户」。
- 手机号/邮箱校验对齐用户管理规则。
---
## 7. 设计器
### 7.1 套件 Tab
- 卡片标题:**转正**(与请假并列)。
- 拖入插入容器 + 9 子字段。
### 7.2 容器属性
- 「显示字段」九项勾选(默认全开),同步子字段 `hidden` / `visibleFields`。
- 隐藏回写提示文案见 §5.3。
### 7.3 子字段属性
- 与普通同类型控件一致(含职位多选、部门单选)。
- 转正日期可暴露「恢复自动计算」相关说明(运行时按钮也可挂在字段旁,实现时与请假时长 UX 对齐)。
---
## 8. 建议文件落点
| 区域 | 路径(示意) |
|------|----------------|
| 套件容器 | `web-admin/src/components/oa-approval/OaConfirmationKit.vue` |
| DragRule | `web-admin/src/utils/oa-confirmation-kit-rule.ts` |
| 注册 | 现有 kit 注册入口旁增加转正 |
| 默认值 + 联动 | `web-admin/src/utils/oa-confirmation-kit-runtime.ts` |
| 自然月加 | 前后端共享算法(前端 TS + 后端 Python,行为一致) |
| 用户管理 | `web-admin/src/views/system/user/index.vue` + API 类型 |
| 模型 / SQL | `SysUser` + `docs/sql/` 增量 |
| userInfo | `backend` `_build_user_info_payload` |
| 回写服务 | `backend/app/services/oa_confirmation_kit_sync.py` + 通过态钩子 |
---
## 9. 验收标准
1. 用户管理可维护入职日期、试用期(月);`userInfo` 可带出。
2. 套件 Tab 可拖「转正」;可隐藏部分字段。
3. 发起:主数据六项自动填;职位为空;转正日可按规则建议;手改后断开,可恢复自动。
4. 通过前主数据不变;通过后六项回写发起人;空字段不清空库。
5. 隐藏字段的默认值在通过后仍可回写。
6. 部门值为 `{id,name}`;职位为现有多选控件。
7. 回写失败不撤销审批通过状态。
---
## 10. 开放实现细节(不阻塞产品)
- 列表是否增加「入职日期」列:实现时最小范围可不加列。
- 「恢复自动计算」是字段旁链接还是属性面板说明:对齐请假时长即可。
- `visibleFields` 与子字段 `hidden` 以何者为 source of truth:实现选一并在 runtime 归一化。