2026-07-14-oa-approval-dingtalk.md 20 KB

OA 审批(钉钉交互)Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在 web-admin + Python 后端交付独立「OA审批」模块:四步配置(基础/表单/流程/高级)、工作流角色、审批中心办理;引擎按钉钉式流程树运行。

Architecture: 前端用 FormCreate(Element Plus)做表单设计器、自研竖向流程树(参考 AntFlow 交互)+ Fresh Mint;后端新建 oa_* 表与 oa_approval 引擎,实例锁定已发布版本;审批人不读 sys_role,用 oa_role / dept_leader / direct_supervisor 解析。旧 X6/wf_* 菜单废弃。

Tech Stack: FastAPI, SQLAlchemy, Pydantic v2, Vue 3, Element Plus, FormCreate (@form-create/designer@^3 + @form-create/element-ui@^3), TypeScript, MinIO 附件上传, pytest

Spec: docs/superpowers/specs/2026-07-14-oa-approval-dingtalk-design.md

V1 审批人选人(已定,不含连续多级主管/表单联系人):
users | oa_roles(含预置 GM/CHAIRMAN)| dept_leader | direct_supervisor(无主管默认回退部门负责人)| initiator_select;节点级或签/会签。

表单底座锁定: FormCreate(Element Plus 版)。不在实现中途再切 VForm3,除非 spike 证明不可用(见 Task 0)。


分阶段交付(建议按序;每阶段可独立验收)

Phase 产出 验收焦点
A 库表、组织字段、工作流角色、审批人解析器 解析单元测试绿
B 应用/分组/版本 API、发布校验 可用 API 创建草稿并发布最小流程
C 运行时:发起/审批/抄送/条件/公式/联动/关联/附件校验 pytest + 手工 Postman
D 前端:菜单、角色、应用列表、四步向导壳 可点通四步保存
E 表单设计器 + 流程树 + 高级设置 UI 对齐钉钉交互 + Mint
F 审批中心全页 + 旧模块下线 + 菜单 SQL E2E 请假样例

File Structure

Backend(新建)

  • backend/app/api/oa_approval.py — 路由聚合
  • backend/app/schemas/oa_approval.py — Pydantic
  • backend/app/crud/oa_approval.py — CRUD
  • backend/app/services/oa_assignee.py — 审批人解析
  • backend/app/services/oa_formula.py — 公式/联动
  • backend/app/services/oa_engine.py — 发起与推进
  • backend/app/services/oa_publish.py — 发布校验
  • backend/tests/test_oa_assignee.py
  • backend/tests/test_oa_formula.py
  • backend/tests/test_oa_engine.py
  • docs/sql/patch_2026-07-14_oa_approval.sql — 增量 DDL + 菜单种子说明
  • Modify: backend/app/models/models.py — SysUser.supervisor_user_id、SysDept.leader_user_id、全部 Oa* 模型
  • Modify: backend/app/api/__init__.py、backend/app/main.py — 注册路由
  • Modify: 用户/部门相关 schema·CRUD·前端表单(主管、负责人 ID)

Frontend(新建)

  • web-admin/src/api/oaApproval.ts
  • web-admin/src/types/oa-approval.d.ts(或写入 api.d.ts 命名空间)
  • web-admin/src/views/oa-approval/apps/index.vue — 应用列表
  • web-admin/src/views/oa-approval/apps/designer/index.vue — 四步壳
  • web-admin/src/views/oa-approval/apps/designer/BasicSettings.vue
  • web-admin/src/views/oa-approval/apps/designer/FormDesigner.vue
  • web-admin/src/views/oa-approval/apps/designer/FlowDesigner.vue
  • web-admin/src/views/oa-approval/apps/designer/AdvancedSettings.vue
  • web-admin/src/views/oa-approval/roles/index.vue — 工作流角色
  • web-admin/src/views/oa-approval/start/index.vue — 发起入口
  • web-admin/src/views/oa-approval/tasks/pending.vue | done.vue | mine.vue | cc.vue
  • web-admin/src/views/oa-approval/instances/detail.vue
  • web-admin/src/components/oa-approval/OaAttachmentField.vue — 附件卡片(对齐协议附件)
  • web-admin/src/components/oa-approval/flow-tree/* — 竖向树节点组件
  • Modify: web-admin/src/router/modules/sqxp.ts(或独立 oa-approval.ts 并挂载)
  • Modify: web-admin/src/App.vue — panelTitleBarTextRules 如需
  • Modify: 用户管理、部门管理页 — 主管 / leader_user_id
  • Modify: 隐藏旧 system/workflow/* 菜单(SQL 或前端 filter)

依赖

  • web-admin: @form-create/designer@3.5.0、@form-create/element-ui@3.3.1(已安装;Vue3 + Element Plus)

Phase A — 数据与解析

Task 0: FormCreate spike(半天内)

Files:

  • Modify: web-admin/package.json(确认可安装后再锁版本)

  • [x] Step 1: 在 web-admin 安装 FormCreate Vue3 + Element Plus 设计器依赖并跑通最小 demo 页(临时路由即可)。

cd web-admin
pnpm add @form-create/element-plus @form-create/designer
# 若包名因官方版本差异调整,以能渲染 designer 为准,并把最终包名/版本写回本计划「依赖」节
pnpm dev
  • Step 2: 确认:拖拽单行文本、必填、导出 JSON schema;主题可用 CSS 变量靠拢 Mint(主色替换即可)。
  • Step 3: 若不可用,才评估 VForm3;默认不得切换。记录锁定版本到本计划顶部「Tech Stack」。
  • Step 4: Commit
git add web-admin/package.json web-admin/pnpm-lock.yaml
git commit -m "$(cat <<'EOF'
chore(web-admin): add FormCreate for OA approval form designer

EOF
)"

Task 1: 组织字段补齐(主管 / 部门负责人用户 ID)

Files:

  • Create: docs/sql/patch_2026-07-14_oa_approval.sql(本 Task 先写 ALTER 段)
  • Modify: backend/app/models/models.py — SysUser、SysDept
  • Modify: 用户/部门的 schema、CRUD、list/detail API、前端表单项

  • [ ] Step 1: 在补丁 SQL 增加:

-- 直属主管(发起人的主管)
ALTER TABLE sys_user
  ADD COLUMN supervisor_user_id VARCHAR(64) NULL COMMENT '直属主管用户ID' AFTER dept_id;

-- 部门负责人用户ID(发起人部门负责人);保留原 leader 姓名展示可用同步回填
ALTER TABLE sys_dept
  ADD COLUMN leader_user_id VARCHAR(64) NULL COMMENT '部门负责人用户ID' AFTER status;
  • Step 2: 模型字段与前端:用户编辑增加「直属主管」用户选择器;部门编辑负责人写入 leader_user_id,并同步 leader 显示名为所选用户昵称(兼容旧列)。
  • Step 3: 手工:配张三主管=钱七、部门 A 负责人=赵六,接口返回新字段。
  • Step 4: Commit
git commit -m "$(cat <<'EOF'
feat: add supervisor_user_id and dept leader_user_id for OA approval

EOF
)"

Task 2: oa_* 表 DDL + SQLAlchemy 模型

Files:

  • Modify: docs/sql/patch_2026-07-14_oa_approval.sql
  • Modify: backend/app/models/models.py

  • [ ] Step 1: 追加表(字段顺序:id → 业务 → 公共字段),至少包括:

oa_app_group, oa_app, oa_app_version,
oa_role, oa_role_user,
oa_instance, oa_task, oa_relation

关键列约定:

oa_role.role_code VARCHAR(64)  -- 预置 GM / CHAIRMAN
oa_role.role_kind CHAR(16)     -- custom | preset
oa_app_version.status          -- draft | published
oa_app_version.form_schema_json / flow_tree_json / advanced_settings_json  TEXT/LONGTEXT
oa_instance.initiator_user_id, initiator_dept_id, form_data_json, status
oa_task.assignee_user_id, node_key, task_type(approval|cc), status, comment_text, resolve_source(nullable) -- 如 supervisor_fallback_dept_leader
  • Step 2: 在 models.py 增加对应 Oa* 类(命名与表名一致,风格对齐现有 Wf*/Sys*)。
  • Step 3: 本地执行补丁 SQL,确认表创建成功。
  • Step 4: Commit

Task 3: 工作流角色 CRUD + 预置种子

Files:

  • Create: backend/app/schemas/oa_approval.py(角色段)
  • Create: backend/app/crud/oa_approval.py(角色函数)
  • Create: backend/app/api/oa_approval.py
  • Modify: backend/app/api/__init__.py、backend/app/main.py

  • [ ] Step 1: API:

GET    /api/admin/oa-approval/roles
POST   /api/admin/oa-approval/roles
PUT    /api/admin/oa-approval/roles/{id}
DELETE /api/admin/oa-approval/roles/{id}   # preset → 403
PUT    /api/admin/oa-approval/roles/{id}/users   body: { userIds: string[] }
POST   /api/admin/oa-approval/roles/ensure-presets   # 幂等写入 GM/CHAIRMAN
  • Step 2: ensure_presets(tenant_no):若不存在则插入 role_kind=preset、role_code in (GM, CHAIRMAN)。
  • Step 3: 租户首次访问角色列表时调用 ensure。
  • Step 4: 手工验证:删 GM 返回错误;可改成员。
  • Step 5: Commit

Task 4: 审批人解析器 + 单元测试

Files:

  • Create: backend/app/services/oa_assignee.py
  • Create: backend/tests/test_oa_assignee.py

  • [ ] Step 1: 写失败测试(无实现时)

# backend/tests/test_oa_assignee.py
def test_dept_leader_resolves_by_initiator_dept(db_session, tenant_no):
    # 准备 dept A leader=U1, user initiator dept=A
    from app.services.oa_assignee import resolve_assignees
    cfg = {"assigneeType": "dept_leader"}
    users, meta = resolve_assignees(db_session, tenant_no, cfg, initiator_user_id="zhang", initiator_dept_id="deptA", advanced={"supervisorFallbackToDeptLeader": True}, initiator_select_user_ids=None)
    assert users == ["leaderA"]

def test_direct_supervisor_fallback_to_dept_leader(db_session, tenant_no):
    cfg = {"assigneeType": "direct_supervisor"}
    users, meta = resolve_assignees(..., initiator_user_id="no_supervisor", initiator_dept_id="deptA", advanced={"supervisorFallbackToDeptLeader": True}, ...)
    assert users == ["leaderA"]
    assert meta.get("resolve_source") == "supervisor_fallback_dept_leader"

def test_oa_role_gm(db_session, tenant_no):
    cfg = {"assigneeType": "oa_roles", "oaRoleIds": ["<gm_role_id>"]}
    users, meta = resolve_assignees(...)
    assert "gm_user" in users

def test_never_reads_sys_role(db_session, tenant_no, monkeypatch):
    # 仅绑定 sys_role 的用户不应因 assigneeType=oa_roles 被选中
    ...
  • Step 2: 运行 pytest backend/tests/test_oa_assignee.py -v → 预期 FAIL(import/函数不存在)。
  • Step 3: 实现 resolve_assignees(...) 返回 (list[str] user_ids, dict meta),覆盖五种 assigneeType;空结果抛业务错误。
  • Step 4: pytest 全绿。
  • Step 5: Commit
git commit -m "$(cat <<'EOF'
feat(oa-approval): add assignee resolver with dept leader and supervisor fallback

EOF
)"

Phase B — 应用定义与发布

Task 5: 分组,应用 / 草稿版本 API

Files:

  • Modify: backend/app/schemas/oa_approval.py、crud/oa_approval.py、api/oa_approval.py

  • [ ] Step 1: 实现:

CRUD /api/admin/oa-approval/groups
CRUD /api/admin/oa-approval/apps
GET/PUT /api/admin/oa-approval/apps/{id}/draft
# draft 体:basic + formSchemaJson + flowTreeJson + advancedSettingsJson
# 无草稿则从最新 published 克隆或新建 version_no=1 status=draft
  • Step 2: advancedSettingsJson 默认:
{
  "supervisorFallbackToDeptLeader": true,
  "allowWithdrawRunning": true,
  "approverDedup": false,
  "commentRequired": false,
  "allowAddSign": true
}
  • Step 3: 字段一致性:_row_to_dict 为可读真源;Create/Update 与之对齐。
  • Step 4: Commit

Task 6: 发布校验

Files:

  • Create: backend/app/services/oa_publish.py
  • Modify: API POST /apps/{id}/publish

  • [ ] Step 1: 校验规则(失败返回中文错误列表):

    • 表单至少 1 个可填字段
    • 流程树从发起人可达结束
    • 条件分支完整
    • 每个审批节点 assigneeType 合法且引用非空(dept_leader/direct_supervisor/initiator_select 除外)
  • [ ] Step 2: 通过后:status=published,is_latest_published=1,旧 published 取消 latest。

  • [ ] Step 3: 单测或手工:缺审批人策略时拒绝发布。

  • [ ] Step 4: Commit


Phase C — 运行时引擎

Task 7: 公式与数据联动

Files:

  • Create: backend/app/services/oa_formula.py
  • Create: backend/tests/test_oa_formula.py

  • [ ] Step 1: 支持白名单 AST:字段引用、+ - * /、IF、SUM(明细后续可扩);禁止 eval 任意代码。

  • [ ] Step 2: apply_formulas_and_linkages(schema, form_data) -> form_data;联动按字段 props.linkage 规则回填。

  • [ ] Step 3: 测试:合法公式;非法表达式抛错。

  • [ ] Step 4: Commit

Task 8: 引擎发起 / 推进 / 审批动作

Files:

  • Create: backend/app/services/oa_engine.py
  • Create: backend/tests/test_oa_engine.py
  • Modify: api/oa_approval.py

API:

GET  /api/admin/oa-approval/templates
POST /api/admin/oa-approval/instances
GET  /api/admin/oa-approval/instances/{id}
GET  /api/admin/oa-approval/tasks/pending|done|mine|cc
POST /api/admin/oa-approval/tasks/{id}/approve
POST /api/admin/oa-approval/tasks/{id}/reject
POST /api/admin/oa-approval/tasks/{id}/add-sign
POST /api/admin/oa-approval/instances/{id}/withdraw
  • Step 1: start_instance:鉴权发起范围 → 锁定 latest published → 校验附件 URL/必填 → 公式 → 写 instance/relations → 沿 flow_tree_json 推进建 task。
  • Step 2: complete_task:权限校验 → 或签/会签 → 条件分支 → 抄送 → 结束状态。
  • Step 3: 附件:form_data 中 attachment 字段为逗号分隔 URL;必填与数量在服务端再验。
  • Step 4: 关联审批:写 oa_relation;详情返回可跳转列表。
  • Step 5: pytest:直线审批;dept_leader 分部门;主管回退;GM 预置角色。
  • Step 6: Commit

Phase D — 前端壳与配置台

Task 9: 路由、菜单、API 模块、工作流角色页

Files:

  • Create: web-admin/src/api/oaApproval.ts
  • Create: web-admin/src/views/oa-approval/roles/index.vue(对照 system/user 列表模式)
  • Create: web-admin/src/views/oa-approval/apps/index.vue(对照 system/unit)
  • Modify: router;docs/sql/patch_...sql 追加 sys_menu OA 审批节点
  • Hide: 旧 workflow 菜单

  • [ ] Step 1: 菜单:OA审批 → 审批管理 / 工作流角色 / 发起审批 / 待处理 / 已处理 / 我发起的 / 抄送我的。

  • [ ] Step 2: 角色页:列表、新增自定义、成员绑定、预置不可删。

  • [ ] Step 3: 应用列表:新增 → 进设计器;发布状态 Tag。

  • [ ] Step 4: UI:P24 或扩展模式码写进 PR 说明;按钮带图标;状态用 el-tag。

  • [ ] Step 5: Commit

Task 10: 四步设计器壳 + 基础设置 + 高级设置

Files:

  • Create: designer/index.vue、BasicSettings.vue、AdvancedSettings.vue

  • [ ] Step 1: 顶栏步骤:基础设置 | 表单设计 | 流程设计 | 高级设置;Mint 高亮;预览/发布按钮。

  • [ ] Step 2: 基础设置字段对齐钉钉截图(图标、名称 50、分组、说明 100、谁可发起、表单管理员)。

  • [ ] Step 3: 高级设置:撤销/去重/意见/加签 + 无直属主管回退部门负责人开关(默认开)。

  • [ ] Step 4: PUT draft 自动/手动保存。

  • [ ] Step 5: Commit


Phase E — 设计器

Task 11: 表单设计器(FormCreate)

Files:

  • Create: FormDesigner.vue、OaAttachmentField.vue

  • [ ] Step 1: 嵌入 FormCreate designer;导出 JSON 存 formSchemaJson。

  • [ ] Step 2: V1 控件以 Spec §4.5 为准:分栏、单行/多行/数字/单选/多选/日期/日期区间/说明/电话、图片/金额/附件/部门/省市区/计算公式/关联审批单;不做 AI/手写签名/身份证/联系人/外部联系人/行业通讯录部门;二期不做明细表格/地点/评分/级联。属性含数据联动配置入口。

  • [ ] Step 3: 附件组件:即时 fetchUploadAttachment;卡片布局对齐 ProtocolFormDialog;值=URL 逗号串。

  • [ ] Step 4: 落地 Spec §4.6 兼容契约:共享控件字典(稳定 type)+ form_data 值类型表(前后端镜像);PC/手机 375 预览;分栏窄屏强制单列。

  • [ ] Step 5: 发布校验拒绝未知 type;单测覆盖值类型序列化(多选数组、日期区间对象、附件逗号 URL 等)。

  • [ ] Step 6: 自检:无 File/DOM/组件实例进 Schema;部门/省市区/附件语义可映射移动端选择器与上传。

  • [ ] Step 7: Commit

Task 12: 竖向流程树设计器

Files:

  • Create: FlowDesigner.vue、components/oa-approval/flow-tree/*

  • [ ] Step 1: 数据结构(存 flowTreeJson):

type FlowNode = {
  id: string
  type: 'start' | 'approval' | 'cc' | 'condition' | 'end'
  name: string
  approverConfig?: {
    assigneeType: 'users' | 'oa_roles' | 'dept_leader' | 'direct_supervisor' | 'initiator_select'
    userIds?: string[]
    oaRoleIds?: string[]
    signMode?: 'or' | 'and' // 或签 | 会签
  }
  conditionGroups?: { name: string; rules: unknown[]; child: FlowNode }[]
  child?: FlowNode | null
}
  • Step 2: UI:发起人 →「+」→ 审批/抄送/条件 → 结束;节点抽屉配置五种选人 + 或签会签。
  • Step 3: 工作流角色下拉:预置置顶(总经理/董事长)+ 自定义。
  • Step 4: 不使用 AntV X6。
  • Step 5: Commit

Phase F — 办理中心与收尾

Task 13: 发起 / 待办 / 已办 / 我发起 / 抄送 / 详情

Files:

  • Create: start/index.vue、tasks/*.vue、instances/detail.vue

  • [ ] Step 1: 发起:分组模板宫格 → 动态渲染 form schema → 附件上传 → 自选人 → 提交。

  • [ ] Step 2: 列表页:标准 list 骨架;状态 el-tag。

  • [ ] Step 3: 详情:表单只读、附件预览下载、进度时间轴、回退来源文案、关联审批跳转、同意/拒绝/加签/撤销。

  • [ ] Step 4: 对照 unit/order 模板页与 Fresh Mint 规范。

  • [ ] Step 5: Commit

Task 14: 用户/部门前端联调、旧工作流下线、图谱与基线

Files:

  • Modify: 用户、部门页
  • Modify: 菜单 SQL / 路由隐藏 system/workflow
  • Run: ./scripts/update-graph.sh all(有新建代码时)
  • Optional: 重导 docs/sql/zkxw.sql 或保留 patch 为权威增量

  • [ ] Step 1: 用户可选直属主管;部门负责人写 leader_user_id。

  • [ ] Step 2: 旧工作流菜单隐藏或提示「请使用 OA审批」。

  • [ ] Step 3: python scripts/check_field_consistency.py(若脚本覆盖新实体)。

  • [ ] Step 4: ./scripts/update-graph.sh backend && ./scripts/update-graph.sh frontend

  • [ ] Step 5: E2E 手工清单(对应 Spec §10):

    1. 发布含条件+抄送的请假应用
    2. A/B 部门 dept_leader
    3. 无主管回退部门负责人
    4. GM/董事长预置角色
    5. 附件上传与详情下载
    6. 公式/联动不被前端篡改
    7. 关联审批跳转
  • [ ] Step 6: Commit


Spec 覆盖自检

Spec 要点 Task
四步配置 + Mint 10–12
FormCreate 表单 + 附件 MinIO 0, 11
Web/移动兼容契约(§4.6:同 Schema/form_data/校验/控件字典) 6, 11, 8
竖向流程树(非 X6) 12
oa_role + GM/CHAIRMAN 3, 12
dept_leader / direct_supervisor + 默认回退 1, 4, 8, 10
公式/联动/关联审批 7, 8, 11, 13
审批中心六页能力 13
版本锁定 / 发布校验 5, 6, 8
不对接业务工单 / 不做 AI 范围外,无 Task
旧 wf 下线 14
租户隔离 全部 API CRUD

未纳入 V1(明确): 连续多级主管、表单内联系人、SQL 任意审批人、打印模板、自动化任务。


风险备忘

  • leader 历史字符串与 leader_user_id 双写:部门保存时同步。
  • FormCreate 样式侵入:用业务包裹 class oa-fc-mint 覆盖主色。
  • 流程树条件 UI 复杂度:先支持简单规则列表(字段/op/value),与引擎 JSON 一致即可。