一、办错了,想收回自己刚办结的那一手
经办人李娜在“经办复核”点了同意,单子进入“归档确认”,王强手上有了待办。
两分钟后李娜发现意见写错了。她想收回自己刚才那一手,但不是发起人撤单,也不是把王强推回上一节点——她要在“我的已办”里撤回自己刚办结的那一行。
撤回之后,李娜原来那条记录不会消失,审批记录里还在;流程里会多出一条新的复核待办。同一个“经办复核”节点,李娜要再办一次。
这篇只回答四件事:什么窗口内能撤;撤回到底动了哪几行;原记录为什么不能改写;配置和前端怎么接。
先说结论:撤回不是删记录,原行留着,新行长出来。
二、四种“往回走”,主体和结果都不一样
审批里能“往回走”的动作不止一个,它们的主体、作用对象、结果完全不同:
| 动作 | 谁发起 | 作用对象 | 结果 | 适合 |
|---|---|---|---|---|
实例撤回(withdraw) | 发起人或办理人 | 整张实例 | 实例 state=30,整单停下 | 不想办这张单了 |
退回上一步(submitType=3) | 当前持有人 | 上一手真实办过的行(血缘) | 回上一手待办 | 这一手办错了,推回去重来 |
退回发起人(submitType=6) | 当前持有人 | 发起节点 | 回发起人,按流程重走 | 资料不齐,让发起人补 |
已办撤回(recall) | 办理人本人 | 自己刚办结的那一行 | 原行留 FINISHED,同节点新行 DOING | 办错了,收回自己这一手重办 |
只有第四种是“收回自己已经办结的那一手”。它不撤整张单,也不把单子推给上一个人;原行不动,同节点新长出一行。
三、窗口:下游整条链没有已办结,才能撤
撤回不是想撤就撤。前置条件有四条,全部排在任何状态变更之前,报错即零写入:
- 实例仍然是 DOING(10);整单已办结时先被实例状态挡下;
- 节点开了
allowRecall; - 目标行必须 FINISHED,操作人必须是它的参与者(
flow.auto/flow.admin例外); - 窗口(宽口径):从目标行沿
task_parent_id链产出的全部下游任务行里,没有一条 FINISHED。
窗口不是“只看下一手”。它把下游整条链都扫一遍;任一条已办结就拒。并行分支也一样,逐支检查。
我这次真跑了一单整单已经办结的:撤回接口返回 流程实例非进行中状态,不可撤回——整单都办完了,先被实例状态挡下。窗口报错是另一档:下游某条分支已经办结、但实例还在进行,此时按规范是 任务[{节点名}]的后续节点已办理,不能撤回(节点名取被撤回的那一行)。两档都是零写入,原行和下游行都不动。
四、撤回三件:下游清场、原行不改写、同节点新行复活
窗口过了,撤回在同一聚合变更里做三件事:
- 下游全部 DOING 行废弃:复用废弃路径;后加签/征询的成员行如果在这一行下游,也会被一起清掉。并行分支触发多支时,多支下游 DOING 全部废弃。
- 原 FINISHED 行一律不改写:只追加一条账本
tf_recallHistory,形状是{operator, time, recalledTaskId}。原行的状态、办理结果、意见都不动。 - 同节点新建一行 DOING:
task_parent_id等于原行的 parent(不是原行),参与者是原行名册+当时生效的委托(不摘原人),expire_time同尺重算,fire 码 3(任务开始事件)走既有建单路径。在这条血缘链上,新行是唯一活待办(兄弟分支不在本次废弃面);重办=普通 execute。
第二条是这篇最反直觉的地方:撤回不是把原行从 FINISHED 翻回 DOING,而是原行留着、另起一行新的任务。新行的 task_parent_id 也不是原行,而是原行当初的 parent——所以新行不是原行的下游,而是同节点的一次重新建单。
我这次真跑的数据:
- 原复核行
FINISHED 20,账本tf_recallHistory里recalledTaskId指向复活出来的新行; - 旧归档行从
DOING 10变成99(撤回时下游清场); - 复活复核行
DOING 10,新任务 id; - 重办提交后,新的归档行才重新建出来。
审批记录里这一段是完整的“同意 → 撤回 → 重办”:
图里 ① 是原复核行,意见还在,下面多了一条“由 <操作人 id> 撤回重办”;② 是被撤回废弃的旧归档行,所以是空白的;③ 是复活后的新复核行,重新提交;④ 是重办后新建的当前归档行。撤回留痕里存的是操作人 id,两个复核行是李娜。空白归档行的办理人、意见为空,是因为这两列读的是已办行的 u_realName/tf_approvalComment;未办结行没有这两个键。
流程图里只看节点:绿框=有历史行(含已废弃),橙框=当前。撤回后“经办复核”重新变橙(当前),归档确认的绿框只是被撤回时废弃的旧行,不是活待办;重办提交后,复核变绿,归档确认变橙、王强变当前。
五、为什么原记录不能改写
因为 jeeflow 没有一条独立的历史表,任务行本身即审批历史。
把已经 FINISHED 的行翻回 DOING,等于改写已经出证的记录:审批记录里那条“谁在什么时候同意了什么”就没了,后面的人再看到的就是一个正在办理的任务,而不是一条历史。
所以撤回的做法是“旧行留痕、另起一行”:原行永远是 FINISHED,账本追加在它身上;新行是一个新的任务 id,重办是一次普通办理。这样审批记录能呈现完整的“同意 → 撤回 → 重办”链,出证和重办互不覆盖。
账本只追加在原 FINISHED 行上;新行只带自己的建单变量,不复制旧账本。recalledTaskId 指向复活出来的新行,原行仍可继续出证。
六、怎么配,前端怎么接
节点上只有一个开关:
| 键 | 说明 |
|---|---|
allowRecall | bool,不写键=false;开启后该节点办理人可撤回自己刚办结的那一行。 |
解析期会拦下两类:会签节点配 allowRecall 报错;首任务节点(申请节点)配 allowRecall 报错。申请节点那手走实例撤回+重新发起,不走已办撤回。
设计器把它放在节点属性的“允许已办撤回”上:是/否。选“是”之后,这个节点的已办行才有撤回资格;窗口仍然由引擎在办理时判。
前端在“我的已办”的每一行上放“撤回”,点下去先弹“确定撤回吗?”,确认后调用:
POST /wf/processTask/recall
{
"operator": "<当前用户 id>",
"processTaskId": "<原已办任务 id>"
}
operator 由集成壳从登录态注入;直连门面时要显式带。
前端不预计算窗口——已办列表行不带节点属性,窗口和开关都由引擎判;前端只把引擎的逐字文案透出来。撤回成功后提示“已撤回,该节点已回到你的待办”,新行出现在“我的待办”。
所以已办行上的“撤回”按钮可能一直可见,即使这一行的窗口已经关了;点下去由引擎拒绝,报错原文透出。这不是前端漏判,是刻意把权威留在服务端。
七、边界:撤回只撤“推进”,不撤“业务数据”
- 业务变量与业务表不回滚:
f_已经进实例变量、persist 已经写业务表;撤回只撤“推进”,不撤业务数据。这是承诺边界,不是 bug。 - 记录类的“名册并委托”不适用:
snaker:custom的留痕行出生即 FINISHED,不许并代理人;这不是说 custom 节点被解析期禁配,禁配只有会签/申请节点。 - 并行分支:该行办结触发多支时,多支下游 DOING 全部废弃;重办后按正常出边重新生成。
- 事件码:撤回这一步落库后 fire 一次
TASK_RECALL(10),只发 10,不补发 5/6——撤回不是办结也不是拒绝;复活行是新建单,另走码 3。 - 不能撤回到任意指定环节:多步回跳是跳转(4)的既有能力,不在已办撤回里。
- 并发承诺边界:同一实例的办理在单进程部署下串行化;多进程部署的跨进程锁不在本特性承诺内。
- 窗口关闭时报错零写入:下游任一已办结、节点没开开关、实例不在进行中,都会在状态变更之前拒;任务状态 99=已废弃。
八、什么时候选它
- 选已办撤回:自己刚办结的那一手办错了,下游还没办完,想收回重办。
- 选退回上一步:当前这一手要推给上一节点重来。
- 选退回发起人:资料不齐,让发起人补,按流程重走。
- 选实例撤回:不想办这张单了,整张单停下。
- 选跳转:要多步回跳到某个指定环节。
一句话:已办撤回只收回“自己已经办结的那一手”,不撤整张单,也不替别人做决定。
八种语言的引擎按同一份契约实现,参考实现是 Java。我这次真跑的是 boot3 演示栈(Java 参考实现 + vben5 前端),实例、任务行、账本都在上面;其余七种语言这次只对照了规范和共享测试流程,没有逐个做 UI 走查。
落地清单
- 确认引擎代次带
allowRecall,设计器节点属性有“允许已办撤回”。 - 只在普通审批节点上开;会签节点、申请节点解析期就会拒。
- 需要撤回时,确认下游整条链没有已办结;不是只看下一手。
- 前端已办行放“撤回”并做二次确认;窗口和开关由引擎判,报错原文透出。
- 上线前跑一遍自己的样本:办结 → 下游待办 → 撤回 → 看原行是否还是 FINISHED、新行是否同节点、旧下游是否 99;再重办,看审批记录是否出现“同意 → 撤回 → 重办”。
参考资料
jeeflow 文档站 规范 04「引擎操作」——已办撤回(recall) jeeflow-doc.mldong.com/spec/04-eng…
jeeflow 文档站 规范 02「流程定义」——已办撤回开关(allowRecall)
jeeflow-doc.mldong.com/spec/02-flo…
jeeflow 文档站 规范 06「统一门面」——processTask/recall 与 tf_recallHistory
jeeflow-doc.mldong.com/spec/06-fac…
jeeflow 文档站 更新日志(八引擎各自代次) jeeflow-doc.mldong.com/changelog/
jeeflow Java 参考实现 github.com/mldong/jeef…
八语言演示站(在线可试) jeeflow-demo.mldong.com/