[BUG] 工作流 doHandle 同意后未 syncToTable 导致入库审批流程卡住

现象

入库单审批流程在「采购复核」节点点击「同意」后,审批历史已写入、钉钉待办已标记完成,但流程实例与业务单据未推进到下一节点(出纳审批),表现为流程「断掉」、长期停留在「审批中 / 采购复核」。

复现步骤

  1. 流程:workflows.repositoryApproval(入库单-审批),采购入库路径
  2. 发起一张采购入库单并完成发起审批(实例正常落到节点 HdJtDr 采购复核)
  3. 采购复核角色成员在移动端(钉钉)对实例执行「同意」(handleType=2nodeId=HdJtDr
  4. 观察:审批历史新增「同意」记录,但实例 currentNodeid 仍为 HdJtDr,无出纳待办、无下一节点钉钉通知

已观测案例(脱敏):

字段
业务单号 RK260703001
流程实例 id 14416
业务主键 id 1604
失败 requestId ca1c1fd4006f4ea7b20f878928a0ce83
审批历史 id 66888
发生时间 2026-07-03 09:58:38 (+08)

期望 vs 实际

期望: WorkflowSvc/doHandle 在写入审批历史并完成钉钉 COMPLETED 后,应调用 workflowUtils.syncToTable,将实例推进至 BfAzoX(出纳审批),创建 WorkflowTaskModel 待办并 createPlatformTask 通知出纳角色成员。

实际: 失败请求在钉钉 COMPLETED未出现 syncToTable 日志,未创建下一节点待办,请求以 HTTP 200 正常结束;全程无 ERROR / Exception / Traceback(静默中断)。

日志证据(脱敏摘要)

失败链(RK260703001 · requestId ca1c1fd…

server.log.3 约 44878–44958 行:

doHandle (HdJtDr, handleType=2)
  → models.listing upsert ✓
  → WorkflowHistoryModel insert id=66888 ✓
  → 钉钉 processCentres/tasks COMPLETED ✓
  → EventLogModel insert id=90411 ✓
  → 请求结束 ✗
  (全程无 workflowUtils.syncToTable / WorkflowTaskModel / createPlatformTask)

gunicorn_access.logPOST …/WorkflowSvc/doHandle → HTTP 200,响应体约 227 字节。

成功对照(同审批人 · 同节点 · 同流程 · 约 5 分钟后)

单号 RK260702011,requestId 25d9a3f18cba494ba60802bdc2e178fdserver.log.3 约 47544–47652 行:

doHandle (HdJtDr, handleType=2)
  → WorkflowHistoryModel ✓
  → 钉钉 COMPLETED ✓
  → syncToTable: currNodeId=BfAzoX(出纳审批)✓
  → WorkflowTaskModel insert ✓
  → createPlatformTask + 钉钉通知出纳 ✓

同类失败(同日)

单号 RK260702012,requestId 49f701cb096c43e49fa8fcdf6e475a70,10:02:13 同样模式:历史 + 钉钉 COMPLETED 后结束,无 syncToTable

可排除项

  • 非角色空缺: predictApproveList 可解析 HdJtDr → 采购复核人、BfAzoX → 出纳审批人
  • 非发起异常: 09:08:45 发起阶段 syncToTable → HdJtDr 正常,待办与钉钉通知均已创建
  • 非用户可见报错: 前端收到 200,审批人侧显示已同意

影响

  • 数据一致性: 审批历史显示已同意,实例/单据仍停在上游节点,下游无法继续审批
  • 业务阻塞: 至少观测到 2 笔采购入库单同日卡住;需人工干预实例才能恢复
  • 用户感知: 审批人认为已处理完毕,出纳无待办,单据长期「审批中」

临时修复方案(消费仓 Semi-A · 已落地)

本评论记录运维侧手工补偿方案,用于在平台 syncToTable 根因修复前恢复卡住的入库审批单。
平台官方 API;不替代引擎正常推进。

背景结论(实测)

  • WorkflowSvc.syncToTable / advanceNode / repairInstance无对外 APIerrcode: 60001
  • 卡单形态:审批历史已有「同意」,但实例 nodeId / 业务单 currentNodeid 未推进
  • 正常 syncToTable 会:更新 listing + 实例 + 创建 WorkflowTaskModel + createPlatformTask(钉钉)
  • 手工补偿仅覆盖前三项不触发钉钉待办

脚本(已提交 main · 0e2acfb

路径:scripts/workflow-semi-a-repair.sh

两步式(MUST):先 confirm 只读确认,再 apply 写库

# 第一步:确认流程/节点/角色(不写库)
./scripts/workflow-semi-a-repair.sh confirm --repo-number <入库单号>
# 或 --instance-id <实例id>

# 第二步:确认报告无误后实施
./scripts/workflow-semi-a-repair.sh apply --state scripts/.workflow-repair-confirm/<instanceId>.json
# 非交互:加 --yes

运行时状态目录已加入 .gitignore(不纳入 Git):

  • scripts/.workflow-repair-confirm/
  • scripts/.workflow-repair-applied/

apply 写库内容

  1. models.listingcurrentNodeid → 目标节点,approvalStatus=审批中
  2. WorkflowInstanceModelnodeIdsortKeynodePathList 追加目标节点
  3. WorkflowTaskModel — 新建待办(含 createTimenoApprover=1agentId=0
  4. WorkflowCommentModel — 运维备注(关联本 Issue)

已实施单据(2026-07-05)

单号 实例 卡住节点 推进至 待办 taskId 下一审批人
RK260702009 14406 出纳 BfAzoX 会计 LokxIK 26144 会计
RK260703001 14416 采购复核 HdJtDr 出纳 BfAzoX 26145 出纳
RK260702012 14409 采购复核 HdJtDr 出纳 BfAzoX 26146 出纳

运维注意事项(踩坑记录)

  1. WorkflowTaskModel.createTime 必填
    若为 null,打开流程 / getWorkflowHistory 会报:'<' not supported between instances of 'NoneType' and 'str'

  2. noApprover 应对齐平台标准
    引擎创建的待办多为 noApprover=1agentId=0;手工创建若写 0 可能影响待办中心展示

  3. 请勿让已同意节点重复点「同意」
    历史记录是对的,重复操作可能引入新异常

  4. 钉钉
    手工补偿不调用 createPlatformTask,需人工通知下一审批人走 PC 待办Workflow/form?id=<instanceId>

只读诊断 API(脚本 confirm 阶段使用)

  • getWorkflowHistory — 审批历史 + Semi-A 判定
  • predictApproveList — 节点角色与下一审批人
  • getDisplayTaskByInstanceId — 实例与业务单一致性

与根因修复的关系

本方案为消费仓临时止血;平台侧仍需修复 doHandle 同意后未执行 syncToTable 的问题(见 Issue 正文日志:requestId 对比成功/失败单)。


提交人:消费仓 Agent · 2026-07-05