审批方式与驳回策略
审批人节点是工作流中最核心的节点。每个审批人节点都需要配置审批方式(多人时如何决策)和驳回策略(被驳回时如何处理)。
审批方式
当审批人节点配置了多人时,需要指定审批方式,决定多少人通过才算节点通过。
| 审批方式 | 说明 | 适用场景 |
|---|---|---|
| 或签 | 任一审批人通过即节点通过,其余待审批人自动跳过 | 只需要一人同意即可,如"部门经理审批" |
| 会签 | 所有审批人都通过才节点通过 | 需要多人共同确认,如"财务+法务双签" |
| 依次审批 | 按指定顺序逐一审批,前一人通过后才轮到下一人 | 需要逐级审批,如"总监→副总裁→总裁" |
| 自动通过 | 不生成审批任务,节点自动通过 | 流程自动化的节点,无需人工干预 |
审批方式配置示例
场景 1:请假申请中的部门经理审批
- 审批人:指定角色「部门经理」
- 审批方式:或签(部门经理有多个,只需其中一人审批)
场景 2:合同审批中的财务+法务双签
- 审批人:指定用户组(财务、法务各一人)
- 审批方式:会签(财务和法务都必须通过)
场景 3:项目立项的逐级审批
- 审批人:连续多级上级
- 审批方式:依次审批(按层级顺序,一级一级往上)
驳回策略
当审批人选择驳回时,系统根据配置的驳回策略决定流程走向。
| 驳回策略 | 说明 | 适用场景 |
|---|---|---|
| 终止流程 | 直接结束流程,实例状态变为「已驳回」 | 通用场景,驳回后不再继续 |
| 退回上一步 | 退回到上一个审批/办理节点,重新生成任务 | 需要修改后重新审批 |
| 退回发起人 | 退回到发起人,重新发起 | 需要发起人修改表单 |
| 退回指定节点 | 退回到指定的节点(通过节点标识引用) | 需要退回到特定环节 |
驳回策略配置示例
场景 1:普通审批驳回
- 驳回策略:终止流程
- 结果:审批人驳回后,流程直接结束,发起人收到驳回通知
场景 2:多级审批中的退回修改
- 驳回策略:退回发起人
- 结果:审批人驳回后,流程回到发起人,发起人修改后可重新提交
场景 3:跨部门协作中的退回指定环节
- 驳回策略:退回指定节点
- 节点标识:设置目标节点的 key,如
dept_manager_review - 结果:审批人驳回后,流程回到「部门经理审批」节点,而非发起人
审批类型
每个审批人节点还可以配置审批类型,作为节点级的总开关:
| 审批类型 | 说明 |
|---|---|
| 人工审批 | 正常生成审批任务,需要人工审批(默认) |
| 自动通过 | 节点自动通过,不生成任务 |
| 自动拒绝 | 节点自动拒绝 |
使用场景
- 人工审批:绝大多数场景
- 自动通过:根据前置条件判断,某类申请无需审批(如小额报销自动通过)
- 自动拒绝:根据前置条件判断,某类申请自动拒绝(如超出预算的申请)
边界情况处理
空审批人
当审批人解析为空时(如部门负责人不存在、表单字段为空),根据「空审批人处理策略」自动处理:
| 策略 | 行为 |
|---|---|
| 自动通过 | 节点自动通过 |
| 转交管理员 | 任务转交给系统管理员处理 |
| 自动拒绝 | 节点自动拒绝,流程按驳回策略处理 |
| 转交指定人 | 任务转交给预设的指定人员 |
发起人与审批人同一人
当审批人就是发起人自己时,可根据「发起人与审批人同一人时的处理策略」设置:
| 策略 | 行为 |
|---|---|
| 由发起人自己审批 | 发起人自己审批 |
| 自动跳过 | 自动跳过该节点 |
| 转交给直接上级审批 | 转交给发起人的直接上级 |
| 转交给部门负责人审批 | 转交给发起人的部门负责人 |
审批人去重
当流程中多个节点的审批人为同一人时,可根据「审批人去重策略」设置:
| 策略 | 行为 |
|---|---|
| 自动跳过(默认) | 后续节点自动同意,无需重复审批 |
| 仍需审批 | 每个节点都需重新审批 |
撤回
谁可以撤回
只有发起人才能撤回流程。
什么时候可以撤回
只有当流程状态为「运行中」时,发起人才可以撤回。
撤回后的影响
- 流程实例状态变为「已撤回」
- 所有未处理的任务自动标记为「已跳过」
- 触发撤回事件,可订阅该事件做后续处理
加签 / 减签
允许审批人在不修改流程定义的前提下,临时拉人参与或移除已加签的审批人。前端入口在「待办审批」页面,需要节点配置 actionButtons.addSign.enabled = true、actionButtons.reduceSign.enabled = true。
加签位置
| 位置 | 语义 | 任务 comment 前缀 |
|---|---|---|
before(前加签) | 原任务挂起(status = waiting),加签人先审批;全部完成后原任务自动恢复 pending | [加签-前] |
after(后加签) | 原审批人继续完成,再交给加签人接力 | [加签-后] |
parallel(并加签) | 加签人与原审批人并行处理,按节点 approveMethod 汇总 | [加签-并] |
减签
只能减掉「由加签产生的待办任务」(comment 以 [加签- 开头且 status = pending),减签后任务标记为 skipped。
事件
| 事件 | 触发时机 |
|---|---|
task.addSigned | 加签产生新任务时,每个加签任务触发一次 |
task.reduceSigned | 减签使任务变为 skipped 时,每个被减签任务触发一次 |
事件 payload 与其他 task.* 事件一致(WorkflowTaskEventPayload),meta.comment 携带加签 / 减签时填写的备注。
催办
发起人和管理员(super_admin / tenant_admin)可以对仍在运行中的流程实例的「待办」任务发起催办,提醒审批人尽快处理。
谁可以催办
- 流程实例的发起人
initiatorId === currentUser.userId - 拥有超管 / 租户管理员角色的用户
何时可催办
- 实例
status = running - 目标任务
status = pending
节流策略
每个任务在最近 5 分钟内只允许被催办 1 次,再次提交将返回 429:
{ "code": 429, "message": "催办过于频繁,请 XXXs 后再试" }实例级批量催办接口会静默跳过节流命中的任务,并在 message 中汇报跳过人数。
数据存储
催办记录持久化到 workflow_task_urges 表:
| 字段 | 类型 | 说明 |
|---|---|---|
id | serial | 主键 |
task_id | int | 被催办的任务 ID(外键 → workflow_tasks.id) |
instance_id | int | 所属实例 ID(外键 → workflow_instances.id) |
urger_id | int | 催办发起人用户 ID |
urger_name | varchar(64) | 催办发起人姓名快照 |
message | varchar(256) | 催办留言(可空) |
created_at | timestamp | 催办时间 |
事件
催办成功时触发 task.urged 事件,payload 为 WorkflowTaskEventPayload,meta.comment 携带催办留言、meta.actor 携带催办人。该事件已加入事件订阅白名单,可在「事件订阅」中配置 WebHook / 通知。
REST 接口
| Method | 路径 | 说明 |
|---|---|---|
POST | /api/workflows/tasks/{taskId}/urge | 催办单个任务,body:{ message?: string } |
GET | /api/workflows/tasks/{taskId}/urges | 查询任务的催办历史 |
GET | /api/workflows/instances/{id}/urges | 查询实例的全部催办流水 |
POST | /api/workflows/instances/{id}/urge | 实例级批量催办(对所有 pending 任务发起一次催办,节流命中的任务静默跳过) |
前端入口
「我的申请」详情抽屉内为「审批中」的实例提供「催办」按钮,可选填留言;提交后调用实例级批量催办接口。
转办 / 委派 增强
任务的处理人轨迹
workflow_tasks 新增三个字段维护处理人轨迹:
| 字段 | 类型 | 说明 |
|---|---|---|
original_assignee_id | int | 任务最初的处理人;转办/委派均不会修改 |
transfer_chain | jsonb(int[]) | 转办/委派经手过的处理人 ID 链(最早 → 最近,不含当前 assignee) |
delegated_from_id | int | 仅委派期间设置,指向「最早一次委派的发起人」;委派回执时清空 |
禁止折返转办 / 委派
转办、委派接口都会检查目标用户是否出现在 transfer_chain 中或等于 original_assignee_id;命中即返回 400:
{ "code": 400, "message": "禁止将任务转回曾经经手的处理人" }这样可以避免出现 A → B → A 之类的死循环,强制流转链路单向延伸。
委派反馈后原人接手
当任务带有 delegated_from_id 时,委派人对该任务做 同意 / 拒绝 操作不会推进流程,而是:
- 当前任务状态置为
approved/rejected,留言写入[委派回执] {委派人} 建议同意/拒绝:{意见}; - 自动为最早一次委派的发起人(
delegated_from_id)生成一条新的pending任务,同节点 / 同类型,留言为上一步的回执内容; - 新任务
delegated_from_id = null、transfer_chain = []、original_assignee_id = 原委派人; - 由原委派人对新任务进行最终的同意 / 拒绝,正常推进流程。
多次委派(A → B → C)也只生成一次回执:C 反馈后任务回到 A(最早的委派人)。
外部审批回调入口(approveTaskByCallback / rejectTaskByCallback)不会触发委派回执逻辑。
MSW 行为
前端 Demo 模式(MSW)已同步实现:
- 转办 / 委派接口校验
transferChain防折返; approve/reject接口对带delegatedFromId的任务自动生成回执任务,并返回已提交委派回执,等待原审批人确认。
抄送规则(ccNode)
接收人解析与去重
抄送节点(ccNode)的接收人不再由引擎直接展开,而是在 expandTasksToRows 阶段统一通过 resolveAssigneeIds 解析:
- 引擎层只为每个抄送节点生成一个
TaskAction(assigneeId = null,携带nodeConfig); - 服务层根据
nodeConfig.assigneeType解析最终接收人,结果由内部Set自动去重; - 未声明
assigneeType时自动回退到assigneeIds+assigneeId,兼容旧定义。
变量插值
得益于复用 resolveAssigneeIds,抄送节点支持与审批/办理节点一致的变量化接收人策略:
assigneeType=formUser/formDepartment:从表单字段动态取人;assigneeType=initiator/initiatorLeader/initiatorDept/manager:基于发起人上下文;assigneeType=expression:JSON 表达式按表单/上下文计算接收人;- 所有策略都受
resolveAssigneeIds的统一去重保护,不会重复抄送给同一人。
动态补加抄送
提供运行时补加抄送的接口:
POST /api/workflows/instances/{id}/cc/add
Body: { "nodeKey": string, "userIds": number[] }- 仅对
running状态的实例生效; - 仅发起人或
super_admin/tenant_admin可调用; - 校验目标节点必须存在且
type === 'ccNode'; - 与该实例 + 该节点已抄送过的
assigneeId自动去重; - 新插入的任务
status = 'skipped'、actionAt = null,并触发task.created事件,供消息/订阅链路二次分发。
前端入口
「我的申请」详情抽屉中,当实例为「审批中」且定义包含至少一个 ccNode 时,提供「添加抄送人」按钮,弹窗中可选择目标抄送节点与抄送人列表,提交后调用上述接口。
MSW 行为
Demo 模式(MSW)已同步实现 /api/workflows/instances/:id/cc/add,行为与后端保持一致(去重 + 节点校验 + 运行中校验)。