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
@@ -6,6 +6,7 @@ import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import java.util.List;
import java.util.Map;
/**
* 流程定义 JSON 模型(概览 §5.1 规范,一期 5 节点)。
@@ -38,6 +39,11 @@ public class FlowDefModel {
public List<Node> nodes;
public List<Edge> edges;
public Settings settings;
/**
* 画布坐标(nodeId → 左上角),仅前端设计器/查看器复用;引擎不消费。
* 缺失时前端自行做自动布局,故可选、可空。
*/
public Map<String, Layout> layout;
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Node {
@@ -65,7 +71,20 @@ public class FlowDefModel {
public static class Edge {
public String from;
public String to;
/**
* 出边名(按钮文案)。单出口可空;多出口节点必填且同节点内唯一,
* 办理人在办理页选它决定走哪条边(见 {@link #edgeByName})。
*/
public String name;
/** 出口条件(二期条件自动路由预留;一期只校验透传,引擎不消费) */
public String condition;
}
/** 画布坐标(前端设计器专用) */
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Layout {
public Integer x;
public Integer y;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -135,4 +154,17 @@ public class FlowDefModel {
}
return in;
}
/** 按出边名取边(多出口节点路由用);未命中返回 null */
public Edge edgeByName(String fromNodeId, String name) {
if (name == null) {
return null;
}
for (Edge e : outEdges(fromNodeId)) {
if (name.equals(e.name)) {
return e;
}
}
return null;
}
}