feat: 发起时指派——assignPolicy 配置/assign_json 快照/引擎收窄/preview+apply 契约
build / build-push (push) Successful in 2m8s
build / build-push (push) Successful in 2m8s
This commit is contained in:
@@ -527,6 +527,54 @@ class DefValidatorTest {
|
||||
assertTrue(e.getMessage().contains("出口必须命名"));
|
||||
}
|
||||
|
||||
// ===== 发起时指派 assignPolicy(2026-10-13) =====
|
||||
|
||||
@Test
|
||||
void assign_singleWithWrongCount_fails() {
|
||||
String json = "{"
|
||||
+ "\"code\":\"c1\",\"name\":\"流程\",\"start\":\"start\","
|
||||
+ "\"nodes\":["
|
||||
+ "{\"id\":\"start\",\"type\":\"START\"},"
|
||||
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"ybb\"},"
|
||||
+ "\"assignPolicy\":{\"mode\":\"SINGLE\",\"min\":2}},"
|
||||
+ "{\"id\":\"end\",\"type\":\"END\"}],"
|
||||
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}]}";
|
||||
BizException e = assertThrows(BizException.class,
|
||||
() -> DefValidator.validate(parse(json), new HashSet<>(Collections.singletonList("ybb"))));
|
||||
assertTrue(e.getMessage().contains("SINGLE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assign_minGreaterThanMax_fails() {
|
||||
String json = "{"
|
||||
+ "\"code\":\"c1\",\"name\":\"流程\",\"start\":\"start\","
|
||||
+ "\"nodes\":["
|
||||
+ "{\"id\":\"start\",\"type\":\"START\"},"
|
||||
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"ybb\"},"
|
||||
+ "\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":3,\"max\":2}},"
|
||||
+ "{\"id\":\"end\",\"type\":\"END\"}],"
|
||||
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}]}";
|
||||
BizException e = assertThrows(BizException.class,
|
||||
() -> DefValidator.validate(parse(json), new HashSet<>(Collections.singletonList("ybb"))));
|
||||
assertTrue(e.getMessage().contains("min 不得大于 max"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assign_validMulti_passes() {
|
||||
String json = "{"
|
||||
+ "\"code\":\"c1\",\"name\":\"流程\",\"start\":\"start\","
|
||||
+ "\"nodes\":["
|
||||
+ "{\"id\":\"start\",\"type\":\"START\"},"
|
||||
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"ybb\"},"
|
||||
+ "\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":1,\"max\":3,\"required\":true}},"
|
||||
+ "{\"id\":\"end\",\"type\":\"END\"}],"
|
||||
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}]}";
|
||||
DefValidator.validate(parse(json), new HashSet<>(Collections.singletonList("ybb")));
|
||||
// 无 assignPolicy(存量定义):向前兼容,照常通过
|
||||
String legacy = json.replace(",\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":1,\"max\":3,\"required\":true}", "");
|
||||
DefValidator.validate(parse(legacy), new HashSet<>(Collections.singletonList("ybb")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void overviewSample_def() {
|
||||
// 概览 §5.1 缩样(并行+分支内串行):配齐 6 个 DEPT_KEY 映射应通过
|
||||
|
||||
@@ -98,11 +98,17 @@ class FlowEngineIT extends FlowTestSupport {
|
||||
|
||||
private FlowInstanceService.ApplyOutcome apply(String flowGuid, String bizId,
|
||||
Integer submitterId, String submitterName) {
|
||||
return apply(flowGuid, bizId, submitterId, submitterName, null);
|
||||
}
|
||||
|
||||
/** 发起时指派用例直灌快照入口:仍走 ApplyCommand 主链(契约校验在 OapiFlowContractIT 覆盖) */
|
||||
private FlowInstanceService.ApplyOutcome apply(String flowGuid, String bizId,
|
||||
Integer submitterId, String submitterName, String assignJson) {
|
||||
FlwDef def = defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
|
||||
SCH, flowGuid, 1).orElseThrow(IllegalStateException::new);
|
||||
FlowInstanceService.ApplyCommand cmd = new FlowInstanceService.ApplyCommand(
|
||||
SCH, def, "svc_item", bizId, "张三的测试办件", "http://biz/1", "{\"k\":\"v\"}",
|
||||
submitterId, submitterName, null, "100001", "notice");
|
||||
submitterId, submitterName, null, "100001", "notice", assignJson);
|
||||
return instanceService.apply(cmd);
|
||||
}
|
||||
|
||||
@@ -776,6 +782,158 @@ class FlowEngineIT extends FlowTestSupport {
|
||||
assertEquals(2, logs.get(1).getAttempt());
|
||||
}
|
||||
|
||||
// ===== 发起时指派(2026-10-13):快照收窄/宽松回退/退回存活/R4 覆盖 =====
|
||||
|
||||
/** 三人 USERS 或签节点(USERS 直配不经科室映射,指派收窄用例共用) */
|
||||
private String usersAnyDef(String nodeName) {
|
||||
return "{"
|
||||
+ "\"code\":\"t25\",\"name\":\"USERS或签测试流程\",\"start\":\"start\","
|
||||
+ "\"nodes\":["
|
||||
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
|
||||
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"" + nodeName + "\","
|
||||
+ "\"assignee\":{\"type\":\"USERS\",\"userIds\":[101,102,103]},\"countersign\":\"ANY\"},"
|
||||
+ "{\"id\":\"end\",\"type\":\"END\"}"
|
||||
+ "],"
|
||||
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}],"
|
||||
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
|
||||
}
|
||||
|
||||
/** 或签 3 候选指派 1 人:只为被指派人建任务,该人办理即完成(收窄不改变 ANY 放行语义,I1) */
|
||||
@Test
|
||||
void assign_singleNarrows_anySign() {
|
||||
FlwDef def = saveAndPublishDef("t26", "指派单收窄", usersAnyDef("任一人审批"));
|
||||
FlowInstanceService.ApplyOutcome outcome = apply(def.getFlowGuid(), "biz-26", 201, "张三",
|
||||
"{\"n1\":[{\"userId\":101,\"userName\":\"甲\"}]}");
|
||||
Long instanceId = outcome.getInstanceId();
|
||||
assertEquals(1, taskRepository.findByInstanceIdAndStatus(instanceId, 0).size());
|
||||
assertTrue(tasksOf(instanceId, 102).isEmpty(), "未被指派人不建任务");
|
||||
assertTrue(tasksOf(instanceId, 103).isEmpty(), "未被指派人不建任务");
|
||||
taskService.handle(SCH, tasksOf(instanceId, 101).get(0).getId(), true, "ok", 101);
|
||||
assertEquals(FlwEnums.InstanceStatus.COMPLETED.getCode(),
|
||||
instanceRepository.findById(instanceId).get().getStatus());
|
||||
}
|
||||
|
||||
/** 或签 3 候选指派 2 人:任一人办理即放行,另一条失效,办件完成 */
|
||||
@Test
|
||||
void assign_twoNarrows_anySign_othersInvalidated() {
|
||||
FlwDef def = saveAndPublishDef("t27", "指派双收窄", usersAnyDef("任一人审批"));
|
||||
FlowInstanceService.ApplyOutcome outcome = apply(def.getFlowGuid(), "biz-27", 201, "张三",
|
||||
"{\"n1\":[{\"userId\":101,\"userName\":\"甲\"},{\"userId\":102,\"userName\":\"乙\"}]}");
|
||||
Long instanceId = outcome.getInstanceId();
|
||||
assertEquals(2, taskRepository.findByInstanceIdAndStatus(instanceId, 0).size());
|
||||
taskService.handle(SCH, tasksOf(instanceId, 101).get(0).getId(), true, "我先批", 101);
|
||||
assertEquals(FlwEnums.InstanceStatus.COMPLETED.getCode(),
|
||||
instanceRepository.findById(instanceId).get().getStatus());
|
||||
assertEquals(FlwEnums.TaskStatus.INVALIDATED.getCode(),
|
||||
allTasks(instanceId).stream().filter(t -> t.getAssigneeId().equals(102))
|
||||
.findFirst().get().getStatus());
|
||||
}
|
||||
|
||||
/** 会签 3 候选指派 2 人:收窄=指派人全签(2 条待办),全部同意才完成 */
|
||||
@Test
|
||||
void assign_allNarrows_countersignAll() {
|
||||
mapDept("ky", 101, "甲");
|
||||
mapDept("ky", 102, "乙");
|
||||
mapDept("ky", 103, "丙");
|
||||
FlwDef def = saveAndPublishDef("t28", "指派会签", linearDef("ky", "联合审核"));
|
||||
FlowInstanceService.ApplyOutcome outcome = apply(def.getFlowGuid(), "biz-28", 201, "张三",
|
||||
"{\"n1\":[{\"userId\":101,\"userName\":\"甲\"},{\"userId\":102,\"userName\":\"乙\"}]}");
|
||||
Long instanceId = outcome.getInstanceId();
|
||||
List<FlwTask> pending = taskRepository.findByInstanceIdAndStatus(instanceId, 0);
|
||||
assertEquals(2, pending.size());
|
||||
// 第一人同意:会签未清零仍在途
|
||||
taskService.handle(SCH, tasksOf(instanceId, 101).get(0).getId(), true, "ok", 101);
|
||||
assertEquals(FlwEnums.InstanceStatus.APPROVING.getCode(),
|
||||
instanceRepository.findById(instanceId).get().getStatus());
|
||||
// 第二人同意 → 完成
|
||||
taskService.handle(SCH, tasksOf(instanceId, 102).get(0).getId(), true, "ok", 102);
|
||||
assertEquals(FlwEnums.InstanceStatus.COMPLETED.getCode(),
|
||||
instanceRepository.findById(instanceId).get().getStatus());
|
||||
}
|
||||
|
||||
/** assignJson 缺失 / 快照条目为空数组:均回退全量候选(宽松回退 D3) */
|
||||
@Test
|
||||
void assign_missingOrEmpty_fallsBackToFull() {
|
||||
FlwDef def = saveAndPublishDef("t29", "无指派回退", usersAnyDef("任一人审批"));
|
||||
FlowInstanceService.ApplyOutcome a = apply(def.getFlowGuid(), "biz-29a", 201, "张三", null);
|
||||
assertEquals(3, taskRepository.findByInstanceIdAndStatus(a.getInstanceId(), 0).size());
|
||||
FlowInstanceService.ApplyOutcome b = apply(def.getFlowGuid(), "biz-29b", 201, "张三", "{\"n1\":[]}");
|
||||
assertEquals(3, taskRepository.findByInstanceIdAndStatus(b.getInstanceId(), 0).size());
|
||||
}
|
||||
|
||||
/** 脏 assignJson:log warn 后回退全量(引擎永不因缺指派拒绝或挂起) */
|
||||
@Test
|
||||
void assign_dirtyJson_fallsBack() {
|
||||
FlwDef def = saveAndPublishDef("t30", "脏快照回退", usersAnyDef("任一人审批"));
|
||||
FlowInstanceService.ApplyOutcome outcome = apply(def.getFlowGuid(), "biz-30", 201, "张三", "not-json");
|
||||
assertEquals(3, taskRepository.findByInstanceIdAndStatus(outcome.getInstanceId(), 0).size());
|
||||
}
|
||||
|
||||
/** 退回重入:快照沿用,重入节点仍只发原指派人(I5) */
|
||||
@Test
|
||||
void assign_survivesReturnReentry() {
|
||||
mapDept("km", 103, "主");
|
||||
mapDept("km", 104, "副");
|
||||
mapDept("ka", 101, "甲");
|
||||
mapDept("kb", 102, "乙");
|
||||
FlwDef def = saveAndPublishDef("t31", "退回指派存活", mainlineParallelTailDef("km", "km"));
|
||||
FlowInstanceService.ApplyOutcome outcome = apply(def.getFlowGuid(), "biz-31", 201, "张三",
|
||||
"{\"n3\":[{\"userId\":103,\"userName\":\"主\"}]}");
|
||||
Long instanceId = outcome.getInstanceId();
|
||||
// 推进到 n3:n1 会签(主+副)→ 并行甲乙 → 出口复核只发给被指派的"主"
|
||||
taskService.handle(SCH, tasksOf(instanceId, 103).get(0).getId(), true, "ok", 103);
|
||||
taskService.handle(SCH, tasksOf(instanceId, 104).get(0).getId(), true, "ok", 104);
|
||||
taskService.handle(SCH, tasksOf(instanceId, 101).get(0).getId(), true, "ok", 101);
|
||||
taskService.handle(SCH, tasksOf(instanceId, 102).get(0).getId(), true, "ok", 102);
|
||||
FlwTask tail = tasksOf(instanceId, 103).get(0);
|
||||
assertEquals("n3", tail.getNodeId());
|
||||
assertTrue(tasksOf(instanceId, 104).isEmpty(), "n3 只给被指派人建任务");
|
||||
// 退回至入口 n1 重走
|
||||
taskService.returnBack(SCH, tail.getId(), null, "重新审一遍", 103);
|
||||
assertEquals("n1", instanceRepository.findById(instanceId).get().getCurrentNodes());
|
||||
taskService.handle(SCH, tasksOf(instanceId, 103).get(0).getId(), true, "再入口", 103);
|
||||
taskService.handle(SCH, tasksOf(instanceId, 104).get(0).getId(), true, "ok", 104);
|
||||
taskService.handle(SCH, tasksOf(instanceId, 101).get(0).getId(), true, "ok", 101);
|
||||
taskService.handle(SCH, tasksOf(instanceId, 102).get(0).getId(), true, "ok", 102);
|
||||
// 重入 n3:快照沿用,仍只发原指派人
|
||||
FlwTask tailAgain = tasksOf(instanceId, 103).get(0);
|
||||
assertEquals("n3", tailAgain.getNodeId());
|
||||
assertTrue(tasksOf(instanceId, 104).isEmpty());
|
||||
taskService.handle(SCH, tailAgain.getId(), true, "复核通过", 103);
|
||||
assertEquals(FlwEnums.InstanceStatus.COMPLETED.getCode(),
|
||||
instanceRepository.findById(instanceId).get().getStatus());
|
||||
}
|
||||
|
||||
/** R4 重启:带新指派覆盖快照;不带 assigns(null=清空)回全量(覆盖语义 I5) */
|
||||
@Test
|
||||
void assign_r4RestartOverwrites() {
|
||||
mapDept("ky", 101, "甲");
|
||||
mapDept("ky", 102, "乙");
|
||||
mapDept("ky", 103, "丙");
|
||||
FlwDef def = saveAndPublishDef("t32", "R4指派覆盖", linearDef("ky", "联合审核"));
|
||||
// 首轮:指派 101 → 单待办;拒绝到 30
|
||||
FlowInstanceService.ApplyOutcome first = apply(def.getFlowGuid(), "biz-32", 201, "张三",
|
||||
"{\"n1\":[{\"userId\":101,\"userName\":\"甲\"}]}");
|
||||
assertEquals(1, taskRepository.findByInstanceIdAndStatus(first.getInstanceId(), 0).size());
|
||||
taskService.handle(SCH, tasksOf(first.getInstanceId(), 101).get(0).getId(), false, "不合格", 101);
|
||||
assertEquals(FlwEnums.InstanceStatus.REJECTED.getCode(),
|
||||
instanceRepository.findById(first.getInstanceId()).get().getStatus());
|
||||
// R4 重提带新指派 → 快照覆盖为 102 子集
|
||||
FlowInstanceService.ApplyOutcome second = apply(def.getFlowGuid(), "biz-32", 201, "张三",
|
||||
"{\"n1\":[{\"userId\":102,\"userName\":\"乙\"}]}");
|
||||
assertTrue(second.isDuplicated());
|
||||
assertEquals(first.getInstanceId(), second.getInstanceId());
|
||||
List<FlwTask> pending = taskRepository.findByInstanceIdAndStatus(first.getInstanceId(), 0);
|
||||
assertEquals(1, pending.size());
|
||||
assertEquals(102, pending.get(0).getAssigneeId());
|
||||
// 再拒后重提不带指派(置空)→ 全量 3 条
|
||||
taskService.handle(SCH, pending.get(0).getId(), false, "仍不合格", 102);
|
||||
FlowInstanceService.ApplyOutcome third = apply(def.getFlowGuid(), "biz-32", 201, "张三", null);
|
||||
assertTrue(third.isDuplicated());
|
||||
assertEquals(3, taskRepository.findByInstanceIdAndStatus(first.getInstanceId(), 0).size());
|
||||
assertNull(instanceRepository.findById(first.getInstanceId()).get().getAssignJson());
|
||||
}
|
||||
|
||||
private List<FlwBranch> branchRows(Long instanceId) {
|
||||
return branchRepository.findByInstanceId(instanceId);
|
||||
}
|
||||
|
||||
@@ -584,6 +584,131 @@ class OapiFlowContractIT extends FlowTestSupport {
|
||||
assertEquals(1, def.getVersion().intValue(), "引用自注册行的 SERVICE 定义应发布成功");
|
||||
}
|
||||
|
||||
// ===== 发起时指派契约(2026-10-13):apply/preview + apply assigns 严格校验 =====
|
||||
|
||||
/** 指派契约用例定义:START → n1(可配 assignPolicy)→ n2(不开指派)→ END,两节点同科室 */
|
||||
private String assignDef(String deptTag, String n1Policy) {
|
||||
return "{"
|
||||
+ "\"code\":\"t40\",\"name\":\"指派契约流程\",\"start\":\"start\","
|
||||
+ "\"nodes\":["
|
||||
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
|
||||
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"首审\","
|
||||
+ "\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + dk(deptTag) + "\"},"
|
||||
+ "\"countersign\":\"ANY\"" + n1Policy + "},"
|
||||
+ "{\"id\":\"n2\",\"type\":\"APPROVAL\",\"name\":\"复审\","
|
||||
+ "\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + dk(deptTag) + "\"}},"
|
||||
+ "{\"id\":\"end\",\"type\":\"END\"}"
|
||||
+ "],"
|
||||
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"n2\"},"
|
||||
+ "{\"from\":\"n2\",\"to\":\"end\"}],"
|
||||
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
|
||||
}
|
||||
|
||||
/** 预览契约:只暴露开启指派节点;candidates 带姓名且剔除提交人;selection 缺省补齐;countersign 透传 */
|
||||
@Test
|
||||
void applyPreview_exposesOnlyDesignatableNodes() {
|
||||
mapDept("ybb", 101, "甲");
|
||||
mapDept("ybb", 201, "张三");
|
||||
FlwDef def = saveAndPublishDef("t41", "指派预览",
|
||||
assignDef("ybb", ",\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":1}"));
|
||||
ResponseEntity<JsonNode> resp = client.get("/oapi/flow/apply/preview?schId=1&flowGuid="
|
||||
+ def.getFlowGuid() + "&submitterId=201", JsonNode.class);
|
||||
assertEquals(200, resp.getStatusCodeValue());
|
||||
assertEquals(0, resp.getBody().get("code").asInt());
|
||||
JsonNode data = unwrap(resp.getBody());
|
||||
assertEquals(def.getFlowGuid(), data.get("flowGuid").asText());
|
||||
JsonNode nodes = data.get("nodes");
|
||||
assertEquals(1, nodes.size(), "只暴露开启指派的节点(n2 未开不透出)");
|
||||
JsonNode n1 = nodes.get(0);
|
||||
assertEquals("n1", n1.get("nodeId").asText());
|
||||
assertEquals("首审", n1.get("nodeName").asText());
|
||||
assertEquals("ANY", n1.get("countersign").asText(), "countersign 透传");
|
||||
JsonNode selection = n1.get("selection");
|
||||
assertEquals("MULTI", selection.get("mode").asText());
|
||||
assertEquals(1, selection.get("min").asInt());
|
||||
assertEquals(1, selection.get("max").asInt(), "max 缺省=剔除提交人后的候选数");
|
||||
assertFalse(selection.get("required").asBoolean(), "required 缺省补 false");
|
||||
JsonNode candidates = n1.get("candidates");
|
||||
assertEquals(1, candidates.size(), "候选剔除提交人本人");
|
||||
assertEquals(101, candidates.get(0).get("userId").asInt());
|
||||
assertEquals("甲", candidates.get(0).get("userName").asText());
|
||||
}
|
||||
|
||||
/** 预览降级(I6):开启节点挂无映射科室 → 该节点 candidates=[],接口照常 200 */
|
||||
@Test
|
||||
void applyPreview_deadDept_degradesToEmptyCandidates() {
|
||||
mapDept("ybb", 101, "甲");
|
||||
FlwDef def = saveAndPublishDef("t41b", "指派预览降级",
|
||||
assignDef("nobody", ",\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":1,\"max\":2}"));
|
||||
ResponseEntity<JsonNode> resp = client.get("/oapi/flow/apply/preview?schId=1&flowGuid="
|
||||
+ def.getFlowGuid() + "&submitterId=201", JsonNode.class);
|
||||
assertEquals(200, resp.getStatusCodeValue());
|
||||
assertEquals(0, resp.getBody().get("code").asInt());
|
||||
JsonNode n1 = unwrap(resp.getBody()).get("nodes").get(0);
|
||||
assertEquals("n1", n1.get("nodeId").asText());
|
||||
assertEquals(0, n1.get("candidates").size(), "解析异常节点降级空候选");
|
||||
}
|
||||
|
||||
/** 合法 assigns:200 + 快照落库(姓名走 requireUserName 严格口径)+ 只为被指派人建待办 */
|
||||
@Test
|
||||
void apply_withAssigns_ok() throws Exception {
|
||||
mapDept("ybb", 101, "甲");
|
||||
mapDept("ybb", 102, "乙");
|
||||
FlwDef def = saveAndPublishDef("t42", "指派发起",
|
||||
assignDef("ybb", ",\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":1,\"max\":2}"));
|
||||
String body = "{"
|
||||
+ "\"flowGuid\":\"" + def.getFlowGuid() + "\","
|
||||
+ "\"bizType\":\"svc_item\",\"bizId\":\"apply-as1\",\"bizTitle\":\"指派件\","
|
||||
+ "\"submitterId\":201,\"submitterName\":\"张三\","
|
||||
+ "\"assigns\":[{\"nodeId\":\"n1\",\"userIds\":[101]}]}";
|
||||
ResponseEntity<JsonNode> resp = client.post("/oapi/flow/apply?schId=1", body, JsonNode.class);
|
||||
assertEquals(0, resp.getBody().get("code").asInt());
|
||||
Long instanceId = unwrap(resp.getBody()).get("instanceId").asLong();
|
||||
JsonNode snap = objectMapper.readTree(
|
||||
instanceRepository.findById(instanceId).get().getAssignJson());
|
||||
assertEquals(101, snap.get("n1").get(0).get("userId").asInt());
|
||||
assertEquals("用户101", snap.get("n1").get(0).get("userName").asText(), "姓名=UserBase 严格口径");
|
||||
assertEquals(1, taskRepository.findByInstanceIdAndStatus(instanceId, 0).size());
|
||||
assertEquals(101, taskRepository.findByInstanceIdAndStatus(instanceId, 0).get(0).getAssigneeId());
|
||||
}
|
||||
|
||||
/** 拒绝矩阵(I7/I3):六种非法 assigns 均业务码拒绝且办件未创建 */
|
||||
@Test
|
||||
void apply_assigns_rejected_matrix() {
|
||||
mapDept("ybb", 101, "甲");
|
||||
mapDept("ybb", 102, "乙");
|
||||
FlwDef def = saveAndPublishDef("t43", "指派拒绝矩阵",
|
||||
assignDef("ybb", ",\"assignPolicy\":{\"mode\":\"MULTI\",\"min\":1,\"max\":1}"));
|
||||
FlwDef singleDef = saveAndPublishDef("t44", "单选拒绝",
|
||||
assignDef("ybb", ",\"assignPolicy\":{\"mode\":\"SINGLE\"}"));
|
||||
|
||||
assertAssignRejected(def.getFlowGuid(), "as-x1", ",\"assigns\":[{\"nodeId\":\"n2\",\"userIds\":[101]}]}",
|
||||
"未开启指派节点应拒绝");
|
||||
assertAssignRejected(def.getFlowGuid(), "as-x2", ",\"assigns\":[{\"nodeId\":\"n9\",\"userIds\":[101]}]}",
|
||||
"nodeId 不存在应拒绝");
|
||||
assertAssignRejected(def.getFlowGuid(), "as-x3", ",\"assigns\":[{\"nodeId\":\"n1\",\"userIds\":[999]}]}",
|
||||
"越候选集应拒绝");
|
||||
assertAssignRejected(def.getFlowGuid(), "as-x4", ",\"assigns\":[{\"nodeId\":\"n1\",\"userIds\":[201]}]}",
|
||||
"指派发起人本人应拒绝");
|
||||
assertAssignRejected(singleDef.getFlowGuid(), "as-x5",
|
||||
",\"assigns\":[{\"nodeId\":\"n1\",\"userIds\":[101,102]}]}", "SINGLE 传 2 人应拒绝");
|
||||
assertAssignRejected(def.getFlowGuid(), "as-x6",
|
||||
",\"assigns\":[{\"nodeId\":\"n1\",\"userIds\":[101,102]}]}", "MULTI 超 max 应拒绝");
|
||||
}
|
||||
|
||||
/** 断言一次带 assigns 的 apply 被业务码拒绝且办件未创建 */
|
||||
private void assertAssignRejected(String flowGuid, String bizId, String assignsSegment, String message) {
|
||||
String body = "{"
|
||||
+ "\"flowGuid\":\"" + flowGuid + "\","
|
||||
+ "\"bizType\":\"svc_item\",\"bizId\":\"" + bizId + "\",\"bizTitle\":\"矩阵\","
|
||||
+ "\"submitterId\":201,\"submitterName\":\"张三\"" + assignsSegment;
|
||||
ResponseEntity<JsonNode> resp = client.post("/oapi/flow/apply?schId=1", body, JsonNode.class);
|
||||
assertEquals(200, resp.getStatusCodeValue());
|
||||
assertNotEquals(0, resp.getBody().get("code").asInt(), message);
|
||||
assertFalse(instanceRepository.findBySchIdAndBizTypeAndBizId(1L, "svc_item", bizId).isPresent(),
|
||||
message + "(办件未创建)");
|
||||
}
|
||||
|
||||
/** 轮询等异步回调推送(AFTER_COMMIT + 推送池),最长约 5s */
|
||||
private boolean awaitPayloadContains(List<String> payloads, String needle) throws InterruptedException {
|
||||
for (int i = 0; i < 25; i++) {
|
||||
|
||||
Reference in New Issue
Block a user