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
@@ -1,5 +1,7 @@
package com.yunhai.flowcenter.service;
import com.yunhai.flowcenter.engine.BranchScope;
import com.yunhai.flowcenter.engine.FlowDefModel;
import com.yunhai.flowcenter.engine.FlowEngine;
import com.yunhai.flowcenter.entity.FlwBranch;
import com.yunhai.flowcenter.entity.FlwDef;
@@ -111,6 +113,7 @@ public class FlowQueryService {
row.put("handleTime", str(t.getHandleTime()));
row.put("deadline", str(t.getDeadline()));
row.put("urgeCount", t.getUrgeCount());
row.put("edgeName", t.getEdgeName());
trace.add(row);
}
detail.setTrace(trace);
@@ -135,6 +138,53 @@ public class FlowQueryService {
}
FlwDef def = defRepository.findById(instance.getDefId()).orElse(null);
detail.setNodeSummary(def == null ? "" : FlowEngine.nodeSummary(flowEngine.parseModel(def)));
// 办理面板数据(出口/退回目标/移交标记):仅对持本人待办的查看者填充
FlwTask myPend = viewerId == null ? null : tasks.stream()
.filter(t -> viewerId.equals(t.getAssigneeId())
&& t.getStatus() == FlwEnums.TaskStatus.PENDING.getCode())
.findFirst().orElse(null);
if (myPend != null && def != null) {
FlowDefModel model = flowEngine.parseModel(def);
String curNodeId = myPend.getNodeId();
List<FlowDefModel.Edge> outs = model.outEdges(curNodeId);
if (outs.size() > 1) {
List<Map<String, Object>> outlets = new ArrayList<>();
for (FlowDefModel.Edge e : outs) {
Map<String, Object> o = new HashMap<>();
o.put("name", e.name);
o.put("toNodeId", e.to);
FlowDefModel.Node to = model.node(e.to);
o.put("toNodeName", to == null ? e.to
: (to.name == null || to.name.isEmpty() ? e.to : to.name));
outlets.add(o);
}
detail.setOutlets(outlets);
} else {
detail.setOutlets(new ArrayList<>());
}
// 可退回目标:办件已办过的主线审批节点(去重,排除当前节点)
Map<String, Map<String, Object>> targets = new java.util.LinkedHashMap<>();
for (FlwTask t : tasks) {
if (t.getStatus() != FlwEnums.TaskStatus.APPROVED.getCode()
|| t.getNodeId().equals(myPend.getNodeId())
|| BranchScope.keyOf(model, t.getNodeId()) != null) {
continue;
}
FlowDefModel.Node node = model.node(t.getNodeId());
if (node != null && FlowDefModel.TYPE_APPROVAL.equals(node.type)
&& !targets.containsKey(t.getNodeId())) {
Map<String, Object> r = new HashMap<>();
r.put("nodeId", node.id);
r.put("nodeName", node.name == null || node.name.isEmpty() ? node.id : node.name);
targets.put(node.id, r);
}
}
detail.setReturnTargets(new ArrayList<>(targets.values()));
detail.setCanTransfer(true);
} else {
detail.setOutlets(new ArrayList<>());
detail.setReturnTargets(new ArrayList<>());
}
return detail;
}