fix(trace): 科室视图抄送行补遮蔽——ccTraceRow 补 fillCcScope 取 node.receivers 落 assigneeType/deptKey/deptName(NOTIFY 用 receivers 而非 assignee,直接复用 fillAssigneeScope 会空填),applyAssigneeScope 抽公共落值段;FlowEngineIT 增 ccTraceRows_carryDeptScope_forDeptView + 原 notify 用例补 USERS 断言;FlowTestSupport 增 notifyDeptDef。mvn test 209 全绿
build / build-push (push) Successful in 49s

This commit is contained in:
chenxing
2026-10-12 20:45:29 +08:00
parent 62aff51fed
commit c02bdc5eca
3 changed files with 67 additions and 3 deletions
@@ -304,7 +304,8 @@ public class FlowQueryService {
rows.add(new TimedRow(r.getDecideTime(), 1, r.getId(), routeTraceRow(r))); rows.add(new TimedRow(r.getDecideTime(), 1, r.getId(), routeTraceRow(r)));
} }
for (FlwCc c : ccs) { for (FlwCc c : ccs) {
rows.add(new TimedRow(c.getSentTime(), 2, c.getId(), ccTraceRow(c))); rows.add(new TimedRow(c.getSentTime(), 2, c.getId(),
ccTraceRow(c, model, instance, deptNames)));
} }
rows.sort(Comparator.comparing((TimedRow r) -> r.time, rows.sort(Comparator.comparing((TimedRow r) -> r.time,
Comparator.nullsLast(Comparator.naturalOrder())) Comparator.nullsLast(Comparator.naturalOrder()))
@@ -355,8 +356,11 @@ public class FlowQueryService {
return row; return row;
} }
/** 抄送轨迹行(kind=CC):no taskId/opinion,接收人记在 assignee* 字段(前端复用人列) */ /** 抄送轨迹行(kind=CC):no taskId/opinion,接收人记在 assignee* 字段(前端复用人列)。
private static Map<String, Object> ccTraceRow(FlwCc c) { * 2026-10-12 补科室口径:抄送节点用 receivers,走 {@link #fillCcScope} 富化 assigneeType,
* 前端 dept 档据此显科室名 / "抄送对象",不再回落接收人真实姓名。 */
private Map<String, Object> ccTraceRow(FlwCc c, FlowDefModel model, FlwInstance instance,
Map<String, String> deptNames) {
Map<String, Object> row = new HashMap<>(); Map<String, Object> row = new HashMap<>();
row.put("rowKey", "cc" + c.getId()); row.put("rowKey", "cc" + c.getId());
row.put("ccId", c.getId()); row.put("ccId", c.getId());
@@ -367,6 +371,7 @@ public class FlowQueryService {
row.put("assigneeName", c.getUserName()); row.put("assigneeName", c.getUserName());
row.put("status", null); row.put("status", null);
row.put("time", str(c.getSentTime())); row.put("time", str(c.getSentTime()));
fillCcScope(row, model, c.getNodeId(), instance.getSchId(), deptNames);
return row; return row;
} }
@@ -469,6 +474,25 @@ public class FlowQueryService {
Long schId, Map<String, String> deptNames) { Long schId, Map<String, String> deptNames) {
FlowDefModel.Node node = model == null ? null : model.node(nodeId); FlowDefModel.Node node = model == null ? null : model.node(nodeId);
FlowDefModel.Assignee assignee = node == null ? null : node.assignee; FlowDefModel.Assignee assignee = node == null ? null : node.assignee;
applyAssigneeScope(row, assignee, schId, deptNames);
}
/**
* 抄送行(kind=CC)科室口径富化(2026-10-12):NOTIFY 节点的接收人存在 {@code receivers}
* 字段而非 {@code assignee},故不能直接复用 {@link #fillAssigneeScope}——否则取到的 assignee
* 恒为 null,dept 档下抄送行只能回落接收人真实姓名(原先的泄露点)。
* 节点在定义里找不到(重提后的老节点、已删节点)不填,前端统一显示"抄送对象"。
*/
private void fillCcScope(Map<String, Object> row, FlowDefModel model, String nodeId,
Long schId, Map<String, String> deptNames) {
FlowDefModel.Node node = model == null ? null : model.node(nodeId);
FlowDefModel.Assignee receivers = node == null ? null : node.receivers;
applyAssigneeScope(row, receivers, schId, deptNames);
}
/** 两种口径的公共落值:assigneeType 必填;DEPT_KEY 另填 deptKey + 映射里的科室名 */
private void applyAssigneeScope(Map<String, Object> row, FlowDefModel.Assignee assignee,
Long schId, Map<String, String> deptNames) {
if (assignee == null || assignee.type == null) { if (assignee == null || assignee.type == null) {
return; return;
} }
@@ -507,6 +507,10 @@ class FlowEngineIT extends FlowTestSupport {
kinds.add((String) row.get("kind")); kinds.add((String) row.get("kind"));
} }
assertEquals(java.util.Arrays.asList("SUBMIT", "CC", "CC", "APPROVE"), kinds); assertEquals(java.util.Arrays.asList("SUBMIT", "CC", "CC", "APPROVE"), kinds);
// 科室口径富化(2026-10-12):抄送行取节点的 receivers(不是 assignee,见 fillCcScope)——
// 否则 dept 档下抄送行拿不到 assigneeType,前端只能回落接收人真实姓名(原先的泄露点)
assertEquals("USERS", trace.get(1).get("assigneeType"));
assertEquals("USERS", trace.get(2).get("assigneeType"));
// 抄送我的:301 视角一行,回填办件信息 // 抄送我的:301 视角一行,回填办件信息
com.yunhai.platform.utils.model.BasePageResult<com.yunhai.flowcenter.model.FlowModels.CcItem> com.yunhai.platform.utils.model.BasePageResult<com.yunhai.flowcenter.model.FlowModels.CcItem>
ccPage = queryService.pageCc(SCH, 301, null, 1, 10); ccPage = queryService.pageCc(SCH, 301, null, 1, 10);
@@ -515,6 +519,27 @@ class FlowEngineIT extends FlowTestSupport {
assertEquals("张三的测试办件", ccPage.getContent().get(0).getBizTitle()); assertEquals("张三的测试办件", ccPage.getContent().get(0).getBizTitle());
} }
/**
* 抄送轨迹行的科室口径富化(2026-10-12):NOTIFY 节点的接收人存在 {@code receivers} 字段,
* 与审批节点的 {@code assignee} 不同名——富化若只认 assignee 就恒为空,dept 档下抄送行
* 拿不到科室名/类型,前端只能回落接收人真实姓名。本例锁死"科室抄送 → 科室名"这一路。
*/
@Test
void ccTraceRows_carryDeptScope_forDeptView() {
mapDept("km", 101, "甲");
FlwDef def = saveAndPublishDef("t16b", "抄送科室", notifyDeptDef("km"));
Long instanceId = apply(def.getFlowGuid(), "biz-16b", 201, "张三").getInstanceId();
java.util.List<java.util.Map<String, Object>> trace =
queryService.detail(SCH, instanceId, null).getTrace();
java.util.Map<String, Object> cc = trace.stream()
.filter(r -> "CC".equals(r.get("kind")))
.findFirst().orElseThrow(() -> new AssertionError("轨迹缺 kind=CC 行"));
assertEquals("DEPT_KEY", cc.get("assigneeType"));
assertEquals(dk("km"), cc.get("deptKey"));
assertEquals("km科", cc.get("deptName"));
}
/** NOTIFY 在并行分支内:瞬时到达 JOIN 不占等待(审批支到齐即放行),落库与轨迹同主线口径 */ /** NOTIFY 在并行分支内:瞬时到达 JOIN 不占等待(审批支到齐即放行),落库与轨迹同主线口径 */
@Test @Test
void notify_inParallelBranch_reachesJoin_withoutBlocking() { void notify_inParallelBranch_reachesJoin_withoutBlocking() {
@@ -167,6 +167,21 @@ public abstract class FlowTestSupport {
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}"; + "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
} }
/** 抄送主线流程(科室口径):START → cc(DEPT_KEY deptA) → n1(deptA) → END */
protected String notifyDeptDef(String deptA) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"抄送科室测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"cc1\",\"type\":\"NOTIFY\",\"name\":\"抄送科室\",\"receivers\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + dk(deptA) + "\"}},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + dk(deptA) + "\"}},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"cc1\"},{\"from\":\"cc1\",\"to\":\"n1\"},"
+ "{\"from\":\"n1\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 抄送并行流程:START → split → {n1(deptA), cc(USERS [301])} → join → END(抄送支瞬时到达汇聚) */ /** 抄送并行流程:START → split → {n1(deptA), cc(USERS [301])} → join → END(抄送支瞬时到达汇聚) */
protected String notifyParallelDef(String deptA) { protected String notifyParallelDef(String deptA) {
return "{" return "{"