feat: 引擎多出口路由/退回/移交/或签补齐 + 定义 layout 坐标
build / build-push (push) Successful in 5m11s

- 多出口:FlwTask 增 edge_name 列(sql/20260916),FlwTaskRepository.claimAndFinish
  同步落列;FlowTaskService.handle 出度>1 时 edgeName 必填;FlowEngine.continueAdvance
  按 edge name 选路(单出口原路径逐字节兼容,未命中不推进);FlowAdvanceService/
  FlowNodeAdvanceListener 透传;BranchScope 从 FlowEngine 提取
- DefValidator:多出口出口命名必填且同节点唯一;跨分支连线发布期明确拒绝
- 退回 POST /task/{id}/returnBack:默认退主管线最近已批节点,精确失效当前节点待办,
  split 幂等 upsert 使退回重走并行段不撞 flw_branch uk
- 移交 POST /task/{id}/transfer:TRANSFERRED(5)+新建待办,AssigneeResolver.requireUserName
- 或签 ANY 补齐:首个同意失效同节点兄弟待办
- 已办口径/撤回守卫:status in (1,2,4,5);新事件 flow.returned/flow.transferred
- FlowDiagramService:EdgeVo.name/NodeVo.state/DiagramVo.layout,办件图状态计算
- 新 IT:多出口×3、ANY 或签、退回重走并行段、退回/移交/撤回

mvn test 84/84 绿
This commit is contained in:
chenxing
2026-09-16 22:23:02 +08:00
parent 55e3a3f800
commit b8441065d0
31 changed files with 981 additions and 126 deletions
@@ -103,4 +103,55 @@ public abstract class FlowTestSupport {
private static String codeOf() {
return "tc_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8);
}
/** 多出口流程:START → n1(deptA,出口"转乙科"→n2 / "通过"→end) → end;n2(deptB) → end */
protected String multiOutletDef(String deptA, String deptB) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"多出口测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"入口审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"n2\",\"type\":\"APPROVAL\",\"name\":\"复核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptB + "\"}},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},"
+ "{\"from\":\"n1\",\"to\":\"n2\",\"name\":\"转乙科\"},"
+ "{\"from\":\"n1\",\"to\":\"end\",\"name\":\"通过\"},"
+ "{\"from\":\"n2\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 或签流程:START → n1(deptKey, ANY 或签) → END */
protected String anyCountersignDef(String deptKey) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"夡签测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"任一人审批\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptKey + "\"},\"countersign\":\"ANY\"},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 主线带并行段:START → n1(deptA) → split → {ta(ka), tb(kb)} → join → n3(deptA) → END(退回重入用) */
protected String mainlineParallelTailDef(String deptA, String deptMain) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"主线并行尾验流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"入口\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptMain + "\"}},"
+ "{\"id\":\"split1\",\"type\":\"PARALLEL_SPLIT\"},"
+ "{\"id\":\"ta\",\"type\":\"APPROVAL\",\"name\":\"甲科审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"ka\"}},"
+ "{\"id\":\"tb\",\"type\":\"APPROVAL\",\"name\":\"乙科审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"kb\"}},"
+ "{\"id\":\"join1\",\"type\":\"PARALLEL_JOIN\",\"waitFor\":\"ALL\"},"
+ "{\"id\":\"n3\",\"type\":\"APPROVAL\",\"name\":\"出口复核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"split1\"},"
+ "{\"from\":\"split1\",\"to\":\"ta\"},{\"from\":\"split1\",\"to\":\"tb\"},"
+ "{\"from\":\"ta\",\"to\":\"join1\"},{\"from\":\"tb\",\"to\":\"join1\"},"
+ "{\"from\":\"join1\",\"to\":\"n3\"},{\"from\":\"n3\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
}