|
|
@@ -2,6 +2,7 @@ package com.qqflow.engine.domain.flow.service;
|
|
|
|
|
|
import com.qqflow.engine.config.security.LoginUser;
|
|
|
import com.qqflow.engine.domain.flow.dto.ApproveTaskDTO;
|
|
|
+import com.qqflow.engine.domain.flow.dto.NextNodeDTO;
|
|
|
import com.qqflow.engine.domain.flow.dto.ProcessProgressDTO;
|
|
|
import com.qqflow.engine.domain.flow.dto.StartProcessDTO;
|
|
|
import com.qqflow.engine.domain.flow.enums.DefinitionStatus;
|
|
|
@@ -166,6 +167,70 @@ class FlowSubNodeServiceTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 普通审批节点(未开启子节点):应能正常创建任务、审批通过并结束流程。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testNormalApprovalNodeWithoutSubNodes() {
|
|
|
+ Long definitionId = createDefinitionWithoutSubNodes();
|
|
|
+ Long instanceId = startProcess(definitionId);
|
|
|
+
|
|
|
+ List<ApprovalTask> tasks = approvalTaskMapper.selectByInstanceId(instanceId);
|
|
|
+ assertEquals(1, tasks.size(), "普通节点启动后应产生一个任务");
|
|
|
+ ApprovalTask task = tasks.get(0);
|
|
|
+ assertNull(task.getSubNodeId(), "普通节点任务不应有子节点ID");
|
|
|
+ assertEquals(TaskStatus.PENDING.getCode(), task.getTaskStatus());
|
|
|
+
|
|
|
+ // 详情页应能正常加载
|
|
|
+ ProcessProgressDTO progress = processInstanceService.getProgress(instanceId);
|
|
|
+ assertEquals(1, progress.getRemainingNodeCount(), "应剩余一个待审批节点");
|
|
|
+ assertTrue(progress.getNodes().isEmpty() || progress.getNodes().get(0).getSubNodes() == null,
|
|
|
+ "普通节点不应包含子节点进度");
|
|
|
+
|
|
|
+ approve(task.getId());
|
|
|
+ progress = processInstanceService.getProgress(instanceId);
|
|
|
+ assertEquals(5, progress.getInstance().getStatus(), "流程应已通过完成");
|
|
|
+ assertEquals(0, progress.getRemainingNodeCount(), "剩余节点数应为 0");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开启子节点但未配置任何子节点:应回退为普通审批节点处理,避免无任务可审导致流程卡死。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testApprovalNodeWithEmptySubNodesFallback() {
|
|
|
+ Long definitionId = createDefinitionWithEmptySubNodes();
|
|
|
+ Long instanceId = startProcess(definitionId);
|
|
|
+
|
|
|
+ List<ApprovalTask> tasks = approvalTaskMapper.selectByInstanceId(instanceId);
|
|
|
+ assertEquals(1, tasks.size(), "空子节点配置时应回退为普通节点并产生任务");
|
|
|
+ ApprovalTask task = tasks.get(0);
|
|
|
+ assertNull(task.getSubNodeId(), "回退为普通节点后任务不应有子节点ID");
|
|
|
+ assertEquals(TaskStatus.PENDING.getCode(), task.getTaskStatus());
|
|
|
+
|
|
|
+ approve(task.getId());
|
|
|
+ ProcessProgressDTO progress = processInstanceService.getProgress(instanceId);
|
|
|
+ assertEquals(5, progress.getInstance().getStatus(), "流程应已通过完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 子节点任务应能正确返回父节点的下游节点列表,用于前端选择下一节点。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void testGetNextNodesForSubNodeTask() {
|
|
|
+ Long definitionId = createDefinitionWithSubNodesAndNextApproval();
|
|
|
+ Long instanceId = startProcess(definitionId);
|
|
|
+
|
|
|
+ List<ApprovalTask> tasks = approvalTaskMapper.selectByInstanceId(instanceId);
|
|
|
+ ApprovalTask sub1Task = tasks.stream()
|
|
|
+ .filter(t -> "sub1".equals(t.getSubNodeId()) && TaskStatus.PENDING.getCode().equals(t.getTaskStatus()))
|
|
|
+ .findFirst()
|
|
|
+ .orElseThrow();
|
|
|
+
|
|
|
+ List<NextNodeDTO> nextNodes = approvalTaskService.getNextNodes(sub1Task.getId());
|
|
|
+ assertEquals(1, nextNodes.size(), "子节点任务应返回父节点的下游节点");
|
|
|
+ assertEquals("second_approval", nextNodes.get(0).getNodeId(), "应返回正确的下游审批节点");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 并行子节点驳回:驳回一个子节点,只影响该子节点,其他子节点继续,重新审批后流程结束。
|
|
|
*/
|
|
|
@Test
|
|
|
@@ -222,6 +287,62 @@ class FlowSubNodeServiceTest {
|
|
|
return processDefinitionService.saveDefinition(po);
|
|
|
}
|
|
|
|
|
|
+ private Long createDefinitionWithoutSubNodes() {
|
|
|
+ String modelJson = "{\"nodes\":["
|
|
|
+ + "{\"id\":\"start\",\"type\":\"start\"}"
|
|
|
+ + ", {\"id\":\"approval\",\"type\":\"approval\",\"name\":\"审批\","
|
|
|
+ + "\"properties\":{\"assigneeType\":\"ROLE\",\"assigneeValue\":\"super_admin\",\"approveMode\":\"or\"}}"
|
|
|
+ + ", {\"id\":\"end\",\"type\":\"end\"}],"
|
|
|
+ + "\"edges\":["
|
|
|
+ + "{\"sourceNodeId\":\"start\",\"targetNodeId\":\"approval\"}"
|
|
|
+ + ", {\"sourceNodeId\":\"approval\",\"targetNodeId\":\"end\"}]}";
|
|
|
+ ProcessDefinition po = new ProcessDefinition();
|
|
|
+ po.setProcessCode("NORMAL_FLOW_" + System.currentTimeMillis());
|
|
|
+ po.setProcessName("普通流程");
|
|
|
+ po.setModelJson(modelJson);
|
|
|
+ po.setStatus(DefinitionStatus.ENABLED.getCode());
|
|
|
+ return processDefinitionService.saveDefinition(po);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long createDefinitionWithEmptySubNodes() {
|
|
|
+ String modelJson = "{\"nodes\":["
|
|
|
+ + "{\"id\":\"start\",\"type\":\"start\"}"
|
|
|
+ + ", {\"id\":\"approval\",\"type\":\"approval\",\"name\":\"审批\","
|
|
|
+ + "\"properties\":{\"assigneeType\":\"ROLE\",\"assigneeValue\":\"super_admin\",\"approveMode\":\"or\",\"subNodesEnabled\":true,\"subNodeMode\":\"sequential\",\"subNodes\":[]}}"
|
|
|
+ + ", {\"id\":\"end\",\"type\":\"end\"}],"
|
|
|
+ + "\"edges\":["
|
|
|
+ + "{\"sourceNodeId\":\"start\",\"targetNodeId\":\"approval\"}"
|
|
|
+ + ", {\"sourceNodeId\":\"approval\",\"targetNodeId\":\"end\"}]}";
|
|
|
+ ProcessDefinition po = new ProcessDefinition();
|
|
|
+ po.setProcessCode("EMPTY_SUB_NODE_" + System.currentTimeMillis());
|
|
|
+ po.setProcessName("空子节点流程");
|
|
|
+ po.setModelJson(modelJson);
|
|
|
+ po.setStatus(DefinitionStatus.ENABLED.getCode());
|
|
|
+ return processDefinitionService.saveDefinition(po);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long createDefinitionWithSubNodesAndNextApproval() {
|
|
|
+ String subNodes = "[{\"id\":\"sub1\",\"name\":\"初审\",\"assigneeValue\":\"super_admin\",\"approveMode\":\"or\"}" +
|
|
|
+ ",{\"id\":\"sub2\",\"name\":\"复审\",\"assigneeValue\":\"super_admin\",\"approveMode\":\"or\"}]";
|
|
|
+ String modelJson = "{\"nodes\":["
|
|
|
+ + "{\"id\":\"start\",\"type\":\"start\"}"
|
|
|
+ + ", {\"id\":\"approval\",\"type\":\"approval\",\"name\":\"一级审批\","
|
|
|
+ + "\"properties\":{\"subNodesEnabled\":true,\"subNodeMode\":\"parallel\",\"subNodes\":" + subNodes + "}}"
|
|
|
+ + ", {\"id\":\"second_approval\",\"type\":\"approval\",\"name\":\"二级审批\","
|
|
|
+ + "\"properties\":{\"assigneeType\":\"ROLE\",\"assigneeValue\":\"super_admin\",\"approveMode\":\"or\"}}"
|
|
|
+ + ", {\"id\":\"end\",\"type\":\"end\"}],"
|
|
|
+ + "\"edges\":["
|
|
|
+ + "{\"sourceNodeId\":\"start\",\"targetNodeId\":\"approval\"}"
|
|
|
+ + ", {\"sourceNodeId\":\"approval\",\"targetNodeId\":\"second_approval\"}"
|
|
|
+ + ", {\"sourceNodeId\":\"second_approval\",\"targetNodeId\":\"end\"}]}";
|
|
|
+ ProcessDefinition po = new ProcessDefinition();
|
|
|
+ po.setProcessCode("SUB_NODE_WITH_NEXT_" + System.currentTimeMillis());
|
|
|
+ po.setProcessName("带子节点及下游节点流程");
|
|
|
+ po.setModelJson(modelJson);
|
|
|
+ po.setStatus(DefinitionStatus.ENABLED.getCode());
|
|
|
+ return processDefinitionService.saveDefinition(po);
|
|
|
+ }
|
|
|
+
|
|
|
private Long startProcess(Long definitionId) {
|
|
|
StartProcessDTO dto = new StartProcessDTO();
|
|
|
dto.setProcessDefinitionId(definitionId);
|