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 请假样例 |
backend/app/api/oa_approval.py — 路由聚合backend/app/schemas/oa_approval.py — Pydanticbackend/app/crud/oa_approval.py — CRUDbackend/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.pybackend/tests/test_oa_formula.pybackend/tests/test_oa_engine.pydocs/sql/patch_2026-07-14_oa_approval.sql — 增量 DDL + 菜单种子说明backend/app/models/models.py — SysUser.supervisor_user_id、SysDept.leader_user_id、全部 Oa* 模型backend/app/api/__init__.py、backend/app/main.py — 注册路由web-admin/src/api/oaApproval.tsweb-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.vueweb-admin/src/views/oa-approval/apps/designer/FormDesigner.vueweb-admin/src/views/oa-approval/apps/designer/FlowDesigner.vueweb-admin/src/views/oa-approval/apps/designer/AdvancedSettings.vueweb-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.vueweb-admin/src/views/oa-approval/instances/detail.vueweb-admin/src/components/oa-approval/OaAttachmentField.vue — 附件卡片(对齐协议附件)web-admin/src/components/oa-approval/flow-tree/* — 竖向树节点组件web-admin/src/router/modules/sqxp.ts(或独立 oa-approval.ts 并挂载)web-admin/src/App.vue — panelTitleBarTextRules 如需leader_user_idsystem/workflow/* 菜单(SQL 或前端 filter)web-admin: @form-create/designer@3.5.0、@form-create/element-ui@3.3.1(已安装;Vue3 + Element Plus)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
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
)"
Files:
docs/sql/patch_2026-07-14_oa_approval.sql(本 Task 先写 ALTER 段)backend/app/models/models.py — SysUser、SysDeptModify: 用户/部门的 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;
leader_user_id,并同步 leader 显示名为所选用户昵称(兼容旧列)。git commit -m "$(cat <<'EOF'
feat: add supervisor_user_id and dept leader_user_id for OA approval
EOF
)"
oa_* 表 DDL + SQLAlchemy 模型Files:
docs/sql/patch_2026-07-14_oa_approval.sqlModify: 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
models.py 增加对应 Oa* 类(命名与表名一致,风格对齐现有 Wf*/Sys*)。Files:
backend/app/schemas/oa_approval.py(角色段)backend/app/crud/oa_approval.py(角色函数)backend/app/api/oa_approval.pyModify: 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
ensure_presets(tenant_no):若不存在则插入 role_kind=preset、role_code in (GM, CHAIRMAN)。Files:
backend/app/services/oa_assignee.pyCreate: 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 被选中
...
pytest backend/tests/test_oa_assignee.py -v → 预期 FAIL(import/函数不存在)。resolve_assignees(...) 返回 (list[str] user_ids, dict meta),覆盖五种 assigneeType;空结果抛业务错误。git commit -m "$(cat <<'EOF'
feat(oa-approval): add assignee resolver with dept leader and supervisor fallback
EOF
)"
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
advancedSettingsJson 默认:{
"supervisorFallbackToDeptLeader": true,
"allowWithdrawRunning": true,
"approverDedup": false,
"commentRequired": false,
"allowAddSign": true
}
_row_to_dict 为可读真源;Create/Update 与之对齐。Files:
backend/app/services/oa_publish.pyModify: API POST /apps/{id}/publish
[ ] Step 1: 校验规则(失败返回中文错误列表):
assigneeType 合法且引用非空(dept_leader/direct_supervisor/initiator_select 除外)[ ] Step 2: 通过后:status=published,is_latest_published=1,旧 published 取消 latest。
[ ] Step 3: 单测或手工:缺审批人策略时拒绝发布。
[ ] Step 4: Commit
Files:
backend/app/services/oa_formula.pyCreate: 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
Files:
backend/app/services/oa_engine.pybackend/tests/test_oa_engine.pyapi/oa_approval.pyAPI:
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
start_instance:鉴权发起范围 → 锁定 latest published → 校验附件 URL/必填 → 公式 → 写 instance/relations → 沿 flow_tree_json 推进建 task。complete_task:权限校验 → 或签/会签 → 条件分支 → 抄送 → 结束状态。form_data 中 attachment 字段为逗号分隔 URL;必填与数量在服务端再验。oa_relation;详情返回可跳转列表。dept_leader 分部门;主管回退;GM 预置角色。Files:
web-admin/src/api/oaApproval.tsweb-admin/src/views/oa-approval/roles/index.vue(对照 system/user 列表模式)web-admin/src/views/oa-approval/apps/index.vue(对照 system/unit)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
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
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
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
}
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
Files:
system/workflow./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):
dept_leader[ ] Step 6: Commit
| 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 双写:部门保存时同步。oa-fc-mint 覆盖主色。