From df936deecaf6bc1db9d40d0ae1c8dec84524bd08 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 7 Apr 2024 15:37:25 +0800 Subject: [PATCH 001/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E5=B9=B6=E8=A1=8C=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/definition/BpmSimpleModelNodeType.java | 7 +++++-- .../flowable/core/util/BpmnModelUtils.java | 14 +++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 4e26d02d9..af8ce3d6f 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -19,10 +19,12 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; START_EVENT_NODE(0, "开始节点"), - APPROVE_USER_NODE (1, "审批人节点"), + APPROVE_USER_NODE(1, "审批人节点"), // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML SCRIPT_TASK_NODE(2, "抄送人节点"), EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), + PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), + PARALLEL_GATEWAY_JOIN_NODE(6, "并行网关聚合节点"), END_EVENT_NODE(-2, "结束节点"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); @@ -32,7 +34,8 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { public static boolean isGatewayNode(Integer type) { // TODO 后续增加并行网关的支持 - return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type); + return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type) + || Objects.equals(PARALLEL_GATEWAY_FORK_NODE.getType(), type); } public static BpmSimpleModelNodeType valueOf(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 03745adce..c68908acf 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -385,12 +385,14 @@ public class BpmnModelUtils { switch (nodeType) { case START_EVENT_NODE: case APPROVE_USER_NODE: - case SCRIPT_TASK_NODE: { + case SCRIPT_TASK_NODE: + case PARALLEL_GATEWAY_JOIN_NODE:{ addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); // 递归调用后续节点 addBpmnSequenceFlow(mainProcess, childNode, endId); break; } + case PARALLEL_GATEWAY_FORK_NODE: case EXCLUSIVE_GATEWAY_NODE: { String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); List conditionNodes = node.getConditionNodes(); @@ -449,6 +451,10 @@ public class BpmnModelUtils { case EXCLUSIVE_GATEWAY_NODE: addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode); break; + case PARALLEL_GATEWAY_FORK_NODE: + case PARALLEL_GATEWAY_JOIN_NODE: + addBpmnParallelGatewayNode(mainProcess, simpleModelNode); + break; default: { // TODO 其它节点类型的实现 } @@ -472,6 +478,12 @@ public class BpmnModelUtils { } } + private static void addBpmnParallelGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) { + ParallelGateway parallelGateway = new ParallelGateway(); + parallelGateway.setId(node.getId()); + mainProcess.addFlowElement(parallelGateway); + } + private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) { ScriptTask scriptTask = new ScriptTask(); scriptTask.setId(node.getId()); From 98998cff6ff7198ead930e7f17bb6782252a926c Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 7 Apr 2024 22:20:46 +0800 Subject: [PATCH 002/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E5=8C=85=E5=AE=B9=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 7 +++--- .../flowable/core/util/BpmnModelUtils.java | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index af8ce3d6f..bfaad44c9 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -25,6 +25,8 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), PARALLEL_GATEWAY_JOIN_NODE(6, "并行网关聚合节点"), + INCLUSIVE_GATEWAY_FORK_NODE(7, "包容网关分叉节点"), + INCLUSIVE_GATEWAY_JOIN_NODE(8, "包容网关聚合节点"), END_EVENT_NODE(-2, "结束节点"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); @@ -33,9 +35,8 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { private final String name; public static boolean isGatewayNode(Integer type) { - // TODO 后续增加并行网关的支持 - return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type) - || Objects.equals(PARALLEL_GATEWAY_FORK_NODE.getType(), type); + return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK_NODE.getType(), type) + || Objects.equals(INCLUSIVE_GATEWAY_FORK_NODE.getType(), type) ; } public static BpmSimpleModelNodeType valueOf(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index c68908acf..47a6583d3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -386,14 +386,16 @@ public class BpmnModelUtils { case START_EVENT_NODE: case APPROVE_USER_NODE: case SCRIPT_TASK_NODE: - case PARALLEL_GATEWAY_JOIN_NODE:{ + case PARALLEL_GATEWAY_JOIN_NODE: + case INCLUSIVE_GATEWAY_JOIN_NODE:{ addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); // 递归调用后续节点 addBpmnSequenceFlow(mainProcess, childNode, endId); break; } case PARALLEL_GATEWAY_FORK_NODE: - case EXCLUSIVE_GATEWAY_NODE: { + case EXCLUSIVE_GATEWAY_NODE: + case INCLUSIVE_GATEWAY_FORK_NODE:{ String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); @@ -455,6 +457,12 @@ public class BpmnModelUtils { case PARALLEL_GATEWAY_JOIN_NODE: addBpmnParallelGatewayNode(mainProcess, simpleModelNode); break; + case INCLUSIVE_GATEWAY_FORK_NODE: + addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.TRUE); + break; + case INCLUSIVE_GATEWAY_JOIN_NODE: + addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.FALSE); + break; default: { // TODO 其它节点类型的实现 } @@ -507,11 +515,22 @@ public class BpmnModelUtils { Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); - // 条件节点的最后一个条件为 网关的 default sequence flow + // 网关的最后一个条件为 网关的 default sequence flow exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); mainProcess.addFlowElement(exclusiveGateway); } + private static void addBpmnInclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node, Boolean isFork) { + InclusiveGateway inclusiveGateway = new InclusiveGateway(); + inclusiveGateway.setId(node.getId()); + if (isFork) { + Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); + // 网关的最后一个条件为 网关的 default sequence flow + inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); + } + mainProcess.addFlowElement(inclusiveGateway); + } + private static void addBpmnEndEventNode(Process mainProcess) { EndEvent endEvent = new EndEvent(); endEvent.setId(BpmnModelConstants.END_EVENT_ID); From 95dbf4f8aa8a49439f6bb2f32b04036b766b6931 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 8 Apr 2024 22:46:58 +0800 Subject: [PATCH 003/102] =?UTF-8?q?bpm=EF=BC=9Acode=20review=20=E9=92=89?= =?UTF-8?q?=E9=92=89=E6=B5=81=E7=A8=8B=E8=AE=BE=E8=AE=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 17 +++++++++++------ .../flowable/core/util/BpmnModelUtils.java | 5 ++++- .../bpm/service/definition/BpmModelService.java | 2 +- .../definition/BpmSimpleModelServiceImpl.java | 1 + .../task/BpmProcessInstanceCopyServiceImpl.java | 3 ++- .../bpm/service/task/BpmSimpleNodeService.java | 6 ++++-- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index af8ce3d6f..38c0d820d 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -18,14 +18,19 @@ import java.util.Objects; public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; + // TODO @jason:_NODE 都删除掉哈; START_EVENT_NODE(0, "开始节点"), - APPROVE_USER_NODE(1, "审批人节点"), - // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML - SCRIPT_TASK_NODE(2, "抄送人节点"), - EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), - PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), + END_EVENT_NODE(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; + + APPROVE_USER_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件;TODO @jason:改成 USER_TASK 是不是好点呀 + // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML; + // TODO @jason:ServiceTask 自定义 xml,有没啥报错信息; + SCRIPT_TASK_NODE(2, "抄送人节点"), // TODO @jason:是不是改成 COPY_TASK 好一点哈; + + EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), // TODO @jason:是不是改成叫 条件分支? + PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? PARALLEL_GATEWAY_JOIN_NODE(6, "并行网关聚合节点"), - END_EVENT_NODE(-2, "结束节点"); + ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index c68908acf..142fdecf2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -340,7 +340,7 @@ public class BpmnModelUtils { return userTaskList; } - // ========== TODO 芋艿:这里得捉摸下; ========== + // ========== TODO @jason:单独出一个 SimpleModelUtils;定位上,它是 BPMN 的精简模式 ========== /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) @@ -382,6 +382,7 @@ public class BpmnModelUtils { } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); + // TODO @jason:建议是,addXXX 都改成 buildXXX,构建出一个什么;然后返回之后,让这个方法添加到自己的结果里; switch (nodeType) { case START_EVENT_NODE: case APPROVE_USER_NODE: @@ -488,9 +489,11 @@ public class BpmnModelUtils { ScriptTask scriptTask = new ScriptTask(); scriptTask.setId(node.getId()); scriptTask.setName(node.getName()); + // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE); scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); // 添加自定义属性 + // TODO @jason:可以使用 ServiceTask 搞 ExtensionAttribute 么? addExtensionAttributes(node, scriptTask); mainProcess.addFlowElement(scriptTask); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index c5f1c962c..48b7ec4f3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -53,7 +53,7 @@ public interface BpmModelService { * @param id 编号 * @param xmlBytes BPMN XML bytes */ - // TODO @芋艿:可能要关注下; + // TODO @芋艿:感觉可以不修改这个方法,而是额外加一个方法;传入 id,bpmn,json; void saveModelBpmnXml(String id, byte[] xmlBytes); /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index cbdabbf7a..9cd03be3c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -26,6 +26,7 @@ import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTyp import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_PARAM; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY; +// TODO @jason:这块可以讨论下,是不是合并成一个 BpmnModelServiceImpl /** * 仿钉钉流程设计 Service 实现类 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index ce1ddd7fd..506fc8919 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -44,9 +44,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @Lazy // 延迟加载,避免循环依赖 private BpmProcessDefinitionService processDefinitionService; + // TODO @芋艿:这里多加了一个 name; @Override public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { - // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) + // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason:抄送节点,会没有来源的 taskId 么? // Task task = taskService.getTask(taskId); // if (ObjectUtil.isNull(task)) { // throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java index 89ba0ee84..06e1f6342 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Service; import java.util.Set; /** - * 仿钉钉快搭各个节点 Service + * 仿钉钉快搭各个节点 Service TODO @jason:注释要有空行哈; * @author jason */ @Service @@ -21,14 +21,16 @@ public class BpmSimpleNodeService { private BpmProcessInstanceCopyService processInstanceCopyService; /** - * 仿钉钉快搭抄送 + * 仿钉钉快搭抄送 TODO @jason:注释要有空行哈; * @param execution 执行的任务(ScriptTask) */ public Boolean copy(DelegateExecution execution) { + // TODO @芋艿:可能要考虑,系统抄送,没有 taskId 的情况。 Set userIds = taskCandidateInvoker.calculateUsers(execution); FlowElement currentFlowElement = execution.getCurrentFlowElement(); processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), currentFlowElement.getId(), currentFlowElement.getName()); return Boolean.TRUE; } + } From 9456d461f9337c6cc590d0201c31501ca9a4074b Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 11 Apr 2024 21:02:38 +0800 Subject: [PATCH 004/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=82=E6=89=A9=E5=B1=95=E5=B1=9E=E6=80=A7=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=20extensionElement=20=E5=B0=9D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 31 +- .../flowable/core/util/BpmnModelUtils.java | 259 +---------------- .../flowable/core/util/SimpleModelUtils.java | 270 ++++++++++++++++++ .../definition/BpmSimpleModelServiceImpl.java | 19 +- .../BpmProcessInstanceCopyServiceImpl.java | 2 +- .../service/task/BpmSimpleNodeService.java | 6 +- 6 files changed, 316 insertions(+), 271 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index a19fa1847..7daa81926 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -18,20 +18,17 @@ import java.util.Objects; public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; - // TODO @jason:_NODE 都删除掉哈; - START_EVENT_NODE(0, "开始节点"), - END_EVENT_NODE(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; + START_EVENT(0, "开始节点"), + END_EVENT(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; - APPROVE_USER_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件;TODO @jason:改成 USER_TASK 是不是好点呀 - // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML; - // TODO @jason:ServiceTask 自定义 xml,有没啥报错信息; - SCRIPT_TASK_NODE(2, "抄送人节点"), // TODO @jason:是不是改成 COPY_TASK 好一点哈; + USER_TASK(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 + COPY_TASK(2, "抄送人节点"), - EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), // TODO @jason:是不是改成叫 条件分支? - PARALLEL_GATEWAY_FORK_NODE(5, "并行网关分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? - PARALLEL_GATEWAY_JOIN_NODE(6, "并行网关聚合节点"), - INCLUSIVE_GATEWAY_FORK_NODE(7, "包容网关分叉节点"), - INCLUSIVE_GATEWAY_JOIN_NODE(8, "包容网关聚合节点"), + EXCLUSIVE_GATEWAY(4, "排他网关"), // TODO @jason:是不是改成叫 条件分支? + PARALLEL_GATEWAY_FORK(5, "并行网关分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 + PARALLEL_GATEWAY_JOIN(6, "并行网关聚合节点"), + INCLUSIVE_GATEWAY_FORK(7, "包容网关分叉节点"), + INCLUSIVE_GATEWAY_JOIN(8, "包容网关聚合节点"), ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); @@ -39,9 +36,13 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { private final Integer type; private final String name; - public static boolean isGatewayNode(Integer type) { - return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK_NODE.getType(), type) - || Objects.equals(INCLUSIVE_GATEWAY_FORK_NODE.getType(), type) ; + /** + * 判断是否为分支节点 + * @param type 节点类型 + */ + public static boolean isBranchNode(Integer type) { + return Objects.equals(EXCLUSIVE_GATEWAY.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK.getType(), type) + || Objects.equals(INCLUSIVE_GATEWAY_FORK.getType(), type) ; } public static BpmSimpleModelNodeType valueOf(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 812150dc8..c514a97d7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -1,43 +1,41 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; -import org.flowable.common.engine.impl.scripting.ScriptingEngines; import org.flowable.common.engine.impl.util.io.BytesStreamSource; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.flowable.bpmn.constants.BpmnXMLConstants.*; +import java.util.*; /** * 流程模型转操作工具类 */ public class BpmnModelUtils { - public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}"; - public static Integer parseCandidateStrategy(FlowElement userTask) { - return NumberUtils.parseInt(userTask.getAttributeValue( + Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + // @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 + if (candidateStrategy == null) { + ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null)); + + } + return candidateStrategy; } public static String parseCandidateParam(FlowElement userTask) { - return userTask.getAttributeValue( + String candidateParam = userTask.getAttributeValue( BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); + if (candidateParam == null) { + ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null); + } + return candidateParam; } /** @@ -339,231 +337,4 @@ public class BpmnModelUtils { } return userTaskList; } - - // ========== TODO @jason:单独出一个 SimpleModelUtils;定位上,它是 BPMN 的精简模式 ========== - - /** - * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) - * - * @param processId 流程标识 - * @param processName 流程名称 - * @param simpleModelNode 仿钉钉流程设计模型数据结构 - * @return Bpmn Model - */ - public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { - BpmnModel bpmnModel = new BpmnModel(); - Process mainProcess = new Process(); - mainProcess.setId(processId); - mainProcess.setName(processName); - mainProcess.setExecutable(Boolean.TRUE); - bpmnModel.addProcess(mainProcess); - // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。 - // 添加 FlowNode - addBpmnFlowNode(mainProcess, simpleModelNode); - // 单独添加 end event 节点 - addBpmnEndEventNode(mainProcess); - // 添加节点之间的连线 Sequence Flow - addBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID); - // 自动布局 - new BpmnAutoLayout(bpmnModel).execute(); - return bpmnModel; - } - - private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String endId) { - // 节点为 null 退出 - if (node == null || node.getId() == null) { - return; - } - BpmSimpleModelNodeVO childNode = node.getChildNode(); - // 如果不是网关节点、且后续节点为 null. 添加与结束节点的连线 - if (!BpmSimpleModelNodeType.isGatewayNode(node.getType()) && (childNode == null || childNode.getId() == null)) { - addBpmnSequenceFlowElement(mainProcess, node.getId(), endId, null, null); - return; - } - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); - Assert.notNull(nodeType, "模型节点类型不支持"); - // TODO @jason:建议是,addXXX 都改成 buildXXX,构建出一个什么;然后返回之后,让这个方法添加到自己的结果里; - switch (nodeType) { - case START_EVENT_NODE: - case APPROVE_USER_NODE: - case SCRIPT_TASK_NODE: - case PARALLEL_GATEWAY_JOIN_NODE: - case INCLUSIVE_GATEWAY_JOIN_NODE:{ - addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); - // 递归调用后续节点 - addBpmnSequenceFlow(mainProcess, childNode, endId); - break; - } - case PARALLEL_GATEWAY_FORK_NODE: - case EXCLUSIVE_GATEWAY_NODE: - case INCLUSIVE_GATEWAY_FORK_NODE:{ - String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); - List conditionNodes = node.getConditionNodes(); - Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); - for (int i = 0; i < conditionNodes.size(); i++) { - BpmSimpleModelNodeVO item = conditionNodes.get(i); - BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); - if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { - addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(), - String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); - addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId); - } else { - addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId, - String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); - } - } - // 递归调用后续节点 - addBpmnSequenceFlow(mainProcess, childNode, endId); - break; - } - default: { - // TODO 其它节点类型的实现 - } - } - - } - - private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) { - SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); - if (StrUtil.isNotEmpty(conditionExpression)) { - sequenceFlow.setConditionExpression(conditionExpression); - } - if (StrUtil.isNotEmpty(seqFlowId)) { - sequenceFlow.setId(seqFlowId); - } - mainProcess.addFlowElement(sequenceFlow); - } - - private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) { - // 节点为 null 退出 - if (simpleModelNode == null || simpleModelNode.getId() == null) { - return; - } - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); - Assert.notNull(nodeType, "模型节点类型不支持"); - switch (nodeType) { - case START_EVENT_NODE: - addBpmnStartEventNode(mainProcess, simpleModelNode); - break; - case APPROVE_USER_NODE: - addBpmnUserTaskNode(mainProcess, simpleModelNode); - break; - case SCRIPT_TASK_NODE: - addBpmnScriptTaSskNode(mainProcess, simpleModelNode); - break; - case EXCLUSIVE_GATEWAY_NODE: - addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode); - break; - case PARALLEL_GATEWAY_FORK_NODE: - case PARALLEL_GATEWAY_JOIN_NODE: - addBpmnParallelGatewayNode(mainProcess, simpleModelNode); - break; - case INCLUSIVE_GATEWAY_FORK_NODE: - addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.TRUE); - break; - case INCLUSIVE_GATEWAY_JOIN_NODE: - addBpmnInclusiveGatewayNode(mainProcess, simpleModelNode, Boolean.FALSE); - break; - default: { - // TODO 其它节点类型的实现 - } - } - - // 如果不是网关类型的接口, 并且chileNode为空退出 - if (!BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) { - return; - } - - // 如果是网关类型接口. 递归添加条件节点 - if (BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { - for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) { - addBpmnFlowNode(mainProcess, node.getChildNode()); - } - } - - // chileNode不为空,递归添加子节点 - if (simpleModelNode.getChildNode() != null) { - addBpmnFlowNode(mainProcess, simpleModelNode.getChildNode()); - } - } - - private static void addBpmnParallelGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) { - ParallelGateway parallelGateway = new ParallelGateway(); - parallelGateway.setId(node.getId()); - mainProcess.addFlowElement(parallelGateway); - } - - private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) { - ScriptTask scriptTask = new ScriptTask(); - scriptTask.setId(node.getId()); - scriptTask.setName(node.getName()); - // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; - scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE); - scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); - // 添加自定义属性 - // TODO @jason:可以使用 ServiceTask 搞 ExtensionAttribute 么? - addExtensionAttributes(node, scriptTask); - mainProcess.addFlowElement(scriptTask); - } - - private static void addExtensionAttributes(BpmSimpleModelNodeVO node, FlowElement flowElement) { - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); - addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, - candidateStrategy == null ? null : String.valueOf(candidateStrategy)); - addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); - } - - private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) { - Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); - ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); - exclusiveGateway.setId(node.getId()); - // 网关的最后一个条件为 网关的 default sequence flow - exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); - mainProcess.addFlowElement(exclusiveGateway); - } - - private static void addBpmnInclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node, Boolean isFork) { - InclusiveGateway inclusiveGateway = new InclusiveGateway(); - inclusiveGateway.setId(node.getId()); - if (isFork) { - Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); - // 网关的最后一个条件为 网关的 default sequence flow - inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); - } - mainProcess.addFlowElement(inclusiveGateway); - } - - private static void addBpmnEndEventNode(Process mainProcess) { - EndEvent endEvent = new EndEvent(); - endEvent.setId(BpmnModelConstants.END_EVENT_ID); - endEvent.setName("结束"); - mainProcess.addFlowElement(endEvent); - } - - private static void addBpmnUserTaskNode(Process mainProcess, BpmSimpleModelNodeVO node) { - UserTask userTask = new UserTask(); - userTask.setId(node.getId()); - userTask.setName(node.getName()); - addExtensionAttributes(node, userTask); - mainProcess.addFlowElement(userTask); - } - - private static void addExtensionAttributes(FlowElement element, String namespace, String name, String value) { - if (value == null) { - return; - } - ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value); - extensionAttribute.setNamespace(namespace); - extensionAttribute.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); - element.addAttribute(extensionAttribute); - } - - private static void addBpmnStartEventNode(Process mainProcess, BpmSimpleModelNodeVO node) { - StartEvent startEvent = new StartEvent(); - startEvent.setId(node.getId()); - startEvent.setName(node.getName()); - mainProcess.addFlowElement(startEvent); - } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java new file mode 100644 index 000000000..dbf7d6473 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -0,0 +1,270 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import org.flowable.bpmn.BpmnAutoLayout; +import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; + +import java.util.List; + +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; + +/** + * 仿钉钉快搭模型相关的工具方法 + * + * @author jason + */ +public class SimpleModelUtils { + + public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}"; + + /** + * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) + * + * @param processId 流程标识 + * @param processName 流程名称 + * @param simpleModelNode 仿钉钉流程设计模型数据结构 + * @return Bpmn Model + */ + public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { + BpmnModel bpmnModel = new BpmnModel(); + Process mainProcess = new Process(); + mainProcess.setId(processId); + mainProcess.setName(processName); + mainProcess.setExecutable(Boolean.TRUE); + bpmnModel.addProcess(mainProcess); + // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。 + // 从 SimpleModel 构建 FlowNode 并添加到 Main Process + buildAndAddBpmnFlowNode(simpleModelNode, mainProcess); + // 单独构建 end event 节点 + buildAndAddBpmnEndEvent(mainProcess); + // 构建并添加节点之间的连线 Sequence Flow + buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID); + // 自动布局 + new BpmnAutoLayout(bpmnModel).execute(); + return bpmnModel; + } + + private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) { + // 节点为 null 退出 + if (node == null || node.getId() == null) { + return; + } + BpmSimpleModelNodeVO childNode = node.getChildNode(); + // 如果不是网关节点、且后续节点为 null. 添加与结束节点的连线 + if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null); + mainProcess.addFlowElement(sequenceFlow); + return; + } + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + switch (nodeType) { + case START_EVENT: + case USER_TASK: + case COPY_TASK: + case PARALLEL_GATEWAY_JOIN: + case INCLUSIVE_GATEWAY_JOIN:{ + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null); + mainProcess.addFlowElement(sequenceFlow); + // 递归调用后续节点 + buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); + break; + } + case PARALLEL_GATEWAY_FORK: + case EXCLUSIVE_GATEWAY: + case INCLUSIVE_GATEWAY_FORK:{ + String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); + List conditionNodes = node.getConditionNodes(); + Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); + for (int i = 0; i < conditionNodes.size(); i++) { + BpmSimpleModelNodeVO item = conditionNodes.get(i); + BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); + if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), + String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); + mainProcess.addFlowElement(sequenceFlow); + // 递归调用后续节点 + buildAndAddBpmnSequenceFlow(mainProcess, nextNodeOnCondition, sequenceFlowTargetId); + } else { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId, + String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); + mainProcess.addFlowElement(sequenceFlow); + } + } + // 递归调用后续节点 + buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); + break; + } + default: { + // TODO 其它节点类型的实现 + } + } + + } + + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String conditionExpression) { + SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); + if (StrUtil.isNotEmpty(conditionExpression)) { + sequenceFlow.setConditionExpression(conditionExpression); + } + if (StrUtil.isNotEmpty(seqFlowId)) { + sequenceFlow.setId(seqFlowId); + } + return sequenceFlow; + } + + private static void buildAndAddBpmnFlowNode(BpmSimpleModelNodeVO simpleModelNode, Process mainProcess) { + // 节点为 null 退出 + if (simpleModelNode == null || simpleModelNode.getId() == null) { + return; + } + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + switch (nodeType) { + case START_EVENT: { + StartEvent startEvent = buildBpmnStartEvent(simpleModelNode); + mainProcess.addFlowElement(startEvent); + break; + } + case USER_TASK: { + UserTask userTask = buildBpmnUserTask(simpleModelNode); + mainProcess.addFlowElement(userTask); + break; + } + case COPY_TASK: { + ServiceTask serviceTask = buildBpmnServiceTask(simpleModelNode); + mainProcess.addFlowElement(serviceTask); + break; + } + case EXCLUSIVE_GATEWAY: { + ExclusiveGateway exclusiveGateway = buildBpmnExclusiveGateway(simpleModelNode); + mainProcess.addFlowElement(exclusiveGateway); + break; + } + case PARALLEL_GATEWAY_FORK: + case PARALLEL_GATEWAY_JOIN: { + ParallelGateway parallelGateway = buildBpmnParallelGateway(simpleModelNode); + mainProcess.addFlowElement(parallelGateway); + break; + } + case INCLUSIVE_GATEWAY_FORK: { + InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.TRUE); + mainProcess.addFlowElement(inclusiveGateway); + break; + } + case INCLUSIVE_GATEWAY_JOIN: { + InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.FALSE); + mainProcess.addFlowElement(inclusiveGateway); + break; + } + default: { + // TODO 其它节点类型的实现 + } + } + + // 如果不是网关类型的接口, 并且chileNode为空退出 + if (!BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) { + return; + } + + // 如果是网关类型接口. 递归添加条件节点 + if (BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { + for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) { + buildAndAddBpmnFlowNode(node.getChildNode(), mainProcess); + } + } + + // chileNode不为空,递归添加子节点 + if (simpleModelNode.getChildNode() != null) { + buildAndAddBpmnFlowNode(simpleModelNode.getChildNode(), mainProcess); + } + } + + private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { + ParallelGateway parallelGateway = new ParallelGateway(); + parallelGateway.setId(node.getId()); + return parallelGateway; + } + + private static ServiceTask buildBpmnServiceTask(BpmSimpleModelNodeVO node) { + ServiceTask serviceTask = new ServiceTask(); + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION); + serviceTask.setImplementation(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); + serviceTask.setId(node.getId()); + serviceTask.setName(node.getName()); + // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; + // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners + addExtensionElement(node, serviceTask); + return serviceTask; + } + + private static void addExtensionElement(BpmSimpleModelNodeVO node, FlowElement flowElement) { + Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); + addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, + candidateStrategy == null ? null : String.valueOf(candidateStrategy)); + addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + } + + private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { + Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); + ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); + exclusiveGateway.setId(node.getId()); + // 网关的最后一个条件为 网关的 default sequence flow + exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); + return exclusiveGateway; + } + + private static InclusiveGateway buildBpmnInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) { + InclusiveGateway inclusiveGateway = new InclusiveGateway(); + inclusiveGateway.setId(node.getId()); + if (isFork) { + Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); + // 网关的最后一个条件为 网关的 default sequence flow + inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); + } + return inclusiveGateway; + } + + private static void buildAndAddBpmnEndEvent(Process mainProcess) { + EndEvent endEvent = new EndEvent(); + endEvent.setId(BpmnModelConstants.END_EVENT_ID); + endEvent.setName("结束"); + mainProcess.addFlowElement(endEvent); + } + + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { + UserTask userTask = new UserTask(); + userTask.setId(node.getId()); + userTask.setName(node.getName()); + addExtensionElement(node, userTask); + return userTask; + } + + private static void addExtensionElement(FlowElement element, String namespace, String name, String value) { + if (value == null) { + return; + } + ExtensionElement extensionElement = new ExtensionElement(); + extensionElement.setNamespace(namespace); + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); + extensionElement.setElementText(value); + extensionElement.setName(name); + element.addExtensionElement(extensionElement); + } + + private static StartEvent buildBpmnStartEvent(BpmSimpleModelNodeVO node) { + StartEvent startEvent = new StartEvent(); + startEvent.setId(node.getId()); + startEvent.setName(node.getName()); + return startEvent; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index 9cd03be3c..53af051dc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimp import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import jakarta.annotation.Resource; import org.flowable.bpmn.model.*; import org.flowable.engine.repository.Model; @@ -22,7 +23,7 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT_NODE; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_PARAM; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY; @@ -56,7 +57,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { // return Boolean.FALSE; // } // 1. JSON 转换成 bpmnModel - BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); + BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); // 2.1 保存 Bpmn XML bpmModelService.saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); // 2.2 保存 JSON 数据 @@ -93,7 +94,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { return null; } BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO(); - rootNode.setType(START_EVENT_NODE.getType()); + rootNode.setType(START_EVENT.getType()); rootNode.setId(startEvent.getId()); rootNode.setName(startEvent.getName()); recursiveBuildSimpleModelNode(startEvent, rootNode); @@ -105,10 +106,10 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { Assert.notNull(nodeType, "节点类型不支持"); // 校验节点是否支持转仿钉钉的流程模型 List outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode); - if (CollUtil.isEmpty(outgoingFlows) || outgoingFlows.get(0).getTargetFlowElement() == null) { + if (CollUtil.isEmpty(outgoingFlows) || CollUtil.getFirst(outgoingFlows).getTargetFlowElement() == null) { return; } - FlowElement targetElement = outgoingFlows.get(0).getTargetFlowElement(); + FlowElement targetElement = CollUtil.getFirst(outgoingFlows).getTargetFlowElement(); // 如果是 EndEvent 直接退出 if (targetElement instanceof EndEvent) { return; @@ -123,7 +124,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) { BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO(); - simpleModelNodeVO.setType(BpmSimpleModelNodeType.APPROVE_USER_NODE.getType()); + simpleModelNodeVO.setType(BpmSimpleModelNodeType.USER_TASK.getType()); simpleModelNodeVO.setName(userTask.getName()); simpleModelNodeVO.setId(userTask.getId()); Map attributes = MapUtil.newHashMap(); @@ -137,13 +138,13 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { private List validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) { switch (nodeType) { - case START_EVENT_NODE: - case APPROVE_USER_NODE: { + case START_EVENT: + case USER_TASK: { List outgoingFlows = currentFlowNode.getOutgoingFlows(); if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) { throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); } - validIsSupportFlowNode(outgoingFlows.get(0).getTargetFlowElement()); + validIsSupportFlowNode(CollUtil.getFirst(outgoingFlows).getTargetFlowElement()); return outgoingFlows; } default: { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index 506fc8919..e4d66f8c4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -47,7 +47,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy // TODO @芋艿:这里多加了一个 name; @Override public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { - // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason:抄送节点,会没有来源的 taskId 么? + // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行 // Task task = taskService.getTask(taskId); // if (ObjectUtil.isNull(task)) { // throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java index 06e1f6342..b0233e03e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java @@ -9,7 +9,8 @@ import org.springframework.stereotype.Service; import java.util.Set; /** - * 仿钉钉快搭各个节点 Service TODO @jason:注释要有空行哈; + * 仿钉钉快搭各个节点 Service + * * @author jason */ @Service @@ -21,7 +22,8 @@ public class BpmSimpleNodeService { private BpmProcessInstanceCopyService processInstanceCopyService; /** - * 仿钉钉快搭抄送 TODO @jason:注释要有空行哈; + * 仿钉钉快搭抄送 + * * @param execution 执行的任务(ScriptTask) */ public Boolean copy(DelegateExecution execution) { From 1e30e4851a3841606292bf572c6a866bffae8c04 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 14 Apr 2024 10:07:55 +0800 Subject: [PATCH 005/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=B5=81=E7=A8=8B=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E5=AD=97=E6=AE=B5=E6=9D=83=E9=99=90=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmFieldPermissionEnum.java | 26 +++++++ .../admin/task/BpmTaskController.java | 8 ++- .../bpm/convert/task/BpmTaskConvert.java | 8 ++- .../core/enums/SimpleModelConstants.java | 24 +++++++ .../flowable/core/util/BpmnModelUtils.java | 23 +++++++ .../flowable/core/util/SimpleModelUtils.java | 61 ++++++++++++++--- .../bpm/service/util/BpmnFormUtils.java | 68 +++++++++++++++++++ 7 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java new file mode 100644 index 000000000..18cc5e4ca --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 表单权限的枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmFieldPermissionEnum { + + EDITABLE(1, "可编辑"), + READONLY(2, "只读"), + HIDE(3, "隐藏"); + + private final Integer permission; + private final String name; + + public static BpmFieldPermissionEnum valueOf(Integer permission) { + return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java index 7d72a133b..1491e5fdd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -19,6 +20,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; @@ -50,6 +52,8 @@ public class BpmTaskController { private BpmProcessInstanceService processInstanceService; @Resource private BpmFormService formService; + @Resource + private BpmProcessDefinitionService bpmProcessDefinitionService; @Resource private AdminUserApi adminUserApi; @@ -134,8 +138,10 @@ public class BpmTaskController { // 获得 Form Map Map formMap = formService.getFormMap( convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); + // 获得 BpmnModel + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, - formMap, userMap, deptMap)); + formMap, userMap, deptMap,bpmnModel)); } @PutMapping("/approve") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 5f4e915d3..25fa107b0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; @@ -81,7 +82,8 @@ public interface BpmTaskConvert { HistoricProcessInstance processInstance, Map formMap, Map userMap, - Map deptMap) { + Map deptMap, + BpmnModel bpmnModel) { List taskVOList = CollectionUtils.convertList(taskList, task -> { BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); @@ -92,6 +94,10 @@ public interface BpmTaskConvert { // 表单信息 BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); if (form != null) { + // 测试一下权限处理 +// List afterChangedFields = BpmnFormUtils.changeCreateFormFiledPermissionRule(form.getFields(), +// BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); + taskVO.setFormId(form.getId()).setFormName(form.getName()).setFormConf(form.getConf()) .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java new file mode 100644 index 000000000..bada4ecb8 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; + +/** + * 仿钉钉快搭 JSON 常量信息 + * + * @author jason + */ +public interface SimpleModelConstants { + + /** + * 流程表单字段权限, 用于标记字段权限 + */ + String FIELDS_PERMISSION = "fieldsPermission"; + + /** + * 字段属性 + */ + String FIELD_ATTRIBUTE = "field"; + + /** + * 权限属性 + */ + String PERMISSION_ATTRIBUTE = "permission"; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index c514a97d7..56edb8c3d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -11,6 +14,9 @@ import org.flowable.common.engine.impl.util.io.BytesStreamSource; import java.util.*; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELD_ATTRIBUTE; +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; + /** * 流程模型转操作工具类 */ @@ -38,6 +44,23 @@ public class BpmnModelUtils { return candidateParam; } + public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return null; + } + final HashMap fieldsPermission = MapUtil.newHashMap(); + List extensionElements = flowElement.getExtensionElements().get(SimpleModelConstants.FIELDS_PERMISSION); + extensionElements.forEach(el -> { + String field = el.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FIELD_ATTRIBUTE); + String permission = el.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, SimpleModelConstants.PERMISSION_ATTRIBUTE); + if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { + fieldsPermission.put(field, Integer.parseInt(permission)); + } + }); + return fieldsPermission; + } + /** * 根据节点,获取入口连线 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index dbf7d6473..c4d39709e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -8,11 +10,13 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimp import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import org.flowable.bpmn.BpmnAutoLayout; -import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; import java.util.List; +import java.util.Map; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELDS_PERMISSION; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; @@ -71,7 +75,7 @@ public class SimpleModelUtils { case USER_TASK: case COPY_TASK: case PARALLEL_GATEWAY_JOIN: - case INCLUSIVE_GATEWAY_JOIN:{ + case INCLUSIVE_GATEWAY_JOIN: { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null); mainProcess.addFlowElement(sequenceFlow); // 递归调用后续节点 @@ -80,7 +84,7 @@ public class SimpleModelUtils { } case PARALLEL_GATEWAY_FORK: case EXCLUSIVE_GATEWAY: - case INCLUSIVE_GATEWAY_FORK:{ + case INCLUSIVE_GATEWAY_FORK: { String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); @@ -202,15 +206,20 @@ public class SimpleModelUtils { serviceTask.setName(node.getName()); // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners - addExtensionElement(node, serviceTask); + addCandidateElements(node, serviceTask); + return serviceTask; } - private static void addExtensionElement(BpmSimpleModelNodeVO node, FlowElement flowElement) { + + /** + * 给节点添加候选人元素 + */ + private static void addCandidateElements(BpmSimpleModelNodeVO node, FlowElement flowElement) { Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); - addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, candidateStrategy == null ? null : String.valueOf(candidateStrategy)); - addExtensionElement(flowElement, FLOWABLE_EXTENSIONS_NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); } @@ -245,16 +254,48 @@ public class SimpleModelUtils { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); - addExtensionElement(node, userTask); + // TODO 暂时测试,后面去掉 + userTask.setFormKey("24"); + // 添加候选人元素 + addCandidateElements(node, userTask); + // 添加表单字段权限属性元素 + addFormFieldsPermission(node, userTask); return userTask; } - private static void addExtensionElement(FlowElement element, String namespace, String name, String value) { + /** + * 给节点添加表单字段权限元素 + */ + private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) { + List> fieldsPermissions = MapUtil.get(node.getAttributes(), + FIELDS_PERMISSION, new TypeReference<>() {}); + if (CollUtil.isNotEmpty(fieldsPermissions)) { + fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FIELDS_PERMISSION, item)); + } + } + + private static void addExtensionElement(FlowElement element, String name, Map attributes) { + if (attributes == null) { + return; + } + ExtensionElement extensionElement = new ExtensionElement(); + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); + extensionElement.setName(name); + attributes.forEach((key, value) -> { + ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value); + extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); + extensionElement.addAttribute(extensionAttribute); + }); + element.addExtensionElement(extensionElement); + } + + private static void addExtensionElement(FlowElement element, String name, String value) { if (value == null) { return; } ExtensionElement extensionElement = new ExtensionElement(); - extensionElement.setNamespace(namespace); + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); extensionElement.setElementText(value); extensionElement.setName(name); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java new file mode 100644 index 000000000..c22385ea8 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.bpm.service.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmFieldPermissionEnum; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELD_ATTRIBUTE; + +/** + * Bpmn 流程表单相关工具方法 + * + * @author jason + */ +public class BpmnFormUtils { + private static final String CREATE_FORM_DISPLAY_ATTRIBUTE = "display"; + private static final String CREATE_FORM_DISABLED_ATTRIBUTE = "disabled"; + + /** + * 修改 form-create 表单组件字段权限规则: 包括可编辑、只读、隐藏规则 + * @param fields 字段规则 + * @param fieldsPermission 字段权限 + * @return 修改权限后的字段规则 + */ + public static List changeCreateFormFiledPermissionRule(List fields, Map fieldsPermission) { + if ( CollUtil.isEmpty(fields) || MapUtil.isEmpty(fieldsPermission)) { + return fields; + } + List afterChangedFields = new ArrayList<>(fields.size()); + fields.forEach( f-> { + Map fieldMap = JsonUtils.parseObject(f, new TypeReference<>() {}); + String field = ObjUtil.defaultIfNull(fieldMap.get(FIELD_ATTRIBUTE), Object::toString, ""); + if (StrUtil.isEmpty(field) || !fieldsPermission.containsKey(field)) { + afterChangedFields.add(f); + return; + } + BpmFieldPermissionEnum fieldPermission = BpmFieldPermissionEnum.valueOf(fieldsPermission.get(field)); + Assert.notNull(fieldPermission, "字段权限不匹配"); + if (BpmFieldPermissionEnum.HIDE == fieldPermission) { + fieldMap.put(CREATE_FORM_DISPLAY_ATTRIBUTE, Boolean.FALSE); + } else if (BpmFieldPermissionEnum.EDITABLE == fieldPermission){ + Map props = MapUtil.get(fieldMap, "props", new cn.hutool.core.lang.TypeReference<>() {}); + if (props == null) { + props = MapUtil.newHashMap(); + fieldMap.put("props", props); + } + props.put(CREATE_FORM_DISABLED_ATTRIBUTE, Boolean.FALSE); + } else if (BpmFieldPermissionEnum.READONLY == fieldPermission) { + Map props = MapUtil.get(fieldMap, "props", new cn.hutool.core.lang.TypeReference<>() {}); + if (props == null) { + props = MapUtil.newHashMap(); + fieldMap.put("props", props); + } + props.put(CREATE_FORM_DISABLED_ATTRIBUTE, Boolean.TRUE); + } + afterChangedFields.add(JsonUtils.toJsonString(fieldMap)); + }); + return afterChangedFields; + } +} From 5d390d2d65fb2213c97f333fb59837874cd62bcf Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 18 Apr 2024 12:38:38 +0800 Subject: [PATCH 006/102] =?UTF-8?q?bpm=EF=BC=9Acode=20review=20=E5=BF=AB?= =?UTF-8?q?=E6=90=AD=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/definition/BpmFieldPermissionEnum.java | 8 ++++++++ .../enums/definition/BpmSimpleModelNodeType.java | 1 + .../controller/admin/task/BpmTaskController.java | 2 +- .../module/bpm/convert/task/BpmTaskConvert.java | 1 + .../core/enums/SimpleModelConstants.java | 7 +++++-- .../flowable/core/util/BpmnModelUtils.java | 16 +++++++++------- .../flowable/core/util/SimpleModelUtils.java | 1 - .../module/bpm/service/util/BpmnFormUtils.java | 3 +++ 8 files changed, 28 insertions(+), 11 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java index 18cc5e4ca..d5410e218 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -13,14 +13,22 @@ import lombok.Getter; @AllArgsConstructor public enum BpmFieldPermissionEnum { + // TODO @jason:改成 WRITE、READ、NONE,更符合权限的感觉哈 EDITABLE(1, "可编辑"), READONLY(2, "只读"), HIDE(3, "隐藏"); + /** + * 权限 + */ private final Integer permission; + /** + * 名字 + */ private final String name; public static BpmFieldPermissionEnum valueOf(Integer permission) { return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values()); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 7daa81926..12cda1d3d 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -38,6 +38,7 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { /** * 判断是否为分支节点 + * * @param type 节点类型 */ public static boolean isBranchNode(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java index 1491e5fdd..f5e6be863 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java @@ -141,7 +141,7 @@ public class BpmTaskController { // 获得 BpmnModel BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, - formMap, userMap, deptMap,bpmnModel)); + formMap, userMap, deptMap, bpmnModel)); } @PutMapping("/approve") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 25fa107b0..dbacd0397 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -95,6 +95,7 @@ public interface BpmTaskConvert { BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); if (form != null) { // 测试一下权限处理 + // TODO @jason:这里是不是还没实现完哈? // List afterChangedFields = BpmnFormUtils.changeCreateFormFiledPermissionRule(form.getFields(), // BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index bada4ecb8..dc09a831c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; +// TODO @jason:这个类,挪到 BpmnModelConstants 里,会不会好点,因为后续 BPMN 标准也需要使用这些字段哈; /** * 仿钉钉快搭 JSON 常量信息 * @@ -7,18 +8,20 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; */ public interface SimpleModelConstants { + // TODO @jason:改成 FORM_FIELD_PERMISSION_ELEMENT 会不会更精准哈; /** * 流程表单字段权限, 用于标记字段权限 */ String FIELDS_PERMISSION = "fieldsPermission"; - + // TODO @jason:改成 FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE 会不会更精准哈; /** * 字段属性 */ String FIELD_ATTRIBUTE = "field"; - + // TODO @jason:改成 FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE 会不会更精准哈; /** * 权限属性 */ String PERMISSION_ATTRIBUTE = "permission"; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 56edb8c3d..1f2e084f8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -25,11 +25,12 @@ public class BpmnModelUtils { public static Integer parseCandidateStrategy(FlowElement userTask) { Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); - // @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 + // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 if (candidateStrategy == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + // TODO @jason:改成下面这样,是不是看着更简洁哈 +// candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null; candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null)); - } return candidateStrategy; } @@ -44,16 +45,17 @@ public class BpmnModelUtils { return candidateParam; } - public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { + // TODO @jason:貌似这个没地方调用??? + public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); if (flowElement == null) { return null; } - final HashMap fieldsPermission = MapUtil.newHashMap(); + Map fieldsPermission = MapUtil.newHashMap(); List extensionElements = flowElement.getExtensionElements().get(SimpleModelConstants.FIELDS_PERMISSION); - extensionElements.forEach(el -> { - String field = el.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FIELD_ATTRIBUTE); - String permission = el.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, SimpleModelConstants.PERMISSION_ATTRIBUTE); + extensionElements.forEach(element -> { + String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FIELD_ATTRIBUTE); + String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, SimpleModelConstants.PERMISSION_ATTRIBUTE); if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { fieldsPermission.put(field, Integer.parseInt(permission)); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index c4d39709e..d9208697d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -207,7 +207,6 @@ public class SimpleModelUtils { // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners addCandidateElements(node, serviceTask); - return serviceTask; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java index c22385ea8..2fc59e522 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java @@ -15,12 +15,14 @@ import java.util.Map; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELD_ATTRIBUTE; +// TODO @jason:这个类,挪到 framework 那的 util 包下哈; /** * Bpmn 流程表单相关工具方法 * * @author jason */ public class BpmnFormUtils { + private static final String CREATE_FORM_DISPLAY_ATTRIBUTE = "display"; private static final String CREATE_FORM_DISABLED_ATTRIBUTE = "disabled"; @@ -65,4 +67,5 @@ public class BpmnFormUtils { }); return afterChangedFields; } + } From cb5cfd31f0136782012f376ad75157fb22ab3c2f Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 18 Apr 2024 20:47:02 +0800 Subject: [PATCH 007/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmFieldPermissionEnum.java | 7 +++---- .../module/bpm/convert/task/BpmTaskConvert.java | 1 + .../flowable/core/enums/BpmnModelConstants.java | 15 +++++++++++++++ .../core/enums/SimpleModelConstants.java | 17 ----------------- .../flowable/core}/util/BpmnFormUtils.java | 14 +++++++------- .../flowable/core/util/BpmnModelUtils.java | 15 ++++++--------- .../flowable/core/util/SimpleModelUtils.java | 6 +++--- 7 files changed, 35 insertions(+), 40 deletions(-) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/{service => framework/flowable/core}/util/BpmnFormUtils.java (84%) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java index d5410e218..a71f1f51b 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -13,10 +13,9 @@ import lombok.Getter; @AllArgsConstructor public enum BpmFieldPermissionEnum { - // TODO @jason:改成 WRITE、READ、NONE,更符合权限的感觉哈 - EDITABLE(1, "可编辑"), - READONLY(2, "只读"), - HIDE(3, "隐藏"); + WRITE(1, "可编辑"), + READ(2, "只读"), + NONE(3, "隐藏"); /** * 权限 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index dbacd0397..2a04d712d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -96,6 +96,7 @@ public interface BpmTaskConvert { if (form != null) { // 测试一下权限处理 // TODO @jason:这里是不是还没实现完哈? + // TODO @芋艿 测试了一下。 暂时注释掉。 前端不知道要怎样改, 可能需要讨论一下如何改 // List afterChangedFields = BpmnFormUtils.changeCreateFormFiledPermissionRule(form.getFields(), // BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 7513a64c8..1138fc5a0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -28,6 +28,21 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + /** + * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 + */ + String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; + + /** + * BPMN ExtensionElement Attribute, 用于标记表单字段 + */ + String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field"; + + /** + * BPMN ExtensionElement Attribute, 用于标记表单权限 + */ + String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; + // TODO @芋艿:这里后面得关注下; /** * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index dc09a831c..d02f9a4f1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; -// TODO @jason:这个类,挪到 BpmnModelConstants 里,会不会好点,因为后续 BPMN 标准也需要使用这些字段哈; /** * 仿钉钉快搭 JSON 常量信息 * @@ -8,20 +7,4 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; */ public interface SimpleModelConstants { - // TODO @jason:改成 FORM_FIELD_PERMISSION_ELEMENT 会不会更精准哈; - /** - * 流程表单字段权限, 用于标记字段权限 - */ - String FIELDS_PERMISSION = "fieldsPermission"; - // TODO @jason:改成 FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE 会不会更精准哈; - /** - * 字段属性 - */ - String FIELD_ATTRIBUTE = "field"; - // TODO @jason:改成 FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE 会不会更精准哈; - /** - * 权限属性 - */ - String PERMISSION_ATTRIBUTE = "permission"; - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java similarity index 84% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java index 2fc59e522..fb8be1ef4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/util/BpmnFormUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.service.util; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; @@ -13,9 +13,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELD_ATTRIBUTE; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE; + -// TODO @jason:这个类,挪到 framework 那的 util 包下哈; /** * Bpmn 流程表单相关工具方法 * @@ -39,23 +39,23 @@ public class BpmnFormUtils { List afterChangedFields = new ArrayList<>(fields.size()); fields.forEach( f-> { Map fieldMap = JsonUtils.parseObject(f, new TypeReference<>() {}); - String field = ObjUtil.defaultIfNull(fieldMap.get(FIELD_ATTRIBUTE), Object::toString, ""); + String field = ObjUtil.defaultIfNull(fieldMap.get(FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE), Object::toString, ""); if (StrUtil.isEmpty(field) || !fieldsPermission.containsKey(field)) { afterChangedFields.add(f); return; } BpmFieldPermissionEnum fieldPermission = BpmFieldPermissionEnum.valueOf(fieldsPermission.get(field)); Assert.notNull(fieldPermission, "字段权限不匹配"); - if (BpmFieldPermissionEnum.HIDE == fieldPermission) { + if (BpmFieldPermissionEnum.NONE == fieldPermission) { fieldMap.put(CREATE_FORM_DISPLAY_ATTRIBUTE, Boolean.FALSE); - } else if (BpmFieldPermissionEnum.EDITABLE == fieldPermission){ + } else if (BpmFieldPermissionEnum.WRITE == fieldPermission){ Map props = MapUtil.get(fieldMap, "props", new cn.hutool.core.lang.TypeReference<>() {}); if (props == null) { props = MapUtil.newHashMap(); fieldMap.put("props", props); } props.put(CREATE_FORM_DISABLED_ATTRIBUTE, Boolean.FALSE); - } else if (BpmFieldPermissionEnum.READONLY == fieldPermission) { + } else if (BpmFieldPermissionEnum.READ == fieldPermission) { Map props = MapUtil.get(fieldMap, "props", new cn.hutool.core.lang.TypeReference<>() {}); if (props == null) { props = MapUtil.newHashMap(); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 1f2e084f8..9e9af0e65 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -6,7 +6,6 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -14,7 +13,7 @@ import org.flowable.common.engine.impl.util.io.BytesStreamSource; import java.util.*; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELD_ATTRIBUTE; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; /** @@ -28,15 +27,13 @@ public class BpmnModelUtils { // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 if (candidateStrategy == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); - // TODO @jason:改成下面这样,是不是看着更简洁哈 -// candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null; candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null)); } return candidateStrategy; } public static String parseCandidateParam(FlowElement userTask) { - String candidateParam = userTask.getAttributeValue( + String candidateParam = userTask.getAttributeValue( BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); if (candidateParam == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); @@ -45,17 +42,17 @@ public class BpmnModelUtils { return candidateParam; } - // TODO @jason:貌似这个没地方调用??? + // TODO @jason:貌似这个没地方调用??? @芋艿 在 BpmTaskConvert里面。暂时注释掉了。 public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); if (flowElement == null) { return null; } Map fieldsPermission = MapUtil.newHashMap(); - List extensionElements = flowElement.getExtensionElements().get(SimpleModelConstants.FIELDS_PERMISSION); + List extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); extensionElements.forEach(element -> { - String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FIELD_ATTRIBUTE); - String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, SimpleModelConstants.PERMISSION_ATTRIBUTE); + String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); + String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { fieldsPermission.put(field, Integer.parseInt(permission)); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index d9208697d..26b709120 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -16,7 +16,7 @@ import org.flowable.bpmn.model.*; import java.util.List; import java.util.Map; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.FIELDS_PERMISSION; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; @@ -267,9 +267,9 @@ public class SimpleModelUtils { */ private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) { List> fieldsPermissions = MapUtil.get(node.getAttributes(), - FIELDS_PERMISSION, new TypeReference<>() {}); + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {}); if (CollUtil.isNotEmpty(fieldsPermissions)) { - fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FIELDS_PERMISSION, item)); + fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); } } From e4fbc11dc453e86097270a2131bd1314186f3cb0 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 27 Apr 2024 09:30:14 +0800 Subject: [PATCH 008/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=89=8D=E7=AB=AF=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=B0=83=E6=95=B4,=20=E6=96=B0=E5=A2=9E=E5=A4=9A?= =?UTF-8?q?=E4=BA=BA=E5=AE=A1=E6=89=B9=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmApproveMethodEnum.java | 33 +++++++++ .../vo/simple/BpmSimpleModelNodeVO.java | 3 + .../core/enums/SimpleModelConstants.java | 4 + .../flowable/core/util/SimpleModelUtils.java | 73 +++++++++++++++++-- 4 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java new file mode 100644 index 000000000..d9ba36449 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 审批方式的枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmApproveMethodEnum { + + SINGLE_PERSON_APPROVE(1, "单人审批"), + ALL_APPROVE(2, "多人会签(需所有审批人同意)"), + ANY_OF_APPROVE(3, "多人或签(一名审批人同意即可)"), + SEQUENTIAL_APPROVE(4, "依次审批"); + + /** + * 审批方式 + */ + private final Integer method; + /** + * 名字 + */ + private final String name; + + public static BpmApproveMethodEnum valueOf(Integer method) { + return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java index 09db4764c..4a9e653e5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java @@ -28,6 +28,9 @@ public class BpmSimpleModelNodeVO { @Schema(description = "模型节点名称", example = "领导审批") private String name; + @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") + private String showText; + @Schema(description = "孩子节点") private BpmSimpleModelNodeVO childNode; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index d02f9a4f1..6892bed14 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -7,4 +7,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; */ public interface SimpleModelConstants { + /** + * 审批方式属性 + */ + String APPROVE_METHOD_ATTRIBUTE = "approveMethod"; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 26b709120..2e44d5286 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -7,8 +7,10 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -16,6 +18,7 @@ import org.flowable.bpmn.model.*; import java.util.List; import java.util.Map; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; @@ -29,6 +32,16 @@ public class SimpleModelUtils { public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}"; + /** + * 所有审批人同意的表达式 + */ + public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= 0 }"; + + /** + * 任一一名审批人同意的表达式 + */ + public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }"; + /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) * @@ -47,10 +60,15 @@ public class SimpleModelUtils { // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。 // 从 SimpleModel 构建 FlowNode 并添加到 Main Process buildAndAddBpmnFlowNode(simpleModelNode, mainProcess); - // 单独构建 end event 节点 - buildAndAddBpmnEndEvent(mainProcess); + // 找到 end event + EndEvent endEvent = (EndEvent) CollUtil.findOne(mainProcess.getFlowElements(), item -> item instanceof EndEvent); + if (endEvent == null) { + // 暂时为了兼容 单独构建 end event 节点 + endEvent = buildAndAddBpmnEndEvent(mainProcess); + } + // 构建并添加节点之间的连线 Sequence Flow - buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID); + buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, endEvent.getId()); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); return bpmnModel; @@ -58,7 +76,7 @@ public class SimpleModelUtils { private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) { // 节点为 null 退出 - if (node == null || node.getId() == null) { + if (node == null || node.getId() == null || END_EVENT.getType().equals(node.getType())) { return; } BpmSimpleModelNodeVO childNode = node.getChildNode(); @@ -169,6 +187,11 @@ public class SimpleModelUtils { mainProcess.addFlowElement(inclusiveGateway); break; } + case END_EVENT: { + EndEvent endEvent = buildBpmnEndEvent(simpleModelNode); + mainProcess.addFlowElement(endEvent); + break; + } default: { // TODO 其它节点类型的实现 } @@ -242,32 +265,59 @@ public class SimpleModelUtils { return inclusiveGateway; } - private static void buildAndAddBpmnEndEvent(Process mainProcess) { + private static EndEvent buildAndAddBpmnEndEvent(Process mainProcess) { EndEvent endEvent = new EndEvent(); endEvent.setId(BpmnModelConstants.END_EVENT_ID); endEvent.setName("结束"); mainProcess.addFlowElement(endEvent); + return endEvent; } private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); - // TODO 暂时测试,后面去掉 - userTask.setFormKey("24"); // 添加候选人元素 addCandidateElements(node, userTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node, userTask); + // 处理多实例 + processMultiInstanceLoopCharacteristics(node, userTask); return userTask; } + private static void processMultiInstanceLoopCharacteristics(BpmSimpleModelNodeVO node, UserTask userTask) { + Integer approveMethod = MapUtil.getInt(node.getAttributes(), SimpleModelConstants.APPROVE_METHOD_ATTRIBUTE); + BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); + if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { + return; + } + MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); + // 设置 collectionVariable。本系统用不到。会在 仅仅为了校验。 + multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); + if (bpmApproveMethodEnum == BpmApproveMethodEnum.ALL_APPROVE) { + multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); + multiInstanceCharacteristics.setSequential(false); + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_OF_APPROVE) { + multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); + multiInstanceCharacteristics.setSequential(false); + userTask.setLoopCharacteristics(multiInstanceCharacteristics); + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.SEQUENTIAL_APPROVE) { + multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); + multiInstanceCharacteristics.setSequential(true); + multiInstanceCharacteristics.setLoopCardinality("1"); + userTask.setLoopCharacteristics(multiInstanceCharacteristics); + } + userTask.setLoopCharacteristics(multiInstanceCharacteristics); + } + /** * 给节点添加表单字段权限元素 */ private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) { List> fieldsPermissions = MapUtil.get(node.getAttributes(), - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {}); + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { + }); if (CollUtil.isNotEmpty(fieldsPermissions)) { fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); } @@ -307,4 +357,11 @@ public class SimpleModelUtils { startEvent.setName(node.getName()); return startEvent; } + + private static EndEvent buildBpmnEndEvent(BpmSimpleModelNodeVO node) { + EndEvent endEvent = new EndEvent(); + endEvent.setId(node.getId()); + endEvent.setName(node.getName()); + return endEvent; + } } From 1bd96eb13ef3a30298163cd951037de27cbe5242 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 28 Apr 2024 19:53:21 +0800 Subject: [PATCH 009/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20=E5=BF=AB=E6=90=AD?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java | 1 + .../bpm/framework/flowable/core/enums/SimpleModelConstants.java | 1 + 2 files changed, 2 insertions(+) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index d9ba36449..004c77468 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil; import lombok.AllArgsConstructor; import lombok.Getter; +// TODO @芋艿:审批方式的名字,可能要看下; /** * BPM 审批方式的枚举 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index 6892bed14..dd7e6a520 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -7,6 +7,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; */ public interface SimpleModelConstants { + // TODO @芋艿:审批方式的名字,可能要看下; /** * 审批方式属性 */ From 4958aaea8151137caa143fa10e8fb202604b00fb Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 30 Apr 2024 00:07:58 +0800 Subject: [PATCH 010/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=B0=83=E6=95=B4=E6=8E=92?= =?UTF-8?q?=E4=BB=96=E7=BD=91=E5=85=B3=E5=92=8C=E6=9D=A1=E4=BB=B6=E8=8A=82?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmSimpleModeConditionType.java | 25 +++++++++ .../core/enums/SimpleModelConstants.java | 15 +++++ .../flowable/core/util/SimpleModelUtils.java | 56 ++++++++++++++----- 3 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java new file mode 100644 index 000000000..042119346 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 仿钉钉的流程器设计器条件节点的条件类型 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmSimpleModeConditionType { + + CUSTOM_EXPRESSION(1, "自定义条件表达式"); + + + private final Integer type; + private final String name; + + public static BpmSimpleModeConditionType valueOf(Integer type) { + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index dd7e6a520..32dae5a8f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -12,4 +12,19 @@ public interface SimpleModelConstants { * 审批方式属性 */ String APPROVE_METHOD_ATTRIBUTE = "approveMethod"; + + /** + * 网关节点默认序列流属性 + */ + String DEFAULT_FLOW_ATTRIBUTE = "defaultFlow"; + + /** + * 条件节点的条件类型属性 + */ + String CONDITION_TYPE_ATTRIBUTE = "conditionType"; + + /** + * 条件节点条件表达式属性 + */ + String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression"; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 2e44d5286..6e5c042e8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -5,9 +5,11 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; @@ -20,6 +22,7 @@ import java.util.Map; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; @@ -57,16 +60,15 @@ public class SimpleModelUtils { mainProcess.setName(processName); mainProcess.setExecutable(Boolean.TRUE); bpmnModel.addProcess(mainProcess); - // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。 + // 前端模型数据结构。 // 从 SimpleModel 构建 FlowNode 并添加到 Main Process buildAndAddBpmnFlowNode(simpleModelNode, mainProcess); // 找到 end event EndEvent endEvent = (EndEvent) CollUtil.findOne(mainProcess.getFlowElements(), item -> item instanceof EndEvent); if (endEvent == null) { - // 暂时为了兼容 单独构建 end event 节点 + // TODO 暂时为了兼容 单独构建 end event 节点. 后面去掉 endEvent = buildAndAddBpmnEndEvent(mainProcess); } - // 构建并添加节点之间的连线 Sequence Flow buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, endEvent.getId()); // 自动布局 @@ -75,14 +77,14 @@ public class SimpleModelUtils { } private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) { - // 节点为 null 退出 + // 节点为 null 或者 为END_EVENT 退出 if (node == null || node.getId() == null || END_EVENT.getType().equals(node.getType())) { return; } BpmSimpleModelNodeVO childNode = node.getChildNode(); - // 如果不是网关节点、且后续节点为 null. 添加与结束节点的连线 + // 如果是网关分支节点. 后续节点可能为 null。但不是 END_EVENT 节点 if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) { - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null); + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null, null); mainProcess.addFlowElement(sequenceFlow); return; } @@ -94,7 +96,7 @@ public class SimpleModelUtils { case COPY_TASK: case PARALLEL_GATEWAY_JOIN: case INCLUSIVE_GATEWAY_JOIN: { - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null); + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); mainProcess.addFlowElement(sequenceFlow); // 递归调用后续节点 buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); @@ -103,21 +105,21 @@ public class SimpleModelUtils { case PARALLEL_GATEWAY_FORK: case EXCLUSIVE_GATEWAY: case INCLUSIVE_GATEWAY_FORK: { - String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); + String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? targetId : childNode.getId(); List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); - for (int i = 0; i < conditionNodes.size(); i++) { - BpmSimpleModelNodeVO item = conditionNodes.get(i); + for (BpmSimpleModelNodeVO item : conditionNodes) { + String conditionExpression = buildConditionExpression(item); BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), - String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); + item.getId(), item.getName(), conditionExpression); mainProcess.addFlowElement(sequenceFlow); // 递归调用后续节点 buildAndAddBpmnSequenceFlow(mainProcess, nextNodeOnCondition, sequenceFlowTargetId); } else { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId, - String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); + item.getId(), item.getName(), conditionExpression); mainProcess.addFlowElement(sequenceFlow); } } @@ -132,7 +134,24 @@ public class SimpleModelUtils { } - private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String conditionExpression) { + /** + * 构造条件表达式 + * + * @param conditionNode 条件节点 + */ + private static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) { + Integer conditionType = MapUtil.getInt(conditionNode.getAttributes(), CONDITION_TYPE_ATTRIBUTE); + BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionType); + String conditionExpression = null; + if (conditionTypeEnum == BpmSimpleModeConditionType.CUSTOM_EXPRESSION) { + conditionExpression = MapUtil.getStr(conditionNode.getAttributes(), CONDITION_EXPRESSION_ATTRIBUTE); + } + // TODO 待增加其它类型 + return conditionExpression; + + } + + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) { SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(conditionExpression)) { sequenceFlow.setConditionExpression(conditionExpression); @@ -140,6 +159,9 @@ public class SimpleModelUtils { if (StrUtil.isNotEmpty(seqFlowId)) { sequenceFlow.setId(seqFlowId); } + if (StrUtil.isNotEmpty(seqName)) { + sequenceFlow.setName(seqName); + } return sequenceFlow; } @@ -249,8 +271,12 @@ public class SimpleModelUtils { Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); - // 网关的最后一个条件为 网关的 default sequence flow - exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); + // 寻找默认的序列流 + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), + item -> BooleanUtil.isTrue(MapUtil.getBool(item.getAttributes(), DEFAULT_FLOW_ATTRIBUTE))); + if (defaultSeqFlow != null) { + exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); + } return exclusiveGateway; } From b6d0176186c77af8ef3053c818932dd6fac1b8f3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 3 May 2024 19:56:12 +0800 Subject: [PATCH 011/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20=E4=BB=BF=E9=92=89?= =?UTF-8?q?=E9=92=89=E6=B5=81=E7=A8=8B=E8=AE=BE=E8=AE=A1=20-=20=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E6=8E=92=E4=BB=96=E7=BD=91=E5=85=B3=E5=92=8C=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/enums/definition/BpmSimpleModeConditionType.java | 2 +- .../framework/flowable/core/enums/SimpleModelConstants.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java index 042119346..9c9732a20 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java @@ -15,11 +15,11 @@ public enum BpmSimpleModeConditionType { CUSTOM_EXPRESSION(1, "自定义条件表达式"); - private final Integer type; private final String name; public static BpmSimpleModeConditionType valueOf(Integer type) { return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index 32dae5a8f..96dc6733a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -13,6 +13,8 @@ public interface SimpleModelConstants { */ String APPROVE_METHOD_ATTRIBUTE = "approveMethod"; + // TODO @芋艿:条件表达式的字段名 + /** * 网关节点默认序列流属性 */ @@ -27,4 +29,5 @@ public interface SimpleModelConstants { * 条件节点条件表达式属性 */ String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression"; + } From f9c7795efbc6c98ea1cb79bb03d7d98dcf497100 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 8 May 2024 20:56:39 +0800 Subject: [PATCH 012/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=8A=84=E9=80=81=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=A2=9E=E5=8A=A0=E8=A1=A8=E5=8D=95=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=89=A9=E5=B1=95=E5=85=83=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/framework/flowable/core/util/SimpleModelUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 6e5c042e8..33d861558 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -251,7 +251,10 @@ public class SimpleModelUtils { serviceTask.setName(node.getName()); // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners + // 添加抄送候选人元素 addCandidateElements(node, serviceTask); + // 添加表单字段权限属性元素 + addFormFieldsPermission(node, serviceTask); return serviceTask; } From f85ca1f88ff62b8e12ef355de9e7d87df01b13df Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 13 May 2024 14:27:01 +0800 Subject: [PATCH 013/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=9D=A1=E4=BB=B6=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=96=B0=E5=A2=9E=E6=9D=A1=E4=BB=B6=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmSimpleModeConditionType.java | 3 +- .../config/BpmFlowableConfiguration.java | 4 ++ ...riableConvertByTypeExpressionFunction.java | 29 +++++++++ .../core/enums/SimpleModelConstants.java | 5 ++ .../simple/SimpleModelConditionGroups.java | 63 +++++++++++++++++++ .../flowable/core/util/SimpleModelUtils.java | 30 ++++++++- 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java index 9c9732a20..f6dd04365 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java @@ -13,7 +13,8 @@ import lombok.Getter; @AllArgsConstructor public enum BpmSimpleModeConditionType { - CUSTOM_EXPRESSION(1, "自定义条件表达式"); + EXPRESSION(1, "条件表达式"), + RULE(2, "条件规则"); private final Integer type; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java index 8e69fdc75..e79437b43 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; import org.flowable.common.engine.api.delegate.event.FlowableEventListener; import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; @@ -56,12 +57,15 @@ public class BpmFlowableConfiguration { @Bean public EngineConfigurationConfigurer bpmProcessEngineConfigurationConfigurer( ObjectProvider listeners, + ObjectProvider customFlowableFunctionDelegates, BpmActivityBehaviorFactory bpmActivityBehaviorFactory) { return configuration -> { // 注册监听器,例如说 BpmActivityEventListener configuration.setEventListeners(ListUtil.toList(listeners.iterator())); // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义 configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); + // 设置自定义的函数 + configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); }; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java new file mode 100644 index 000000000..5ecba588c --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.el; + +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction; +import org.springframework.stereotype.Component; + +/** + * 根据流程变量 variable 的类型, 转换参数的值 + * + * @author jason + */ +@Component +public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction { + + public VariableConvertByTypeExpressionFunction() { + super("convertByType"); + } + + public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) { + Object variable = variableContainer.getVariable(variableName); + if (variable != null && parmaValue != null) { + // 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串 + if (!(parmaValue instanceof String) && variable instanceof String ) { + return parmaValue.toString(); + } + } + return parmaValue; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index 96dc6733a..33e2c016e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -30,4 +30,9 @@ public interface SimpleModelConstants { */ String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression"; + /** + * 条件规则的条件组属性 + */ + String CONDITION_GROUPS_ATTRIBUTE = "conditionGroups"; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java new file mode 100644 index 000000000..ccf7af949 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; + +import lombok.Data; + +import java.util.List; + +/** + * 仿钉钉流程设计器条件节点的条件组 Model + * + * @author jason + */ +@Data +public class SimpleModelConditionGroups { + + /** + * 条件组的逻辑关系是否为与的关系 + */ + private Boolean and; + + /** + * 条件组下的条件 + */ + private List conditions; + + @Data + public static class SimpleModelCondition { + + /** + * 条件下面多个规则的逻辑关系是否为与的关系 + */ + private Boolean and; + + + /** + * 条件下的规则 + */ + private List rules; + } + + @Data + public static class ConditionRule { + + /** + * 类型. TODO 暂时未定义, 未想好 + */ + private Integer type; + + /** + * 运行符号. 例如 == < + */ + private String opCode; + + /** + * 运算符左边的值 + */ + private String leftSide; + + /** + * 运算符右边的值 + */ + private String rightSide; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 33d861558..49eac0371 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -1,18 +1,22 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -143,12 +147,34 @@ public class SimpleModelUtils { Integer conditionType = MapUtil.getInt(conditionNode.getAttributes(), CONDITION_TYPE_ATTRIBUTE); BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionType); String conditionExpression = null; - if (conditionTypeEnum == BpmSimpleModeConditionType.CUSTOM_EXPRESSION) { + if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) { conditionExpression = MapUtil.getStr(conditionNode.getAttributes(), CONDITION_EXPRESSION_ATTRIBUTE); } + if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { + SimpleModelConditionGroups conditionGroups = BeanUtil.toBean(MapUtil.get(conditionNode.getAttributes(), + CONDITION_GROUPS_ATTRIBUTE, new TypeReference>() { + }), + SimpleModelConditionGroups.class); + if (conditionGroups != null && CollUtil.isNotEmpty(conditionGroups.getConditions())) { + List strConditionGroups = conditionGroups.getConditions().stream().map(item -> { + if (CollUtil.isNotEmpty(item.getRules())) { + Boolean and = item.getAnd(); + List list = CollectionUtils.convertList(item.getRules(), (rule) -> { + // 如果非数值类型加引号 + String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() : "\"" + rule.getRightSide() + "\""; + return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide); + }); + return "(" + CollUtil.join(list, and ? " && " : " || ") + ")"; + } else { + return ""; + } + }).toList(); + conditionExpression = String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); + } + + } // TODO 待增加其它类型 return conditionExpression; - } private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) { From b65ccd769f1d5c65940264cf2a7ce1d4bc359af7 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 19 May 2024 11:18:30 +0800 Subject: [PATCH 014/102] =?UTF-8?q?=E3=80=90=E5=90=8C=E6=AD=A5=E3=80=91BPM?= =?UTF-8?q?=EF=BC=9A=E5=90=88=E5=B9=B6=20master-jdk17=20=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../module/bpm/enums/ErrorCodeConstants.java | 4 ++ .../definition/BpmSimpleModelController.java | 39 +++++++++++++++++++ .../vo/simple/BpmSimpleModelSaveReqVO.java | 23 +++++++++++ .../core/enums/BpmnModelConstants.java | 7 ++++ .../flowable/core/util/BpmnModelUtils.java | 2 +- .../definition/BpmModelServiceImpl.java | 25 +++++++++--- .../definition/BpmSimpleModelService.java | 29 ++++++++++++++ .../task/BpmProcessInstanceCopyService.java | 6 ++- .../bpm/service/task/BpmTaskServiceImpl.java | 3 +- yudao-server/pom.xml | 10 ++--- 11 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java diff --git a/pom.xml b/pom.xml index e770c0359..beb8a071b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ yudao-module-system yudao-module-infra - + yudao-module-bpm diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index ec167719c..e344a2145 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -75,4 +75,8 @@ public interface ErrorCodeConstants { // ========== BPM 流程表达式 1-009-014-000 ========== ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); + // ========== BPM 仿钉钉流程设计器 1-009-015-000 ========== + // TODO @芋艿:这个错误码,需要关注下 + ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程"); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java new file mode 100644 index 000000000..2f88c6b6d --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; +import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +// TODO @芋艿:后续考虑下,怎么放这个 Controller +@Tag(name = "管理后台 - BPM 仿钉钉流程设计器") +@RestController +@RequestMapping("/bpm/simple") +public class BpmSimpleModelController { + @Resource + private BpmSimpleModelService bpmSimpleModelService; + + @PostMapping("/save") + @Operation(summary = "保存仿钉钉流程设计模型") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) { + return success(bpmSimpleModelService.saveSimpleModel(reqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得仿钉钉流程设计模型") + @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") + public CommonResult getSimpleModel(@RequestParam("modelId") String modelId){ + return success(bpmSimpleModelService.getSimpleModel(modelId)); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java new file mode 100644 index 000000000..54e0191d5 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// TODO @芋艿:或许挪到 model 里的 simple 包 +@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") +@Data +public class BpmSimpleModelSaveReqVO { + + @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "流程模型编号不能为空") + private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 + + @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "仿钉钉流程设计模型对象不能为空") + @Valid + private BpmSimpleModelNodeVO simpleModelBody; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index e21ba876b..f26890c9a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -1,5 +1,12 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; +import com.google.common.collect.ImmutableSet; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.UserTask; + +import java.util.Set; + /** * BPMN XML 常量信息 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 923c51fa6..e0b2d02b9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; @@ -14,7 +15,6 @@ import java.util.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; -import java.util.*; /** * 流程模型转操作工具类 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index abfa0d568..9a12b7acd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.service.definition; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -103,7 +104,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 保存流程定义 repositoryService.saveModel(model); // 保存 BPMN XML - saveModelBpmnXml(model, bpmnXml); + saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(bpmnXml)); return model.getId(); } @@ -121,7 +122,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 更新模型 repositoryService.saveModel(model); // 更新 BPMN XML - saveModelBpmnXml(model, updateReqVO.getBpmnXml()); + saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(updateReqVO.getBpmnXml())); } @Override @@ -236,11 +237,25 @@ public class BpmModelServiceImpl implements BpmModelService { } } - private void saveModelBpmnXml(Model model, String bpmnXml) { - if (StrUtil.isEmpty(bpmnXml)) { + @Override + public void saveModelBpmnXml(String id, byte[] xmlBytes) { + if (ArrayUtil.isEmpty(xmlBytes)) { return; } - repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml)); + repositoryService.addModelEditorSource(id, xmlBytes); + } + + @Override + public byte[] getModelSimpleJson(String id) { + return repositoryService.getModelEditorSourceExtra(id); + } + + @Override + public void saveModelSimpleJson(String id, byte[] jsonBytes) { + if (ArrayUtil.isEmpty(jsonBytes)) { + return; + } + repositoryService.addModelEditorSourceExtra(id, jsonBytes); } /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java new file mode 100644 index 000000000..9edaa4aa7 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.bpm.service.definition; + +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; +import jakarta.validation.Valid; + +/** + * 仿钉钉流程设计 Service 接口 + * + * @author jason + */ +public interface BpmSimpleModelService { + + /** + * 保存仿钉钉流程设计模型 + * + * @param reqVO 请求信息 + */ + Boolean saveSimpleModel(@Valid BpmSimpleModelSaveReqVO reqVO); + + /** + * 获取仿钉钉流程设计模型结构 + * + * @param modelId 流程模型编号 + * @return 仿钉钉流程设计模型结构 + */ + BpmSimpleModelNodeVO getSimpleModel(String modelId); + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index bd84490e8..94df76d4d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -17,9 +17,11 @@ public interface BpmProcessInstanceCopyService { * 流程实例的抄送 * * @param userIds 抄送的用户编号 - * @param taskId 流程任务编号 + * @param processInstanceId 流程编号 + * @param taskId 任务编号 + * @param taskName 任务名称 */ - void createProcessInstanceCopy(Collection userIds, String taskId); + void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName); /** * 获得抄送的流程的分页 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index aa5326ddd..c18ea5398 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -186,7 +186,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2. 抄送用户 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(), + reqVO.getId(), task.getName()); } // 情况一:被委派的任务,不调用 complete 去完成任务 diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index bc850b590..e5b816b9b 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -46,11 +46,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-bpm-biz + ${revision} + From afad8ac619c624bb5ef9b76c41809a1a57f617bc Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 20 May 2024 21:20:26 +0800 Subject: [PATCH 015/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=A2=9E=E5=8A=A0=E5=8F=91?= =?UTF-8?q?=E8=B5=B7=E4=BA=BA=E8=87=AA=E5=B7=B1=E5=80=99=E9=80=89=E4=BA=BA?= =?UTF-8?q?=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmTaskCandidateStartUserStrategy.java | 50 +++++++++++++++++++ .../enums/BpmTaskCandidateStrategyEnum.java | 1 + 2 files changed, 51 insertions(+) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java new file mode 100644 index 000000000..7341c6c60 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类。 用于需要发起人信息复核等场景 + * + * @author jason + */ +@Component +public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER; + } + + /** + * 无需校验参数 + */ + @Override + public void validateParam(String param) {} + + @Override + public Set calculateUsers(DelegateExecution execution, String param) { + String startUserId = processInstanceService.getProcessInstance(execution.getProcessInstanceId()).getStartUserId(); + return SetUtils.asSet(Long.valueOf(startUserId)); + } + + /** + * 不需要参数 + */ + @Override + public boolean isParamRequired() { + return false; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index a8b538501..596ff73f1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -21,6 +21,7 @@ public enum BpmTaskCandidateStrategyEnum { POST(22, "岗位"), USER(30, "用户"), START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 + START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 USER_GROUP(40, "用户组"), EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager ; From d34fef67dae7259e7a05c5f19778d097467ec0e4 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 23 May 2024 22:34:56 +0800 Subject: [PATCH 016/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=AE=A1=E6=89=B9=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E8=B6=85=E6=97=B6=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmTimerBoundaryEventType.java | 24 ++++ .../BpmUserTaskTimeoutActionEnum.java | 26 ++++ .../core/enums/BpmnModelConstants.java | 10 ++ .../listener/BpmTimerFiredEventListener.java | 117 ++++++++++++++++++ .../SysNotifyTodoTaskReminderConsumer.java | 42 +++++++ .../message/task/TodoTaskReminderMessage.java | 34 +++++ .../task/TodoTaskReminderProducer.java | 25 ++++ .../simple/SimpleModelUserTaskConfig.java | 68 ++++++++++ .../flowable/core/util/SimpleModelUtils.java | 81 ++++++++---- .../bpm/service/task/BpmTaskService.java | 7 ++ .../bpm/service/task/BpmTaskServiceImpl.java | 7 ++ 11 files changed, 417 insertions(+), 24 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java new file mode 100644 index 000000000..b4c63c25f --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 定时器边界事件类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmTimerBoundaryEventType { + + USER_TASK_TIMEOUT(1,"用户任务超时"); + + private final Integer type; + private final String name; + + public static BpmTimerBoundaryEventType typeOf(Integer type) { + return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java new file mode 100644 index 000000000..cc4d06d9d --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户任务超时处理执行动作枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskTimeoutActionEnum { + + AUTO_REMINDER(1,"自动提醒"), + AUTO_APPROVE(2, "自动同意"), + AUTO_REJECT(3, "自动拒绝"); + + private final Integer action; + private final String name; + + public static BpmUserTaskTimeoutActionEnum actionOf(Integer action) { + return ArrayUtil.firstMatch(item -> item.getAction().equals(action), values()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index f26890c9a..ec99ff978 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -30,6 +30,16 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 + */ + String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记定时边界事件类型 + */ + String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType"; + /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java new file mode 100644 index 000000000..780b6b739 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -0,0 +1,117 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; +import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; +import com.google.common.collect.ImmutableSet; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; +import org.flowable.job.api.Job; +import org.flowable.task.api.Task; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * 监听定时器触发事件 + * + * @author jason + */ +@Component +@Slf4j +public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmModelService bpmModelService; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmTaskService bpmTaskService; + + @Resource + private TodoTaskReminderProducer todoTaskReminderProducer; + + public static final Set TIME_EVENTS = ImmutableSet.builder() + .add(FlowableEngineEventType.TIMER_FIRED) + .build(); + + public BpmTimerFiredEventListener() { + super(TIME_EVENTS); + } + + @Override + protected void timerFired(FlowableEngineEntityEvent event) { + String processDefinitionId = event.getProcessDefinitionId(); + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); + Job entity = (Job) event.getEntity(); + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); + // 如果是定时器边界事件 + if (element instanceof BoundaryEvent) { + BoundaryEvent boundaryEvent = (BoundaryEvent) element; + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE)); + Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null)); + BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType); + // 类型为用户任务超时未处理的情况 + if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) { + ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION)); + Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null)); + processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction); + } + } + } + + private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) { + BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); + if (userTaskTimeoutAction != null) { + // 查询超时未处理的任务 + List taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey); + taskList.forEach(task -> { + // 自动提醒 + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { + TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) + .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); + todoTaskReminderProducer.sendReminderMessage(message); + } + // 自动同意 + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_APPROVE) { + // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 + TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); + TenantContextHolder.setIgnore(false); + BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId()) + .setReason("超时系统自动同意"); + bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); + + } + // 自动拒绝 + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REJECT) { + // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 + TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); + TenantContextHolder.setIgnore(false); + BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"); + bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req); + } + }); + } + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java new file mode 100644 index 000000000..d0dd51e93 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; +import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; +import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 待办任务提醒 - 站内信的消费者 + * + * @author jason + */ +@Component +@Slf4j +public class SysNotifyTodoTaskReminderConsumer { + + private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind"; + + @Resource + private NotifyMessageSendApi notifyMessageSendApi; + + @EventListener + @Async + public void onMessage(TodoTaskReminderMessage message) { + log.info("站内信消费者接收到消息 [消息内容({})] ", message); + TenantUtils.execute(message.getTenantId(), ()-> { + Map templateParams = MapUtil.newHashMap(); + templateParams.put("name", message.getTaskName()); + NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId()) + .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams); + notifyMessageSendApi.sendSingleMessageToAdmin(req); + }); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java new file mode 100644 index 000000000..f91b67327 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 待办任务提醒消息 + * + * @author jason + */ +@Data +public class TodoTaskReminderMessage { + + /** + * 租户 Id + */ + @NotNull(message = "租户 Id 不能未空") + private Long tenantId; + + /** + * 用户Id + */ + @NotNull(message = "用户 Id 不能未空") + private Long userId; + + /** + * 任务名称 + */ + @NotEmpty(message = "任务名称不能未空") + private String taskName; + + // TODO 暂时只有站内信通知. 后面可以增加 +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java new file mode 100644 index 000000000..816e3a71f --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task; + +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * 待办任务提醒 Producer + * + * @author jason + */ +@Component +@Validated +public class TodoTaskReminderProducer { + + @Resource + private ApplicationContext applicationContext; + + public void sendReminderMessage(@Valid TodoTaskReminderMessage message) { + applicationContext.publishEvent(message); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java new file mode 100644 index 000000000..49fd21018 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 仿钉钉流程设计器审批节点配置 Model + * + * @author jason + */ +@Data +public class SimpleModelUserTaskConfig { + + /** + * 候选人策略 + */ + private Integer candidateStrategy; + + /** + * 候选人参数 + */ + private String candidateParam; + + /** + * 字段权限 + */ + private List> fieldsPermission; + + /** + * 审批方式 + */ + private Integer approveMethod; + + + /** + * 超时处理 + */ + private TimeoutHandler timeoutHandler; + + + @Data + public static class TimeoutHandler { + + /** + * 是否开启超时处理 + */ + private Boolean enable; + + /** + * 超时执行的动作 + */ + private Integer action; + + /** + * 超时时间设置 + */ + private String timeDuration; + + /** + * 如果执行动作是自动提醒, 最大提醒次数 + */ + private Integer maxRemindCount; + + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 49eac0371..72f6157a9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -5,27 +5,27 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; import java.util.List; import java.util.Map; +import java.util.Objects; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; @@ -205,8 +205,15 @@ public class SimpleModelUtils { break; } case USER_TASK: { - UserTask userTask = buildBpmnUserTask(simpleModelNode); + // 获取用户任务的配置 + SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(simpleModelNode.getAttributes(), SimpleModelUserTaskConfig.class); + UserTask userTask = buildBpmnUserTask(simpleModelNode, userTaskConfig); mainProcess.addFlowElement(userTask); + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { + // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 + BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); + mainProcess.addFlowElement(boundaryEvent); + } break; } case COPY_TASK: { @@ -263,6 +270,28 @@ public class SimpleModelUtils { } } + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { + // 定时器边界事件 + BoundaryEvent boundaryEvent = new BoundaryEvent(); + boundaryEvent.setId(IdUtil.fastUUID()); + // 设置关联的任务为不会被中断 + boundaryEvent.setCancelActivity(false); + boundaryEvent.setAttachedToRef(userTask); + TimerEventDefinition eventDefinition = new TimerEventDefinition(); + eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); + if (Objects.equals(AUTO_REMINDER.getAction(), timeoutHandler.getAction()) && + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { + // 最大提醒次数 + eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); + } + boundaryEvent.addEventDefinition(eventDefinition); + // 添加定时器边界事件类型 + addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); + // 添加超时执行动作元素 + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); + return boundaryEvent; + } + private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { ParallelGateway parallelGateway = new ParallelGateway(); parallelGateway.setId(node.getId()); @@ -278,9 +307,14 @@ public class SimpleModelUtils { // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners // 添加抄送候选人元素 - addCandidateElements(node, serviceTask); + addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM), + serviceTask); // 添加表单字段权限属性元素 - addFormFieldsPermission(node, serviceTask); + List> fieldsPermissions = MapUtil.get(node.getAttributes(), + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { + }); + addFormFieldsPermission(fieldsPermissions, serviceTask); return serviceTask; } @@ -288,12 +322,10 @@ public class SimpleModelUtils { /** * 给节点添加候选人元素 */ - private static void addCandidateElements(BpmSimpleModelNodeVO node, FlowElement flowElement) { - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); + private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) { addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, - candidateStrategy == null ? null : String.valueOf(candidateStrategy)); - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + candidateStrategy == null ? null : candidateStrategy.toString()); + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); } private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { @@ -328,21 +360,25 @@ public class SimpleModelUtils { return endEvent; } - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); + // 设置审批任务的截止时间 + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { + userTask.setDueDate(userTaskConfig.getTimeoutHandler().getTimeDuration()); + } + // 添加候选人元素 - addCandidateElements(node, userTask); + addCandidateElements(userTaskConfig.getCandidateStrategy(), userTaskConfig.getCandidateParam(), userTask); // 添加表单字段权限属性元素 - addFormFieldsPermission(node, userTask); + addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask); // 处理多实例 - processMultiInstanceLoopCharacteristics(node, userTask); + processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTask); return userTask; } - private static void processMultiInstanceLoopCharacteristics(BpmSimpleModelNodeVO node, UserTask userTask) { - Integer approveMethod = MapUtil.getInt(node.getAttributes(), SimpleModelConstants.APPROVE_METHOD_ATTRIBUTE); + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { return; @@ -369,10 +405,7 @@ public class SimpleModelUtils { /** * 给节点添加表单字段权限元素 */ - private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) { - List> fieldsPermissions = MapUtil.get(node.getAttributes(), - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { - }); + private static void addFormFieldsPermission(List> fieldsPermissions, FlowElement flowElement) { if (CollUtil.isNotEmpty(fieldsPermissions)) { fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index f69757f14..0b989f44c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -127,6 +127,13 @@ public interface BpmTaskService { */ Task getTask(String id); + /** + * 根据条件查询已经分配的用户任务列表 + * @param processInstanceId 流程实例编号 + * @param taskDefineKey 任务定义 Key + */ + List getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey); + /** * 获取当前任务的可回退的 UserTask 集合 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index c18ea5398..4d6f9bae3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -433,6 +433,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); } + @Override + public List getAssignedTaskListByConditions(String processInstanceId, String defineKey) { + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId) + .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables(); + return taskQuery.list(); + } + private HistoricTaskInstance getHistoricTask(String id) { return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); } From d2750f08ce67e6cfa2db4873bdf54bb93bcc439e Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 26 May 2024 10:57:23 +0800 Subject: [PATCH 017/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=AE=A1=E6=89=B9=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=B7=BB=E5=8A=A0=E6=8B=92=E7=BB=9D=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...entType.java => BpmBoundaryEventType.java} | 9 ++- .../BpmUserTaskRejectHandlerType.java | 25 ++++++ .../core/enums/BpmnModelConstants.java | 15 +++- .../core/listener/BpmTaskEventListener.java | 76 ++++++++++++++++++- .../listener/BpmTimerFiredEventListener.java | 23 +++--- .../simple/SimpleModelUserTaskConfig.java | 19 ++++- .../flowable/core/util/BpmnModelUtils.java | 29 +++++++ .../flowable/core/util/SimpleModelUtils.java | 45 +++++++++-- .../bpm/service/task/BpmTaskService.java | 7 +- .../bpm/service/task/BpmTaskServiceImpl.java | 54 +++++++++---- 10 files changed, 254 insertions(+), 48 deletions(-) rename yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/{BpmTimerBoundaryEventType.java => BpmBoundaryEventType.java} (57%) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java similarity index 57% rename from yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java index b4c63c25f..f824dfaac 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java @@ -5,20 +5,21 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * 定时器边界事件类型枚举 + * BPM 边界事件 (boundary event) 自定义类型枚举 * * @author jason */ @Getter @AllArgsConstructor -public enum BpmTimerBoundaryEventType { +public enum BpmBoundaryEventType { - USER_TASK_TIMEOUT(1,"用户任务超时"); + USER_TASK_TIMEOUT(1,"用户任务超时"), + USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理"); private final Integer type; private final String name; - public static BpmTimerBoundaryEventType typeOf(Integer type) { + public static BpmBoundaryEventType typeOf(Integer type) { return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java new file mode 100644 index 000000000..b1bb07f17 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 用户任务拒绝处理类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskRejectHandlerType { + + TERMINATION(1, "终止流程"), + RETURN_PRE_USER_TASK(2, "驳回到用户任务"); + + private final Integer type; + private final String name; + + public static BpmUserTaskRejectHandlerType typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index ec99ff978..a3b0bc0c8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -30,15 +30,25 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型 + */ + String BOUNDARY_EVENT_TYPE = "boundaryEventType"; + /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 */ String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; /** - * BPMN ExtensionElement 的扩展属性,用于标记定时边界事件类型 + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 */ - String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType"; + String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id + */ + String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 @@ -66,4 +76,5 @@ public interface BpmnModelConstants { */ Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); + String REJECT_POST_PROCESS_MESSAGE_NAME = "message_reject_post_process"; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 733cc2b41..7f6d8c5c4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -2,23 +2,41 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; +import org.flowable.engine.delegate.event.FlowableMessageEvent; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.util.List; +import java.util.Objects; import java.util.Set; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseBoundaryEventExtensionElement; + /** * 监听 {@link Task} 的开始与完成 * @@ -34,15 +52,22 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Resource @Lazy // 解决循环依赖 private BpmActivityService activityService; + @Resource + @Lazy // 解决循环依赖 + private BpmProcessInstanceService processInstanceService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmModelService bpmModelService; public static final Set TASK_EVENTS = ImmutableSet.builder() .add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_ASSIGNED) -// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 + //.add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 + .add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED) .add(FlowableEngineEventType.ACTIVITY_CANCELLED) .build(); - public BpmTaskEventListener(){ + public BpmTaskEventListener() { super(TASK_EVENTS); } @@ -53,7 +78,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void taskAssigned(FlowableEngineEntityEvent event) { - taskService.updateTaskExtAssign((Task)event.getEntity()); + taskService.updateTaskExtAssign((Task) event.getEntity()); } @Override @@ -72,4 +97,47 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { }); } + @Override + protected void activityMessageReceived(FlowableMessageEvent event) { + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId()); + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId()); + if (element instanceof BoundaryEvent) { + BoundaryEvent boundaryEvent = (BoundaryEvent) element; + String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); + // 如果自定义类型为拒绝后处理,进行拒绝处理 + if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) { + String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE); + rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType)); + } + } + } + + private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) { + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); + if (userTaskRejectHandlerType != null) { + List taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey); + taskList.forEach(task -> { + Integer taskStatus = FlowableUtils.getTaskStatus(task); + // 只有处于拒绝状态下才处理 + if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) { + // 终止流程 + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) { + processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task)); + return; + } + // 驳回 + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { + String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID); + if (returnTaskId != null) { + BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId()) + .setTargetTaskDefinitionKey(returnTaskId) + .setReason("任务拒绝回退"); + taskService.returnTask(getLoginUserId(), reqVO); + } + } + } + }); + } + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index 780b6b739..891d91409 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -1,11 +1,10 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; -import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; @@ -18,7 +17,6 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BoundaryEvent; import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.ExtensionElement; import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; @@ -29,7 +27,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.List; -import java.util.Optional; import java.util.Set; /** @@ -69,23 +66,21 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe // 如果是定时器边界事件 if (element instanceof BoundaryEvent) { BoundaryEvent boundaryEvent = (BoundaryEvent) element; - ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE)); - Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null)); - BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType); + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); // 类型为用户任务超时未处理的情况 - if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) { - ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION)); - Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null)); - processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction); + if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) { + String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); + userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction)); } } } - private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) { + private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) { BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); if (userTaskTimeoutAction != null) { - // 查询超时未处理的任务 - List taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey); + // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? + List taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefKey); taskList.forEach(task -> { // 自动提醒 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java index 49fd21018..1ff3dd714 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import lombok.Data; import java.util.List; @@ -33,12 +34,15 @@ public class SimpleModelUserTaskConfig { */ private Integer approveMethod; - /** * 超时处理 */ private TimeoutHandler timeoutHandler; + /** + * 用户任务拒绝处理 + */ + private RejectHandler rejectHandler; @Data public static class TimeoutHandler { @@ -62,7 +66,20 @@ public class SimpleModelUserTaskConfig { * 如果执行动作是自动提醒, 最大提醒次数 */ private Integer maxRemindCount; + } + @Data + public static class RejectHandler { + + /** + * 用户任务拒绝处理类型 {@link BpmUserTaskRejectHandlerType} + */ + private Integer type; + + /** + * 用户任务拒绝后驳回的节点 Id + */ + private String returnNodeId; } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index e0b2d02b9..7a0b2b761 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; @@ -360,4 +361,32 @@ public class BpmnModelUtils { return userTaskList; } + /** + * 在用户任务中查找自定义的边界事件 + * + * @param userTask 用户任务 + * @param bpmBoundaryEventType 自定义的边界事件类型 + */ + public static BoundaryEvent findCustomBoundaryEventOfUserTask(UserTask userTask, BpmBoundaryEventType bpmBoundaryEventType) { + if (userTask == null) { + return null; + } + BoundaryEvent result = null; + for (BoundaryEvent item : userTask.getBoundaryEvents()) { + String boundaryEventType = parseBoundaryEventExtensionElement(item, BpmnModelConstants.BOUNDARY_EVENT_TYPE); + if (Objects.equals(bpmBoundaryEventType.getType(), NumberUtils.parseInt(boundaryEventType))) { + result = item; + break; + } + } + return result; + } + + public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { + if (boundaryEvent == null) { + return null; + } + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement)); + return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null); + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 72f6157a9..993baaa66 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig.RejectHandler; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -22,13 +23,14 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; -import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; -import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; +import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 仿钉钉快搭模型相关的工具方法 @@ -42,12 +44,12 @@ public class SimpleModelUtils { /** * 所有审批人同意的表达式 */ - public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= 0 }"; + public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }"; /** * 任一一名审批人同意的表达式 */ - public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }"; + public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) @@ -59,6 +61,12 @@ public class SimpleModelUtils { */ public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { BpmnModel bpmnModel = new BpmnModel(); + // 不加这个 解析 Message 会报 NPE 异常 + bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); + Message rejectPostProcessMsg = new Message(); + rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME); + bpmnModel.addMessage(rejectPostProcessMsg); + Process mainProcess = new Process(); mainProcess.setId(processId); mainProcess.setName(processName); @@ -214,6 +222,12 @@ public class SimpleModelUtils { BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); mainProcess.addFlowElement(boundaryEvent); } + if (userTaskConfig.getRejectHandler() != null) { + // 添加用户任务拒绝 Message Boundary Event, 用于任务的拒绝处理 + BoundaryEvent boundaryEvent = buildUserTaskRejectBoundaryEvent(userTask, userTaskConfig.getRejectHandler()); + mainProcess.addFlowElement(boundaryEvent); + } + break; } case COPY_TASK: { @@ -270,10 +284,27 @@ public class SimpleModelUtils { } } + private static BoundaryEvent buildUserTaskRejectBoundaryEvent(UserTask userTask, RejectHandler rejectHandler) { + BoundaryEvent messageBoundaryEvent = new BoundaryEvent(); + messageBoundaryEvent.setId("Event-" + IdUtil.fastUUID()); + // 设置关联的任务为不会被中断 + messageBoundaryEvent.setCancelActivity(false); + messageBoundaryEvent.setAttachedToRef(userTask); + MessageEventDefinition messageEventDefinition = new MessageEventDefinition(); + messageEventDefinition.setMessageRef(REJECT_POST_PROCESS_MESSAGE_NAME); + messageBoundaryEvent.addEventDefinition(messageEventDefinition); + addExtensionElement(messageBoundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_REJECT_POST_PROCESS.getType().toString()); + addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); + if (Objects.equals(rejectHandler.getType(), RETURN_PRE_USER_TASK.getType())) { + addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); + } + return messageBoundaryEvent; + } + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); - boundaryEvent.setId(IdUtil.fastUUID()); + boundaryEvent.setId("Event-" + IdUtil.fastUUID()); // 设置关联的任务为不会被中断 boundaryEvent.setCancelActivity(false); boundaryEvent.setAttachedToRef(userTask); @@ -286,7 +317,7 @@ public class SimpleModelUtils { } boundaryEvent.addEventDefinition(eventDefinition); // 添加定时器边界事件类型 - addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); // 添加超时执行动作元素 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); return boundaryEvent; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 0b989f44c..f65e3333b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -90,6 +90,8 @@ public interface BpmTaskService { */ void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); + + /** * 将流程任务分配给指定用户 * @@ -129,10 +131,11 @@ public interface BpmTaskService { /** * 根据条件查询已经分配的用户任务列表 - * @param processInstanceId 流程实例编号 + * @param processInstanceId 流程实例编号,不允许为空 + * @param executionId execution Id * @param taskDefineKey 任务定义 Key */ - List getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey); + List getAssignedTaskListByConditions(String processInstanceId, String executionId, String taskDefineKey); /** * 获取当前任务的可回退的 UserTask 集合 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 4d6f9bae3..57ea05812 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -9,16 +9,18 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -26,6 +28,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BoundaryEvent; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; @@ -33,6 +36,7 @@ import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; +import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; @@ -245,7 +249,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务: - * + *

* 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批 * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批 * @@ -278,7 +282,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.resolveTask(parentTaskId); // 3.1.2 更新流程任务 status updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus()); - // 3.2 情况二:处理向【向后】加签 + // 3.2 情况二:处理向【向后】加签 } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 @@ -333,14 +337,29 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); - // 3. 更新流程实例,审批不通过! + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 寻找用户任务的自定义拒绝后处理边界事件 + BoundaryEvent rejectBoundaryEvent = BpmnModelUtils.findCustomBoundaryEventOfUserTask((UserTask) flowElement, + BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS); + + if (rejectBoundaryEvent != null) { + Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()) + .activityId(rejectBoundaryEvent.getId()).singleResult(); + if (execution != null) { + // 3.1 触发消息边界事件. 进一步的处理交给 BpmTaskEventListener + runtimeService.messageEventReceived(BpmnModelConstants.REJECT_POST_PROCESS_MESSAGE_NAME, execution.getId()); + return; + } + } + // 3.2 没有找到拒绝后处理边界事件, 更新流程实例,审批不通过! processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); } /** * 更新流程任务的 status 状态 * - * @param id 任务编号 + * @param id 任务编号 * @param status 状态 */ private void updateTaskStatus(String id, Integer status) { @@ -350,7 +369,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 更新流程任务的 status 状态、reason 理由 * - * @param id 任务编号 + * @param id 任务编号 * @param status 状态 * @param reason 理由(审批通过、审批不通过的理由) */ @@ -434,9 +453,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { } @Override - public List getAssignedTaskListByConditions(String processInstanceId, String defineKey) { - TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId) - .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables(); + public List getAssignedTaskListByConditions(String processInstanceId, String executionId, String defineKey) { + Assert.notNull(processInstanceId, "processInstanceId 不能为空"); + TaskQuery taskQuery = taskService.createTaskQuery().taskAssigned().processInstanceId(processInstanceId).active() + .includeTaskLocalVariables(); + if (StrUtil.isNotEmpty(executionId)) { + taskQuery.executionId(executionId); + } + if (StrUtil.isNotEmpty(defineKey)) { + taskQuery.taskDefinitionKey(defineKey); + } return taskQuery.list(); } @@ -664,7 +690,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { List currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { - List userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); + List userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); } return taskEntity; @@ -673,8 +699,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 创建加签子任务 * - * @param userIds 被加签的用户 ID - * @param taskEntity 被加签的任务 + * @param userIds 被加签的用户 ID + * @param taskEntity 被加签的任务 */ private void createSignTaskList(List userIds, TaskEntityImpl taskEntity) { if (CollUtil.isEmpty(userIds)) { @@ -703,7 +729,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.1 向前加签,设置审批人 if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { task.setAssignee(assignee); - // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 + // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 } else { task.setOwner(assignee); } From 007639d61aeed2b1530b6d32555ac85980566c86 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 27 May 2024 09:28:04 +0800 Subject: [PATCH 018/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E7=AE=80=E5=8C=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=8A=82=E7=82=B9=E6=8B=92=E7=BB=9D=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 1 + .../BpmUserTaskRejectHandlerType.java | 2 +- .../core/listener/BpmTaskEventListener.java | 110 +++++++----------- .../flowable/core/util/BpmnModelUtils.java | 30 ++--- .../flowable/core/util/SimpleModelUtils.java | 35 ++---- .../bpm/service/task/BpmTaskServiceImpl.java | 33 +++--- 6 files changed, 79 insertions(+), 132 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index e344a2145..daa4d8616 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -51,6 +51,7 @@ public interface ErrorCodeConstants { ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务"); ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人"); ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); + ErrorCode TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID = new ErrorCode(1_009_005_015, "回退任务未指定目标任务编号"); ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); // ========== 动态表单模块 1-009-010-000 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index b1bb07f17..7a455f382 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -14,7 +14,7 @@ import lombok.Getter; public enum BpmUserTaskRejectHandlerType { TERMINATION(1, "终止流程"), - RETURN_PRE_USER_TASK(2, "驳回到用户任务"); + RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"); private final Integer type; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 7f6d8c5c4..89e685467 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -2,41 +2,23 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; -import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.BoundaryEvent; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; -import org.flowable.engine.delegate.event.FlowableMessageEvent; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.List; -import java.util.Objects; import java.util.Set; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseBoundaryEventExtensionElement; - /** * 监听 {@link Task} 的开始与完成 * @@ -52,18 +34,12 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Resource @Lazy // 解决循环依赖 private BpmActivityService activityService; - @Resource - @Lazy // 解决循环依赖 - private BpmProcessInstanceService processInstanceService; - @Resource - @Lazy // 延迟加载,避免循环依赖 - private BpmModelService bpmModelService; public static final Set TASK_EVENTS = ImmutableSet.builder() .add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_ASSIGNED) //.add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 - .add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED) +// .add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED) .add(FlowableEngineEventType.ACTIVITY_CANCELLED) .build(); @@ -97,47 +73,47 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { }); } - @Override - protected void activityMessageReceived(FlowableMessageEvent event) { - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId()); - FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId()); - if (element instanceof BoundaryEvent) { - BoundaryEvent boundaryEvent = (BoundaryEvent) element; - String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); - // 如果自定义类型为拒绝后处理,进行拒绝处理 - if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) { - String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE); - rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType)); - } - } - } - - private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) { - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); - if (userTaskRejectHandlerType != null) { - List taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey); - taskList.forEach(task -> { - Integer taskStatus = FlowableUtils.getTaskStatus(task); - // 只有处于拒绝状态下才处理 - if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) { - // 终止流程 - if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) { - processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task)); - return; - } - // 驳回 - if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { - String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID); - if (returnTaskId != null) { - BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId()) - .setTargetTaskDefinitionKey(returnTaskId) - .setReason("任务拒绝回退"); - taskService.returnTask(getLoginUserId(), reqVO); - } - } - } - }); - } - } +// @Override +// protected void activityMessageReceived(FlowableMessageEvent event) { +// BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId()); +// FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId()); +// if (element instanceof BoundaryEvent) { +// BoundaryEvent boundaryEvent = (BoundaryEvent) element; +// String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); +// // 如果自定义类型为拒绝后处理,进行拒绝处理 +// if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) { +// String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE); +// rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType)); +// } +// } +// } +// +// private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) { +// BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); +// if (userTaskRejectHandlerType != null) { +// List taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey); +// taskList.forEach(task -> { +// Integer taskStatus = FlowableUtils.getTaskStatus(task); +// // 只有处于拒绝状态下才处理 +// if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) { +// // 终止流程 +// if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) { +// processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task)); +// return; +// } +// // 驳回 +// if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { +// String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID); +// if (returnTaskId != null) { +// BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId()) +// .setTargetTaskDefinitionKey(returnTaskId) +// .setReason("任务拒绝回退"); +// taskService.returnTask(getLoginUserId(), reqVO); +// } +// } +// } +// }); +// } +// } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 7a0b2b761..cdaa155dc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -5,7 +5,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; @@ -43,6 +42,14 @@ public class BpmnModelUtils { return candidateParam; } + public static String parseExtensionElement(FlowElement flowElement, String elementName) { + if (flowElement == null) { + return null; + } + ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); + return Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null); + } + // TODO @jason:貌似这个没地方调用??? @芋艿 在 BpmTaskConvert里面。暂时注释掉了。 public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); @@ -361,27 +368,6 @@ public class BpmnModelUtils { return userTaskList; } - /** - * 在用户任务中查找自定义的边界事件 - * - * @param userTask 用户任务 - * @param bpmBoundaryEventType 自定义的边界事件类型 - */ - public static BoundaryEvent findCustomBoundaryEventOfUserTask(UserTask userTask, BpmBoundaryEventType bpmBoundaryEventType) { - if (userTask == null) { - return null; - } - BoundaryEvent result = null; - for (BoundaryEvent item : userTask.getBoundaryEvents()) { - String boundaryEventType = parseBoundaryEventExtensionElement(item, BpmnModelConstants.BOUNDARY_EVENT_TYPE); - if (Objects.equals(bpmBoundaryEventType.getType(), NumberUtils.parseInt(boundaryEventType))) { - result = item; - break; - } - } - return result; - } - public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { if (boundaryEvent == null) { return null; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 993baaa66..dd7bad7a8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -23,10 +23,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; @@ -222,12 +220,6 @@ public class SimpleModelUtils { BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); mainProcess.addFlowElement(boundaryEvent); } - if (userTaskConfig.getRejectHandler() != null) { - // 添加用户任务拒绝 Message Boundary Event, 用于任务的拒绝处理 - BoundaryEvent boundaryEvent = buildUserTaskRejectBoundaryEvent(userTask, userTaskConfig.getRejectHandler()); - mainProcess.addFlowElement(boundaryEvent); - } - break; } case COPY_TASK: { @@ -284,23 +276,6 @@ public class SimpleModelUtils { } } - private static BoundaryEvent buildUserTaskRejectBoundaryEvent(UserTask userTask, RejectHandler rejectHandler) { - BoundaryEvent messageBoundaryEvent = new BoundaryEvent(); - messageBoundaryEvent.setId("Event-" + IdUtil.fastUUID()); - // 设置关联的任务为不会被中断 - messageBoundaryEvent.setCancelActivity(false); - messageBoundaryEvent.setAttachedToRef(userTask); - MessageEventDefinition messageEventDefinition = new MessageEventDefinition(); - messageEventDefinition.setMessageRef(REJECT_POST_PROCESS_MESSAGE_NAME); - messageBoundaryEvent.addEventDefinition(messageEventDefinition); - addExtensionElement(messageBoundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_REJECT_POST_PROCESS.getType().toString()); - addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); - if (Objects.equals(rejectHandler.getType(), RETURN_PRE_USER_TASK.getType())) { - addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); - } - return messageBoundaryEvent; - } - private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); @@ -406,9 +381,19 @@ public class SimpleModelUtils { addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask); // 处理多实例 processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTask); + // 添加任务被拒绝的处理元素 + addTaskRejectElements(userTaskConfig.getRejectHandler(), userTask); return userTask; } + private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { + if (rejectHandler == null) { + return; + } + addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); + addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); + } + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 57ea05812..c57232939 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -12,13 +12,12 @@ import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; @@ -28,7 +27,6 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.BoundaryEvent; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; @@ -36,7 +34,6 @@ import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; -import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; @@ -57,6 +54,8 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID; /** * 流程任务实例 Service 实现类 @@ -336,23 +335,23 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.2 添加评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); - + // 3.1 解析用户任务的拒绝处理类型 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - // 寻找用户任务的自定义拒绝后处理边界事件 - BoundaryEvent rejectBoundaryEvent = BpmnModelUtils.findCustomBoundaryEventOfUserTask((UserTask) flowElement, - BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS); - - if (rejectBoundaryEvent != null) { - Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()) - .activityId(rejectBoundaryEvent.getId()).singleResult(); - if (execution != null) { - // 3.1 触发消息边界事件. 进一步的处理交给 BpmTaskEventListener - runtimeService.messageEventReceived(BpmnModelConstants.REJECT_POST_PROCESS_MESSAGE_NAME, execution.getId()); - return; + Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE)); + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); + // 3.2 类型为驳回到指定的任务节点 + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { + String returnTaskId = BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); + if (returnTaskId == null) { + throw exception(TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID); } + BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId) + .setReason(reqVO.getReason()); + returnTask(userId, returnReq); + return; } - // 3.2 没有找到拒绝后处理边界事件, 更新流程实例,审批不通过! + // 3.3 其他情况 终止流程。 TODO 后续可能会增加处理类型 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); } From 95bbf749a166f0900778750b29b0539b4ab4645b Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 27 May 2024 13:23:13 +0800 Subject: [PATCH 019/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20=E5=BF=AB=E6=90=AD?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelController.java | 2 +- .../service/definition/BpmModelService.java | 10 +- .../definition/BpmSimpleModelServiceImpl.java | 118 +----------------- 3 files changed, 13 insertions(+), 117 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java index 2f88c6b6d..1da4250b0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -// TODO @芋艿:后续考虑下,怎么放这个 Controller +// TODO @jason:融合到 BpmModelController 中,url 是 /bpm/model/simple/... 这样,通过一个子目录区分;目的是:逻辑更聚焦! @Tag(name = "管理后台 - BPM 仿钉钉流程设计器") @RestController @RequestMapping("/bpm/simple") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index 48b7ec4f3..b02f5a579 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -46,7 +46,6 @@ public interface BpmModelService { */ byte[] getModelBpmnXML(String id); - /** * 保存流程模型的 BPMN XML * @@ -107,4 +106,13 @@ public interface BpmModelService { */ BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); + // ========== 仿钉钉/飞书的精简模型 ========= + + // TODO @jason:使用 ========== 仿钉钉/飞书的精简模型 ========= 分隔下;把相关的 controller、service 懂合并了;另外,vo 可以挪到 model/simple 这样的形式; + + // TODO @jason:BpmSimpleModelServiceImpl 迁移到这里,搞成 updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) + // TODO @jason:BpmSimpleModelServiceImpl 迁移到这里,搞成 getSimpleModel; + + // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案? + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index 53af051dc..3f77cfbe7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -1,31 +1,19 @@ package cn.iocoder.yudao.module.bpm.service.definition; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import jakarta.annotation.Resource; -import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.repository.Model; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.util.List; -import java.util.Map; - import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_PARAM; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY; // TODO @jason:这块可以讨论下,是不是合并成一个 BpmnModelServiceImpl /** @@ -42,20 +30,12 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { @Override public Boolean saveSimpleModel(BpmSimpleModelSaveReqVO reqVO) { + // 1.1 校验流程模型存在 Model model = bpmModelService.getModel(reqVO.getModelId()); if (model == null) { throw exception(MODEL_NOT_EXISTS); } -// byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId()); -// if (ArrayUtil.isEmpty(bpmnBytes)) { -// // BPMN XML 不存在。新增 -// BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); -// bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); -// return Boolean.TRUE; -// } else { -// // TODO BPMN XML 已经存在。如何修改 ?? TODO add by 芋艿:感觉一个流程,只能二选一,要么 bpmn、要么 simple -// return Boolean.FALSE; -// } + // 1. JSON 转换成 bpmnModel BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); // 2.1 保存 Bpmn XML @@ -77,96 +57,4 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); } - // TODO @jason:一般要支持这个么?感觉 bpmn 转 json 支持会不会太复杂。可以优先级低一点,做下调研~ - - /** - * Bpmn Model 转换成 仿钉钉流程设计模型数据结构(json) 待完善 - * - * @param bpmnModel Bpmn Model - * @return 仿钉钉流程设计模型数据结构 - */ - private BpmSimpleModelNodeVO convertBpmnModelToSimpleModel(BpmnModel bpmnModel) { - if (bpmnModel == null) { - return null; - } - StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel); - if (startEvent == null) { - return null; - } - BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO(); - rootNode.setType(START_EVENT.getType()); - rootNode.setId(startEvent.getId()); - rootNode.setName(startEvent.getName()); - recursiveBuildSimpleModelNode(startEvent, rootNode); - return rootNode; - } - - private void recursiveBuildSimpleModelNode(FlowNode currentFlowNode, BpmSimpleModelNodeVO currentSimpleModeNode) { - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentSimpleModeNode.getType()); - Assert.notNull(nodeType, "节点类型不支持"); - // 校验节点是否支持转仿钉钉的流程模型 - List outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode); - if (CollUtil.isEmpty(outgoingFlows) || CollUtil.getFirst(outgoingFlows).getTargetFlowElement() == null) { - return; - } - FlowElement targetElement = CollUtil.getFirst(outgoingFlows).getTargetFlowElement(); - // 如果是 EndEvent 直接退出 - if (targetElement instanceof EndEvent) { - return; - } - if (targetElement instanceof UserTask) { - BpmSimpleModelNodeVO childNode = convertUserTaskToSimpleModelNode((UserTask) targetElement); - currentSimpleModeNode.setChildNode(childNode); - recursiveBuildSimpleModelNode((FlowNode) targetElement, childNode); - } - // TODO 其它节点类型待实现 - } - - private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) { - BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO(); - simpleModelNodeVO.setType(BpmSimpleModelNodeType.USER_TASK.getType()); - simpleModelNodeVO.setName(userTask.getName()); - simpleModelNodeVO.setId(userTask.getId()); - Map attributes = MapUtil.newHashMap(); - // TODO 暂时是普通审批,需要加会签 - attributes.put("approveMethod", 1); - attributes.computeIfAbsent(USER_TASK_CANDIDATE_STRATEGY, (key) -> BpmnModelUtils.parseCandidateStrategy(userTask)); - attributes.computeIfAbsent(USER_TASK_CANDIDATE_PARAM, (key) -> BpmnModelUtils.parseCandidateParam(userTask)); - simpleModelNodeVO.setAttributes(attributes); - return simpleModelNodeVO; - } - - private List validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) { - switch (nodeType) { - case START_EVENT: - case USER_TASK: { - List outgoingFlows = currentFlowNode.getOutgoingFlows(); - if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) { - throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); - } - validIsSupportFlowNode(CollUtil.getFirst(outgoingFlows).getTargetFlowElement()); - return outgoingFlows; - } - default: { - // TODO 其它节点类型待实现 - throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); - } - } - } - - private void validIsSupportFlowNode(FlowElement targetElement) { - if (targetElement == null) { - return; - } - boolean isSupport = false; - for (Class item : BpmnModelConstants.SUPPORT_CONVERT_SIMPLE_FlOW_NODES) { - if (item.isInstance(targetElement)) { - isSupport = true; - break; - } - } - if (!isSupport) { - throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); - } - } } From 5b97d565cd6632ed340f6d8ae077e31cbd253e83 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 27 May 2024 21:15:08 +0800 Subject: [PATCH 020/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/definition/BpmModelController.java | 19 ++++++ .../definition/BpmSimpleModelController.java | 39 ------------ .../simple/BpmSimpleModelNodeVO.java | 2 +- .../simple/BpmSimpleModelUpdateReqVO.java} | 5 +- .../flowable/core/util/SimpleModelUtils.java | 7 +-- .../service/definition/BpmModelService.java | 28 +++++++-- .../definition/BpmModelServiceImpl.java | 30 ++++++++++ .../definition/BpmSimpleModelService.java | 29 --------- .../definition/BpmSimpleModelServiceImpl.java | 60 ------------------- 9 files changed, 75 insertions(+), 144 deletions(-) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/{ => model}/simple/BpmSimpleModelNodeVO.java (98%) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/{simple/BpmSimpleModelSaveReqVO.java => model/simple/BpmSimpleModelUpdateReqVO.java} (89%) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index dcf91260f..c9ff059ff 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.util.io.IoUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; @@ -145,4 +147,21 @@ public class BpmModelController { return success(true); } + // ========== 仿钉钉/飞书的精简模型 ========= + + @GetMapping("/simple/get") + @Operation(summary = "获得仿钉钉流程设计模型") + @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") + public CommonResult getSimpleModel(@RequestParam("modelId") String modelId){ + return success(modelService.getSimpleModel(modelId)); + } + + @PostMapping("/simple/update") + @Operation(summary = "保存仿钉钉流程设计模型") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) { + modelService.updateSimpleModel(reqVO); + return success(Boolean.TRUE); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java deleted file mode 100644 index 1da4250b0..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; -import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -// TODO @jason:融合到 BpmModelController 中,url 是 /bpm/model/simple/... 这样,通过一个子目录区分;目的是:逻辑更聚焦! -@Tag(name = "管理后台 - BPM 仿钉钉流程设计器") -@RestController -@RequestMapping("/bpm/simple") -public class BpmSimpleModelController { - @Resource - private BpmSimpleModelService bpmSimpleModelService; - - @PostMapping("/save") - @Operation(summary = "保存仿钉钉流程设计模型") - @PreAuthorize("@ss.hasPermission('bpm:model:update')") - public CommonResult saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) { - return success(bpmSimpleModelService.saveSimpleModel(reqVO)); - } - - @GetMapping("/get") - @Operation(summary = "获得仿钉钉流程设计模型") - @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") - public CommonResult getSimpleModel(@RequestParam("modelId") String modelId){ - return success(bpmSimpleModelService.getSimpleModel(modelId)); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java similarity index 98% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 4a9e653e5..4bc5ac85e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple; +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java similarity index 89% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java index 54e0191d5..20f3bf1b1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple; +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; @@ -6,10 +6,9 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; -// TODO @芋艿:或许挪到 model 里的 simple 包 @Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") @Data -public class BpmSimpleModelSaveReqVO { +public class BpmSimpleModelUpdateReqVO { @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotEmpty(message = "流程模型编号不能为空") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index dd7bad7a8..a5fbb5a27 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -7,7 +7,7 @@ import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; @@ -59,11 +59,6 @@ public class SimpleModelUtils { */ public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { BpmnModel bpmnModel = new BpmnModel(); - // 不加这个 解析 Message 会报 NPE 异常 - bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); - Message rejectPostProcessMsg = new Message(); - rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME); - bpmnModel.addMessage(rejectPostProcessMsg); Process mainProcess = new Process(); mainProcess.setId(processId); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index b02f5a579..3b9646f73 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -1,7 +1,11 @@ package cn.iocoder.yudao.module.bpm.service.definition; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import jakarta.validation.Valid; import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.repository.Model; @@ -49,7 +53,7 @@ public interface BpmModelService { /** * 保存流程模型的 BPMN XML * - * @param id 编号 + * @param id 编号 * @param xmlBytes BPMN XML bytes */ // TODO @芋艿:感觉可以不修改这个方法,而是额外加一个方法;传入 id,bpmn,json; @@ -57,6 +61,7 @@ public interface BpmModelService { /** * 获得仿钉钉快搭模型的 JSON 数据 + * * @param id 编号 * @return JSON bytes */ @@ -64,7 +69,8 @@ public interface BpmModelService { /** * 保存仿钉钉快搭模型的 JSON 数据 - * @param id 编号 + * + * @param id 编号 * @param jsonBytes JSON bytes */ void saveModelSimpleJson(String id, byte[] jsonBytes); @@ -108,10 +114,20 @@ public interface BpmModelService { // ========== 仿钉钉/飞书的精简模型 ========= - // TODO @jason:使用 ========== 仿钉钉/飞书的精简模型 ========= 分隔下;把相关的 controller、service 懂合并了;另外,vo 可以挪到 model/simple 这样的形式; + /** + * 获取仿钉钉流程设计模型结构 + * + * @param modelId 流程模型编号 + * @return 仿钉钉流程设计模型结构 + */ + BpmSimpleModelNodeVO getSimpleModel(String modelId); - // TODO @jason:BpmSimpleModelServiceImpl 迁移到这里,搞成 updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) - // TODO @jason:BpmSimpleModelServiceImpl 迁移到这里,搞成 getSimpleModel; + /** + * 更新仿钉钉流程设计模型 + * + * @param reqVO 请求信息 + */ + void updateSimpleModel(@Valid BpmSimpleModelUpdateReqVO reqVO); // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案? diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 9a12b7acd..349b4200d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -9,12 +9,15 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -209,6 +212,33 @@ public class BpmModelServiceImpl implements BpmModelService { return repositoryService.getBpmnModel(processDefinitionId); } + @Override + public BpmSimpleModelNodeVO getSimpleModel(String modelId) { + Model model = getModel(modelId); + if (model == null) { + throw exception(MODEL_NOT_EXISTS); + } + // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ 获取 仿钉钉快搭模型的JSON 数据 + byte[] jsonBytes = getModelSimpleJson(model.getId()); + return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); + } + + @Override + public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) { + // 1.1 校验流程模型存在 + Model model = getModel(reqVO.getModelId()); + if (model == null) { + throw exception(MODEL_NOT_EXISTS); + } + // 1.2 JSON 转换成 bpmnModel + BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); + // 2.1 保存 Bpmn XML + saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); + // 2.2 保存 JSON 数据 + saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModelBody())); + } + + /** * 校验流程表单已配置 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java deleted file mode 100644 index 9edaa4aa7..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.bpm.service.definition; - -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; -import jakarta.validation.Valid; - -/** - * 仿钉钉流程设计 Service 接口 - * - * @author jason - */ -public interface BpmSimpleModelService { - - /** - * 保存仿钉钉流程设计模型 - * - * @param reqVO 请求信息 - */ - Boolean saveSimpleModel(@Valid BpmSimpleModelSaveReqVO reqVO); - - /** - * 获取仿钉钉流程设计模型结构 - * - * @param modelId 流程模型编号 - * @return 仿钉钉流程设计模型结构 - */ - BpmSimpleModelNodeVO getSimpleModel(String modelId); - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java deleted file mode 100644 index 3f77cfbe7..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package cn.iocoder.yudao.module.bpm.service.definition; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; -import jakarta.annotation.Resource; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.engine.repository.Model; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; - -// TODO @jason:这块可以讨论下,是不是合并成一个 BpmnModelServiceImpl -/** - * 仿钉钉流程设计 Service 实现类 - * - * @author jason - */ -@Service -@Validated -public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { - - @Resource - private BpmModelService bpmModelService; - - @Override - public Boolean saveSimpleModel(BpmSimpleModelSaveReqVO reqVO) { - // 1.1 校验流程模型存在 - Model model = bpmModelService.getModel(reqVO.getModelId()); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } - - // 1. JSON 转换成 bpmnModel - BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); - // 2.1 保存 Bpmn XML - bpmModelService.saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); - // 2.2 保存 JSON 数据 - bpmModelService.saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModelBody())); - return Boolean.TRUE; - } - - @Override - public BpmSimpleModelNodeVO getSimpleModel(String modelId) { - Model model = bpmModelService.getModel(modelId); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } - // 暂时不用 bpmn 转 json, 有点复杂, - // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ 获取 仿钉钉快搭模型的JSON 数据 - byte[] jsonBytes = bpmModelService.getModelSimpleJson(model.getId()); - return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); - } - -} From 1ae06b89e4cc639a638e8745a36535c3c3bc3468 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 27 May 2024 21:18:59 +0800 Subject: [PATCH 021/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20simple=20=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E7=9A=84=E8=BD=AC=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmApproveMethodEnum.java | 7 +- .../flowable/core/util/SimpleModelUtils.java | 73 ++++++++++++++----- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index 004c77468..522ff45ea 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -15,9 +15,9 @@ import lombok.Getter; public enum BpmApproveMethodEnum { SINGLE_PERSON_APPROVE(1, "单人审批"), - ALL_APPROVE(2, "多人会签(需所有审批人同意)"), - ANY_OF_APPROVE(3, "多人或签(一名审批人同意即可)"), - SEQUENTIAL_APPROVE(4, "依次审批"); + ALL_APPROVE(2, "多人会签(需所有审批人同意)"), // 会签 + ANY_OF_APPROVE(3, "多人或签(一名审批人同意即可)"), // 或签 + SEQUENTIAL_APPROVE(4, "依次审批"); // 依次审批 /** * 审批方式 @@ -31,4 +31,5 @@ public enum BpmApproveMethodEnum { public static BpmApproveMethodEnum valueOf(Integer method) { return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index dd7bad7a8..1f6771756 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -49,6 +49,8 @@ public class SimpleModelUtils { */ public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; + // TODO @jason:建议方法名,改成 buildBpmnModel + // TODO @yunai:注释需要完善下; /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) * @@ -59,28 +61,31 @@ public class SimpleModelUtils { */ public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { BpmnModel bpmnModel = new BpmnModel(); + bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace; + // TODO 芋艿:后续在 review // 不加这个 解析 Message 会报 NPE 异常 - bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); Message rejectPostProcessMsg = new Message(); rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME); bpmnModel.addMessage(rejectPostProcessMsg); - Process mainProcess = new Process(); - mainProcess.setId(processId); - mainProcess.setName(processName); - mainProcess.setExecutable(Boolean.TRUE); - bpmnModel.addProcess(mainProcess); - // 前端模型数据结构。 + Process process = new Process(); + process.setId(processId); + process.setName(processName); + process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么? + bpmnModel.addProcess(process); + + // 前端模型数据结构 // 从 SimpleModel 构建 FlowNode 并添加到 Main Process - buildAndAddBpmnFlowNode(simpleModelNode, mainProcess); + buildAndAddBpmnFlowNode(simpleModelNode, process); // 找到 end event - EndEvent endEvent = (EndEvent) CollUtil.findOne(mainProcess.getFlowElements(), item -> item instanceof EndEvent); + EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent); if (endEvent == null) { // TODO 暂时为了兼容 单独构建 end event 节点. 后面去掉 - endEvent = buildAndAddBpmnEndEvent(mainProcess); + endEvent = buildAndAddBpmnEndEvent(process); } + // 构建并添加节点之间的连线 Sequence Flow - buildAndAddBpmnSequenceFlow(mainProcess, simpleModelNode, endEvent.getId()); + buildAndAddBpmnSequenceFlow(process, simpleModelNode, endEvent.getId()); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); return bpmnModel; @@ -197,20 +202,27 @@ public class SimpleModelUtils { return sequenceFlow; } + // TODO @jason:要不改成 recursionNode 递归节点,然后把 build 名字让出来,专门用于构建各种 Node + // TODO @jason:simpleModelNode 改成 node,mainProcess 改成 process;更符合递归的感觉哈,处理当前节点 private static void buildAndAddBpmnFlowNode(BpmSimpleModelNodeVO simpleModelNode, Process mainProcess) { // 节点为 null 退出 + // TODO @jason:是不是写个 isValidNode 方法:判断是否为有效节点; if (simpleModelNode == null || simpleModelNode.getId() == null) { return; } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); + // TODO @jason:要不抽个 buildNode 方法,然后返回一个 List,之后这个方法 addFlowElement;原因是,让当前这个方法,有主干逻辑;不然现在太长了; switch (nodeType) { case START_EVENT: { + // TODO @jason:每个 nodeType,buildXXX 方法要不更明确,并且去掉 Bpmn; StartEvent startEvent = buildBpmnStartEvent(simpleModelNode); mainProcess.addFlowElement(startEvent); break; } case USER_TASK: { + // TODO @jason:这个,搞成一个 buildUserTask,然后把下面这 2 种节点,搞在一起实现类;这样 buildNode 里面可以更简洁; + // TODO @jason:这里还有个想法,是不是可以所有的都叫 buildXXXNode,然后里面有一些是 bpmn 相关的构建,叫做 buildBpmnUserTask,用于区分; // 获取用户任务的配置 SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(simpleModelNode.getAttributes(), SimpleModelUserTaskConfig.class); UserTask userTask = buildBpmnUserTask(simpleModelNode, userTaskConfig); @@ -259,18 +271,23 @@ public class SimpleModelUtils { } // 如果不是网关类型的接口, 并且chileNode为空退出 + // TODO @jason:建议这个判断去掉,可以更简洁一点;因为往下走;如果不成功,本身也就会结束哈;主要是,这里多了一个这样的判断,增加了理解成本; if (!BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) { return; } - // 如果是网关类型接口. 递归添加条件节点 - if (BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { + // 如果是“条件”节点,则递归处理条件 + if (BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) + && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { + // TODO @jason:可以搞成 stream 写成一行哈; for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) { buildAndAddBpmnFlowNode(node.getChildNode(), mainProcess); } } + // 如果有“子”节点,则递归处理子节点 // chileNode不为空,递归添加子节点 + // TODO @jason:这个,是不是不写判断,直接继续调用;因为本身 buildAndAddBpmnFlowNode 就会最开始判断了哈,就不重复判断了; if (simpleModelNode.getChildNode() != null) { buildAndAddBpmnFlowNode(simpleModelNode.getChildNode(), mainProcess); } @@ -301,30 +318,33 @@ public class SimpleModelUtils { private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { ParallelGateway parallelGateway = new ParallelGateway(); parallelGateway.setId(node.getId()); + // TODO @jason:setName + + // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论 return parallelGateway; } private static ServiceTask buildBpmnServiceTask(BpmSimpleModelNodeVO node) { ServiceTask serviceTask = new ServiceTask(); - serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION); - serviceTask.setImplementation(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); - // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; - // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners + // TODO @jason:建议用 delegateExpression;原因是,直接走 bpmSimpleNodeService.copy(execution) 的话,万一后续抄送改实现,可能比较麻烦。最好是搞个独立的 bean,然后它去调用抄 bpmSimpleNodeService; + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION); + serviceTask.setImplementation(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); + // 添加抄送候选人元素 addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM), serviceTask); + // 添加表单字段权限属性元素 + // TODO @芋艿:这块关注下哈; List> fieldsPermissions = MapUtil.get(node.getAttributes(), - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { - }); + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {}); addFormFieldsPermission(fieldsPermissions, serviceTask); return serviceTask; } - /** * 给节点添加候选人元素 */ @@ -350,6 +370,9 @@ public class SimpleModelUtils { private static InclusiveGateway buildBpmnInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) { InclusiveGateway inclusiveGateway = new InclusiveGateway(); inclusiveGateway.setId(node.getId()); + // TODO @jason:这里是不是 setName 哈; + + // TODO @芋艿 + jason:是不是搞个合并网关;这里微信讨论下,有点奇怪; if (isFork) { Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); // 网关的最后一个条件为 网关的 default sequence flow @@ -375,6 +398,9 @@ public class SimpleModelUtils { userTask.setDueDate(userTaskConfig.getTimeoutHandler().getTimeDuration()); } + // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批 + + // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? // 添加候选人元素 addCandidateElements(userTaskConfig.getCandidateStrategy(), userTaskConfig.getCandidateParam(), userTask); // 添加表单字段权限属性元素 @@ -455,10 +481,14 @@ public class SimpleModelUtils { element.addExtensionElement(extensionElement); } + // ========== 各种 build 节点的方法 ========== + private static StartEvent buildBpmnStartEvent(BpmSimpleModelNodeVO node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); + + // TODO 芋艿 + jason:要不要在开启节点后面,加一个“发起人”任务节点,然后自动审批通过 return startEvent; } @@ -466,6 +496,9 @@ public class SimpleModelUtils { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); endEvent.setName(node.getName()); + + // TODO @芋艿 + jason:要不要加一个终止定义? return endEvent; } + } From 8f31e745dd838189ad4d5a9ff78d32b3ef0dcf85 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 28 May 2024 00:19:28 +0800 Subject: [PATCH 022/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/core/util/SimpleModelUtils.java | 133 +++++++++--------- .../definition/BpmModelServiceImpl.java | 2 +- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 1ce5464f4..87d9097ba 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -19,6 +19,7 @@ import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -49,8 +50,9 @@ public class SimpleModelUtils { */ public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; - // TODO @jason:建议方法名,改成 buildBpmnModel + // TODO-DONE @jason:建议方法名,改成 buildBpmnModel // TODO @yunai:注释需要完善下; + /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) * @@ -59,11 +61,12 @@ public class SimpleModelUtils { * @param simpleModelNode 仿钉钉流程设计模型数据结构 * @return Bpmn Model */ - public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { + public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { BpmnModel bpmnModel = new BpmnModel(); bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace; // TODO 芋艿:后续在 review - // 不加这个 解析 Message 会报 NPE 异常 + // @芋艿 这个 Message 可以去掉 暂时用不上 + // 不加这个 解析 Message 会报 NPE 异常 . Message rejectPostProcessMsg = new Message(); rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME); bpmnModel.addMessage(rejectPostProcessMsg); @@ -76,13 +79,9 @@ public class SimpleModelUtils { // 前端模型数据结构 // 从 SimpleModel 构建 FlowNode 并添加到 Main Process - buildAndAddBpmnFlowNode(simpleModelNode, process); + traverseNodeToBuildFlowNode(simpleModelNode, process); // 找到 end event EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent); - if (endEvent == null) { - // TODO 暂时为了兼容 单独构建 end event 节点. 后面去掉 - endEvent = buildAndAddBpmnEndEvent(process); - } // 构建并添加节点之间的连线 Sequence Flow buildAndAddBpmnSequenceFlow(process, simpleModelNode, endEvent.getId()); @@ -202,95 +201,100 @@ public class SimpleModelUtils { return sequenceFlow; } - // TODO @jason:要不改成 recursionNode 递归节点,然后把 build 名字让出来,专门用于构建各种 Node - // TODO @jason:simpleModelNode 改成 node,mainProcess 改成 process;更符合递归的感觉哈,处理当前节点 - private static void buildAndAddBpmnFlowNode(BpmSimpleModelNodeVO simpleModelNode, Process mainProcess) { - // 节点为 null 退出 - // TODO @jason:是不是写个 isValidNode 方法:判断是否为有效节点; - if (simpleModelNode == null || simpleModelNode.getId() == null) { + // TODO-DONE @jason:要不改成 recursionNode 递归节点,然后把 build 名字让出来,专门用于构建各种 Node + // @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow + // TODO-DONE @jason:node 改成 node,process 改成 process;更符合递归的感觉哈,处理当前节点 + private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { + // 判断是否有效节点 + // TODO-DONE @jason:是不是写个 isValidNode 方法:判断是否为有效节点; + if (!isValidNode(node)) { return; } - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); - // TODO @jason:要不抽个 buildNode 方法,然后返回一个 List,之后这个方法 addFlowElement;原因是,让当前这个方法,有主干逻辑;不然现在太长了; + + // TODO-DONE @jason:要不抽个 buildNode 方法,然后返回一个 List,之后这个方法 addFlowElement;原因是,让当前这个方法,有主干逻辑;不然现在太长了; + List flowElements = buildFlowNode(node, nodeType); + flowElements.forEach(process::addFlowElement); + + // 如果不是网关类型的接口, 并且chileNode为空退出 + // TODO-DONE @jason:建议这个判断去掉,可以更简洁一点;因为往下走;如果不成功,本身也就会结束哈;主要是,这里多了一个这样的判断,增加了理解成本; + // 如果是“分支”节点,则递归处理条件 + if (BpmSimpleModelNodeType.isBranchNode(node.getType()) + && ArrayUtil.isNotEmpty(node.getConditionNodes())) { + // TODO-DONE @jason:可以搞成 stream 写成一行哈 + node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); + } + + // 如果有“子”节点,则递归处理子节点 + // TODO-DONE @jason:这个,是不是不写判断,直接继续调用;因为本身 buildAndAddBpmnFlowNode 就会最开始判断了哈,就不重复判断了; + traverseNodeToBuildFlowNode(node.getChildNode(), process); + } + + private static boolean isValidNode(BpmSimpleModelNodeVO node) { + return node != null && node.getId() != null; + } + + private static List buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) { + List list = new ArrayList<>(); switch (nodeType) { case START_EVENT: { - // TODO @jason:每个 nodeType,buildXXX 方法要不更明确,并且去掉 Bpmn; - StartEvent startEvent = buildBpmnStartEvent(simpleModelNode); - mainProcess.addFlowElement(startEvent); + // TODO-DONE @jason:每个 nodeType,buildXXX 方法要不更明确,并且去掉 Bpmn; + StartEvent startEvent = buildStartEvent(node); + list.add(startEvent); break; } case USER_TASK: { // TODO @jason:这个,搞成一个 buildUserTask,然后把下面这 2 种节点,搞在一起实现类;这样 buildNode 里面可以更简洁; // TODO @jason:这里还有个想法,是不是可以所有的都叫 buildXXXNode,然后里面有一些是 bpmn 相关的构建,叫做 buildBpmnUserTask,用于区分; // 获取用户任务的配置 - SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(simpleModelNode.getAttributes(), SimpleModelUserTaskConfig.class); - UserTask userTask = buildBpmnUserTask(simpleModelNode, userTaskConfig); - mainProcess.addFlowElement(userTask); + SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(node.getAttributes(), SimpleModelUserTaskConfig.class); + UserTask userTask = buildBpmnUserTask(node, userTaskConfig); + list.add(userTask); if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); - mainProcess.addFlowElement(boundaryEvent); + //process.addFlowElement(boundaryEvent); + list.add(boundaryEvent); } break; } case COPY_TASK: { - ServiceTask serviceTask = buildBpmnServiceTask(simpleModelNode); - mainProcess.addFlowElement(serviceTask); + ServiceTask serviceTask = buildServiceTask(node); + list.add(serviceTask); break; } case EXCLUSIVE_GATEWAY: { - ExclusiveGateway exclusiveGateway = buildBpmnExclusiveGateway(simpleModelNode); - mainProcess.addFlowElement(exclusiveGateway); + ExclusiveGateway exclusiveGateway = buildExclusiveGateway(node); + list.add(exclusiveGateway); break; } case PARALLEL_GATEWAY_FORK: case PARALLEL_GATEWAY_JOIN: { - ParallelGateway parallelGateway = buildBpmnParallelGateway(simpleModelNode); - mainProcess.addFlowElement(parallelGateway); + ParallelGateway parallelGateway = buildParallelGateway(node); + list.add(parallelGateway); break; } case INCLUSIVE_GATEWAY_FORK: { - InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.TRUE); - mainProcess.addFlowElement(inclusiveGateway); + InclusiveGateway inclusiveGateway = buildInclusiveGateway(node, Boolean.TRUE); + list.add(inclusiveGateway); break; } case INCLUSIVE_GATEWAY_JOIN: { - InclusiveGateway inclusiveGateway = buildBpmnInclusiveGateway(simpleModelNode, Boolean.FALSE); - mainProcess.addFlowElement(inclusiveGateway); + InclusiveGateway inclusiveGateway = buildInclusiveGateway(node, Boolean.FALSE); + list.add(inclusiveGateway); break; } case END_EVENT: { - EndEvent endEvent = buildBpmnEndEvent(simpleModelNode); - mainProcess.addFlowElement(endEvent); + EndEvent endEvent = buildEndEvent(node); + list.add(endEvent); break; } default: { // TODO 其它节点类型的实现 } } - - // 如果不是网关类型的接口, 并且chileNode为空退出 - // TODO @jason:建议这个判断去掉,可以更简洁一点;因为往下走;如果不成功,本身也就会结束哈;主要是,这里多了一个这样的判断,增加了理解成本; - if (!BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) { - return; - } - - // 如果是“条件”节点,则递归处理条件 - if (BpmSimpleModelNodeType.isBranchNode(simpleModelNode.getType()) - && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { - // TODO @jason:可以搞成 stream 写成一行哈; - for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) { - buildAndAddBpmnFlowNode(node.getChildNode(), mainProcess); - } - } - - // 如果有“子”节点,则递归处理子节点 - // chileNode不为空,递归添加子节点 - // TODO @jason:这个,是不是不写判断,直接继续调用;因为本身 buildAndAddBpmnFlowNode 就会最开始判断了哈,就不重复判断了; - if (simpleModelNode.getChildNode() != null) { - buildAndAddBpmnFlowNode(simpleModelNode.getChildNode(), mainProcess); - } + return list; } private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { @@ -315,7 +319,7 @@ public class SimpleModelUtils { return boundaryEvent; } - private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { + private static ParallelGateway buildParallelGateway(BpmSimpleModelNodeVO node) { ParallelGateway parallelGateway = new ParallelGateway(); parallelGateway.setId(node.getId()); // TODO @jason:setName @@ -324,7 +328,7 @@ public class SimpleModelUtils { return parallelGateway; } - private static ServiceTask buildBpmnServiceTask(BpmSimpleModelNodeVO node) { + private static ServiceTask buildServiceTask(BpmSimpleModelNodeVO node) { ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); @@ -340,7 +344,8 @@ public class SimpleModelUtils { // 添加表单字段权限属性元素 // TODO @芋艿:这块关注下哈; List> fieldsPermissions = MapUtil.get(node.getAttributes(), - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() {}); + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { + }); addFormFieldsPermission(fieldsPermissions, serviceTask); return serviceTask; } @@ -354,7 +359,7 @@ public class SimpleModelUtils { addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); } - private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { + private static ExclusiveGateway buildExclusiveGateway(BpmSimpleModelNodeVO node) { Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); @@ -367,7 +372,7 @@ public class SimpleModelUtils { return exclusiveGateway; } - private static InclusiveGateway buildBpmnInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) { + private static InclusiveGateway buildInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) { InclusiveGateway inclusiveGateway = new InclusiveGateway(); inclusiveGateway.setId(node.getId()); // TODO @jason:这里是不是 setName 哈; @@ -483,7 +488,7 @@ public class SimpleModelUtils { // ========== 各种 build 节点的方法 ========== - private static StartEvent buildBpmnStartEvent(BpmSimpleModelNodeVO node) { + private static StartEvent buildStartEvent(BpmSimpleModelNodeVO node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); @@ -492,7 +497,7 @@ public class SimpleModelUtils { return startEvent; } - private static EndEvent buildBpmnEndEvent(BpmSimpleModelNodeVO node) { + private static EndEvent buildEndEvent(BpmSimpleModelNodeVO node) { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); endEvent.setName(node.getName()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 349b4200d..4762fac4d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -231,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService { throw exception(MODEL_NOT_EXISTS); } // 1.2 JSON 转换成 bpmnModel - BpmnModel bpmnModel = SimpleModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); + BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); // 2.1 保存 Bpmn XML saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); // 2.2 保存 JSON 数据 From 4bd399cd3261597192963141cd9a885287efad29 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 28 May 2024 08:46:58 +0800 Subject: [PATCH 023/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 23 ++-- .../flowable/core/util/SimpleModelUtils.java | 122 ++++++++++-------- 2 files changed, 77 insertions(+), 68 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 12cda1d3d..8426d4482 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -18,17 +18,18 @@ import java.util.Objects; public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; - START_EVENT(0, "开始节点"), - END_EVENT(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; + // @芋艿 感觉还是用 START_NODE . END_NODE 比较好. + START_NODE(0, "开始节点"), + END_NODE(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; - USER_TASK(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 - COPY_TASK(2, "抄送人节点"), + APPROVE_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 + COPY_NODE(2, "抄送人节点"), - EXCLUSIVE_GATEWAY(4, "排他网关"), // TODO @jason:是不是改成叫 条件分支? - PARALLEL_GATEWAY_FORK(5, "并行网关分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 - PARALLEL_GATEWAY_JOIN(6, "并行网关聚合节点"), - INCLUSIVE_GATEWAY_FORK(7, "包容网关分叉节点"), - INCLUSIVE_GATEWAY_JOIN(8, "包容网关聚合节点"), + CONDITION_BRANCH_NODE(4, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? + PARALLEL_BRANCH_FORK_NODE(5, "并行分支分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 + PARALLEL_BRANCH_JOIN_NODE(6, "并行分支聚合节点"), + INCLUSIVE_BRANCH_FORK_NODE(7, "包容网关分叉节点"), + INCLUSIVE_BRANCH_JOIN_NODE(8, "包容网关聚合节点"), ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); @@ -42,8 +43,8 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { * @param type 节点类型 */ public static boolean isBranchNode(Integer type) { - return Objects.equals(EXCLUSIVE_GATEWAY.getType(), type) || Objects.equals(PARALLEL_GATEWAY_FORK.getType(), type) - || Objects.equals(INCLUSIVE_GATEWAY_FORK.getType(), type) ; + return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) || Objects.equals(PARALLEL_BRANCH_FORK_NODE.getType(), type) + || Objects.equals(INCLUSIVE_BRANCH_FORK_NODE.getType(), type) ; } public static BpmSimpleModelNodeType valueOf(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 87d9097ba..8fa8da01e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_NODE; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; @@ -63,10 +63,10 @@ public class SimpleModelUtils { */ public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { BpmnModel bpmnModel = new BpmnModel(); + // 不加这个 解析 Message 会报 NPE 异常 . bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace; // TODO 芋艿:后续在 review // @芋艿 这个 Message 可以去掉 暂时用不上 - // 不加这个 解析 Message 会报 NPE 异常 . Message rejectPostProcessMsg = new Message(); rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME); bpmnModel.addMessage(rejectPostProcessMsg); @@ -92,7 +92,7 @@ public class SimpleModelUtils { private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) { // 节点为 null 或者 为END_EVENT 退出 - if (node == null || node.getId() == null || END_EVENT.getType().equals(node.getType())) { + if (node == null || node.getId() == null || END_NODE.getType().equals(node.getType())) { return; } BpmSimpleModelNodeVO childNode = node.getChildNode(); @@ -105,20 +105,20 @@ public class SimpleModelUtils { BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); switch (nodeType) { - case START_EVENT: - case USER_TASK: - case COPY_TASK: - case PARALLEL_GATEWAY_JOIN: - case INCLUSIVE_GATEWAY_JOIN: { + case START_NODE: + case APPROVE_NODE: + case COPY_NODE: + case PARALLEL_BRANCH_JOIN_NODE: + case INCLUSIVE_BRANCH_JOIN_NODE: { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); mainProcess.addFlowElement(sequenceFlow); // 递归调用后续节点 buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); break; } - case PARALLEL_GATEWAY_FORK: - case EXCLUSIVE_GATEWAY: - case INCLUSIVE_GATEWAY_FORK: { + case PARALLEL_BRANCH_FORK_NODE: + case CONDITION_BRANCH_NODE: + case INCLUSIVE_BRANCH_FORK_NODE: { String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? targetId : childNode.getId(); List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); @@ -238,55 +238,50 @@ public class SimpleModelUtils { private static List buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) { List list = new ArrayList<>(); switch (nodeType) { - case START_EVENT: { + case START_NODE: { // TODO-DONE @jason:每个 nodeType,buildXXX 方法要不更明确,并且去掉 Bpmn; - StartEvent startEvent = buildStartEvent(node); + // @芋艿 改成 convert 是不是好理解一点 + StartEvent startEvent = convertStartNode(node); list.add(startEvent); break; } - case USER_TASK: { - // TODO @jason:这个,搞成一个 buildUserTask,然后把下面这 2 种节点,搞在一起实现类;这样 buildNode 里面可以更简洁; - // TODO @jason:这里还有个想法,是不是可以所有的都叫 buildXXXNode,然后里面有一些是 bpmn 相关的构建,叫做 buildBpmnUserTask,用于区分; - // 获取用户任务的配置 - SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(node.getAttributes(), SimpleModelUserTaskConfig.class); - UserTask userTask = buildBpmnUserTask(node, userTaskConfig); - list.add(userTask); - if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { - // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 - BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); - //process.addFlowElement(boundaryEvent); - list.add(boundaryEvent); - } + case APPROVE_NODE: { + // TODO-DONE @jason:这个,搞成一个 buildUserTask,然后把下面这 2 种节点,搞在一起实现类;这样 buildNode 里面可以更简洁; + // TODO-DONE @jason:这里还有个想法,是不是可以所有的都叫 buildXXXNode,然后里面有一些是 bpmn 相关的构建,叫做 buildBpmnUserTask,用于区分; + // @芋艿 改成 convertXXXNode, , 方面里面使用 buildBpmnXXXNode. 是否更好理解 + // 转换审批节点 + List flowElements = convertApproveNode(node); + list.addAll(flowElements); break; } - case COPY_TASK: { - ServiceTask serviceTask = buildServiceTask(node); + case COPY_NODE: { + ServiceTask serviceTask = convertCopyNode(node); list.add(serviceTask); break; } - case EXCLUSIVE_GATEWAY: { - ExclusiveGateway exclusiveGateway = buildExclusiveGateway(node); + case CONDITION_BRANCH_NODE: { + ExclusiveGateway exclusiveGateway = convertConditionBranchNode(node); list.add(exclusiveGateway); break; } - case PARALLEL_GATEWAY_FORK: - case PARALLEL_GATEWAY_JOIN: { - ParallelGateway parallelGateway = buildParallelGateway(node); + case PARALLEL_BRANCH_FORK_NODE: + case PARALLEL_BRANCH_JOIN_NODE: { + ParallelGateway parallelGateway = convertParallelBranchNode(node); list.add(parallelGateway); break; } - case INCLUSIVE_GATEWAY_FORK: { - InclusiveGateway inclusiveGateway = buildInclusiveGateway(node, Boolean.TRUE); + case INCLUSIVE_BRANCH_FORK_NODE: { + InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.TRUE); list.add(inclusiveGateway); break; } - case INCLUSIVE_GATEWAY_JOIN: { - InclusiveGateway inclusiveGateway = buildInclusiveGateway(node, Boolean.FALSE); + case INCLUSIVE_BRANCH_JOIN_NODE: { + InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.FALSE); list.add(inclusiveGateway); break; } - case END_EVENT: { - EndEvent endEvent = buildEndEvent(node); + case END_NODE: { + EndEvent endEvent = convertEndNode(node); list.add(endEvent); break; } @@ -297,6 +292,19 @@ public class SimpleModelUtils { return list; } + private static List convertApproveNode(BpmSimpleModelNodeVO node) { + List flowElements = new ArrayList<>(); + SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(node.getAttributes(), SimpleModelUserTaskConfig.class); + UserTask userTask = buildBpmnUserTask(node, userTaskConfig); + flowElements.add(userTask); + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { + // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 + BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); + flowElements.add(boundaryEvent); + } + return flowElements; + } + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); @@ -319,16 +327,17 @@ public class SimpleModelUtils { return boundaryEvent; } - private static ParallelGateway buildParallelGateway(BpmSimpleModelNodeVO node) { + private static ParallelGateway convertParallelBranchNode(BpmSimpleModelNodeVO node) { ParallelGateway parallelGateway = new ParallelGateway(); parallelGateway.setId(node.getId()); // TODO @jason:setName // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论 + // @芋艿 貌似并行网关没有条件的 return parallelGateway; } - private static ServiceTask buildServiceTask(BpmSimpleModelNodeVO node) { + private static ServiceTask convertCopyNode(BpmSimpleModelNodeVO node) { ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); @@ -359,8 +368,8 @@ public class SimpleModelUtils { addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); } - private static ExclusiveGateway buildExclusiveGateway(BpmSimpleModelNodeVO node) { - Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); + private static ExclusiveGateway convertConditionBranchNode(BpmSimpleModelNodeVO node) { + Assert.notEmpty(node.getConditionNodes(), "条件分支节点不能为空"); ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); // 寻找默认的序列流 @@ -372,28 +381,25 @@ public class SimpleModelUtils { return exclusiveGateway; } - private static InclusiveGateway buildInclusiveGateway(BpmSimpleModelNodeVO node, Boolean isFork) { + private static InclusiveGateway convertInclusiveBranchNode(BpmSimpleModelNodeVO node, Boolean isFork) { InclusiveGateway inclusiveGateway = new InclusiveGateway(); inclusiveGateway.setId(node.getId()); // TODO @jason:这里是不是 setName 哈; // TODO @芋艿 + jason:是不是搞个合并网关;这里微信讨论下,有点奇怪; + // @芋艿 isFork 为 false 就是合并网关。由前端传入。这个前端暂时还未实现 if (isFork) { - Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); - // 网关的最后一个条件为 网关的 default sequence flow - inclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); + Assert.notEmpty(node.getConditionNodes(), "条件节点不能为空"); + // 寻找默认的序列流 + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), + item -> BooleanUtil.isTrue(MapUtil.getBool(item.getAttributes(), DEFAULT_FLOW_ATTRIBUTE))); + if (defaultSeqFlow != null) { + inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); + } } return inclusiveGateway; } - private static EndEvent buildAndAddBpmnEndEvent(Process mainProcess) { - EndEvent endEvent = new EndEvent(); - endEvent.setId(BpmnModelConstants.END_EVENT_ID); - endEvent.setName("结束"); - mainProcess.addFlowElement(endEvent); - return endEvent; - } - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); @@ -488,16 +494,18 @@ public class SimpleModelUtils { // ========== 各种 build 节点的方法 ========== - private static StartEvent buildStartEvent(BpmSimpleModelNodeVO node) { + private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); // TODO 芋艿 + jason:要不要在开启节点后面,加一个“发起人”任务节点,然后自动审批通过 + // @芋艿 这个是不是由前端来实现。 默认开始节点后面跟一个 “发起人”的审批节点(审批人是发起人自己)。 + // 我看有些平台这个审批节点允许删除,有些不允许。由用户决定 return startEvent; } - private static EndEvent buildEndEvent(BpmSimpleModelNodeVO node) { + private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); endEvent.setName(node.getName()); From d9ca52a478a4c56344b042914d3dfa548fdd29d3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 28 May 2024 20:04:21 +0800 Subject: [PATCH 024/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20simple=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E3=80=81seq=20=E8=BF=9E=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 4 +++- .../vo/model/simple/BpmSimpleModelNodeVO.java | 19 ++++++++++++++----- .../simple/BpmSimpleModelUpdateReqVO.java | 2 ++ .../flowable/core/util/SimpleModelUtils.java | 15 ++++++++++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 8426d4482..0f59baf5b 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -30,6 +30,7 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { PARALLEL_BRANCH_JOIN_NODE(6, "并行分支聚合节点"), INCLUSIVE_BRANCH_FORK_NODE(7, "包容网关分叉节点"), INCLUSIVE_BRANCH_JOIN_NODE(8, "包容网关聚合节点"), + // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~ ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); @@ -43,7 +44,8 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { * @param type 节点类型 */ public static boolean isBranchNode(Integer type) { - return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) || Objects.equals(PARALLEL_BRANCH_FORK_NODE.getType(), type) + return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) + || Objects.equals(PARALLEL_BRANCH_FORK_NODE.getType(), type) || Objects.equals(INCLUSIVE_BRANCH_FORK_NODE.getType(), type) ; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 4bc5ac85e..5c2148707 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -28,16 +28,25 @@ public class BpmSimpleModelNodeVO { @Schema(description = "模型节点名称", example = "领导审批") private String name; + // TODO @jason:要不改成 placeholder 和一般 Element-Plus 组件一致。占位符,用于展示。 @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") private String showText; - @Schema(description = "孩子节点") - private BpmSimpleModelNodeVO childNode; + @Schema(description = "子节点") + private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个 - @Schema(description = "网关节点的条件节点") - private List conditionNodes; + @Schema(description = "条件节点") + private List conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用 @Schema(description = "节点的属性") - private Map attributes; + private Map attributes; // TODO @jason:建议是字段分拆下;类似说: + // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 + // Integer approveMethod; 审批方式;仅审批节点会使用 + // TODO @芋艿:审批人的选择; + // TODO @芋艿:没有人的策略? + // TODO @芋艿:审批拒绝的策略? + // TODO @芋艿:配置的可操作列表? + // TODO @芋艿:超时配置;要支持指定时间点、指定时间间隔; + // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java index 20f3bf1b1..33d6a4248 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; +// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下; @Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") @Data public class BpmSimpleModelUpdateReqVO { @@ -14,6 +15,7 @@ public class BpmSimpleModelUpdateReqVO { @NotEmpty(message = "流程模型编号不能为空") private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 + // TODO @jason:simpleModel 要不? @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "仿钉钉流程设计模型对象不能为空") @Valid diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 8fa8da01e..210c1eb70 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -92,11 +92,13 @@ public class SimpleModelUtils { private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) { // 节点为 null 或者 为END_EVENT 退出 + // TODO @jason:isValidNode;然后把 END_NODE 是不是拿到 switch (nodeType) { 那 return 哈?这样出口更统一一点? if (node == null || node.getId() == null || END_NODE.getType().equals(node.getType())) { return; } BpmSimpleModelNodeVO childNode = node.getChildNode(); // 如果是网关分支节点. 后续节点可能为 null。但不是 END_EVENT 节点 + // TODO @芋艿:这个要不要挪到 START_NODE - INCLUSIVE_BRANCH_JOIN_NODE 待定;感觉 switch 那最终是分三个情况;branch、子节点、结束了;(每种情况的注释,需要写的更完整) if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null, null); mainProcess.addFlowElement(sequenceFlow); @@ -104,6 +106,7 @@ public class SimpleModelUtils { } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); + // TODO @jason:下面的 PARALLEL_BRANCH_FORK_NODE、CONDITION_BRANCH_NODE、INCLUSIVE_BRANCH_FORK_NODE 是不是就是 isBranchNode?如果是的话,貌似不用 swtich,而是 if else 分类处理呢。 switch (nodeType) { case START_NODE: case APPROVE_NODE: @@ -119,17 +122,24 @@ public class SimpleModelUtils { case PARALLEL_BRANCH_FORK_NODE: case CONDITION_BRANCH_NODE: case INCLUSIVE_BRANCH_FORK_NODE: { + // TODO @jason:这里 sequenceFlowTargetId 不建议做这样的 default。万一可能有 bug 哈;直接弄到对应的 136- 146 会更安全一点。 String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? targetId : childNode.getId(); List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); for (BpmSimpleModelNodeVO item : conditionNodes) { + // 构建表达式 + // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 String conditionExpression = buildConditionExpression(item); + BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); + // TODO @jason:isValidNode if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { + // TODO @jason:会存在 item.name 未空的情况么?这个时候,要不要兜底处理拼接 SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), item.getId(), item.getName(), conditionExpression); mainProcess.addFlowElement(sequenceFlow); // 递归调用后续节点 + // TODO @jason:最好也有个例子,嘿嘿;S4 buildAndAddBpmnSequenceFlow(mainProcess, nextNodeOnCondition, sequenceFlowTargetId); } else { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId, @@ -137,7 +147,7 @@ public class SimpleModelUtils { mainProcess.addFlowElement(sequenceFlow); } } - // 递归调用后续节点 + // 递归调用后续节点 TODO @jason:最好有个例子哈 buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); break; } @@ -188,6 +198,9 @@ public class SimpleModelUtils { } private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) { + // TODO @jason:最好断言下,sourceId、targetId 必须存在! + // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? + // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(conditionExpression)) { sequenceFlow.setConditionExpression(conditionExpression); From 65a09182e714f25f8696300c14a06263f042ae4b Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 29 May 2024 09:48:28 +0800 Subject: [PATCH 025/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=8A=82=E7=82=B9=E8=BF=9E=E7=BA=BF=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/core/util/SimpleModelUtils.java | 163 ++++++++++++------ 1 file changed, 107 insertions(+), 56 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 210c1eb70..a5a25ca04 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_NODE; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; @@ -84,78 +84,128 @@ public class SimpleModelUtils { EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent); // 构建并添加节点之间的连线 Sequence Flow - buildAndAddBpmnSequenceFlow(process, simpleModelNode, endEvent.getId()); + traverseNodeToBuildSequenceFlow(process, simpleModelNode, endEvent.getId()); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); return bpmnModel; } - private static void buildAndAddBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String targetId) { - // 节点为 null 或者 为END_EVENT 退出 - // TODO @jason:isValidNode;然后把 END_NODE 是不是拿到 switch (nodeType) { 那 return 哈?这样出口更统一一点? - if (node == null || node.getId() == null || END_NODE.getType().equals(node.getType())) { + private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { + // 1.无效节点返回 + if (!isValidNode(node)) { return; } - BpmSimpleModelNodeVO childNode = node.getChildNode(); + // 如果是网关分支节点. 后续节点可能为 null。但不是 END_EVENT 节点 - // TODO @芋艿:这个要不要挪到 START_NODE - INCLUSIVE_BRANCH_JOIN_NODE 待定;感觉 switch 那最终是分三个情况;branch、子节点、结束了;(每种情况的注释,需要写的更完整) - if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) { - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetId, null, null, null); - mainProcess.addFlowElement(sequenceFlow); - return; - } + // TODO-DONE @芋艿:这个要不要挪到 START_NODE - INCLUSIVE_BRANCH_JOIN_NODE 待定;感觉 switch 那最终是分三个情况;branch、子节点、结束了;(每种情况的注释,需要写的更完整) +// if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) { +// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); +// process.addFlowElement(sequenceFlow); +// return; +// } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); - // TODO @jason:下面的 PARALLEL_BRANCH_FORK_NODE、CONDITION_BRANCH_NODE、INCLUSIVE_BRANCH_FORK_NODE 是不是就是 isBranchNode?如果是的话,貌似不用 swtich,而是 if else 分类处理呢。 - switch (nodeType) { - case START_NODE: - case APPROVE_NODE: - case COPY_NODE: - case PARALLEL_BRANCH_JOIN_NODE: - case INCLUSIVE_BRANCH_JOIN_NODE: { + BpmSimpleModelNodeVO childNode = node.getChildNode(); + // 2.1 普通节点 + if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { + // 2.1.1 结束节点退出递归 + if (nodeType == END_NODE) { + return; + } + if (!isValidNode(childNode)) { + // 2.1.2 普通节点且无孩子节点。分两种情况 + // a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线。 + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); + process.addFlowElement(sequenceFlow); + } else { + // 2.1.3 普通节点且有孩子节点。建立连线 SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); - mainProcess.addFlowElement(sequenceFlow); + process.addFlowElement(sequenceFlow); // 递归调用后续节点 - buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); - break; + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } - case PARALLEL_BRANCH_FORK_NODE: - case CONDITION_BRANCH_NODE: - case INCLUSIVE_BRANCH_FORK_NODE: { - // TODO @jason:这里 sequenceFlowTargetId 不建议做这样的 default。万一可能有 bug 哈;直接弄到对应的 136- 146 会更安全一点。 - String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? targetId : childNode.getId(); - List conditionNodes = node.getConditionNodes(); - Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); - for (BpmSimpleModelNodeVO item : conditionNodes) { - // 构建表达式 - // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 - String conditionExpression = buildConditionExpression(item); - - BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); - // TODO @jason:isValidNode - if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { - // TODO @jason:会存在 item.name 未空的情况么?这个时候,要不要兜底处理拼接 - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), - item.getId(), item.getName(), conditionExpression); - mainProcess.addFlowElement(sequenceFlow); - // 递归调用后续节点 - // TODO @jason:最好也有个例子,嘿嘿;S4 - buildAndAddBpmnSequenceFlow(mainProcess, nextNodeOnCondition, sequenceFlowTargetId); - } else { - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId, - item.getId(), item.getName(), conditionExpression); - mainProcess.addFlowElement(sequenceFlow); - } + } else { + // 2.2 分支节点 + List conditionNodes = node.getConditionNodes(); + Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); + // 4.1 分支节点,遍历分支节点. 如下情况: + // 分支1、A->B->C->D->E 和 分支2、A->D->E。 A为分支节点, D为A孩子节点 + // 分支终点节点, 1. 分支节点有孩子节点时为孩子节点 2. 当分支节点孩子为无效节点时。分支嵌套时并且为分支最后一个节点 + String branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId ; + for (BpmSimpleModelNodeVO item : conditionNodes) { + // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 + // 构建表达式 + String conditionExpression = buildConditionExpression(item); + BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); + // 4.2 分支有后续节点, 分支1: A->B->C->D + if (isValidNode(nextNodeOnCondition)) { + // 4.2.1 建立 A->B + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), + item.getId(), item.getName(), conditionExpression); + process.addFlowElement(sequenceFlow); + // 4.2.2 递归调用后续节点连线。 建立 B->C->D 的连线 + traverseNodeToBuildSequenceFlow(process, nextNodeOnCondition, branchEndNodeId); + } else { + // 4.3 分支无后续节点 建立 A->D + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId, + item.getId(), item.getName(), conditionExpression); + process.addFlowElement(sequenceFlow); } - // 递归调用后续节点 TODO @jason:最好有个例子哈 - buildAndAddBpmnSequenceFlow(mainProcess, childNode, targetId); - break; - } - default: { - // TODO 其它节点类型的实现 } + // 递归调用后续节点 继续递归建立 D->E 的连线 + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } + // TODO @jason:下面的 PARALLEL_BRANCH_FORK_NODE、CONDITION_BRANCH_NODE、INCLUSIVE_BRANCH_FORK_NODE 是不是就是 isBranchNode?如果是的话,貌似不用 swtich,而是 if else 分类处理呢。 +// switch (nodeType) { +// case START_NODE: +// case APPROVE_NODE: +// case COPY_NODE: +// case PARALLEL_BRANCH_JOIN_NODE: +// case INCLUSIVE_BRANCH_JOIN_NODE: { +// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); +// process.addFlowElement(sequenceFlow); +// // 递归调用后续节点 +// buildAndAddBpmnSequenceFlow(process, childNode, targetNodeId); +// break; +// } +// case PARALLEL_BRANCH_FORK_NODE: +// case CONDITION_BRANCH_NODE: +// case INCLUSIVE_BRANCH_FORK_NODE: { +// // TODO @jason:这里 sequenceFlowTargetId 不建议做这样的 default。万一可能有 bug 哈;直接弄到对应的 136- 146 会更安全一点。 +// String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? targetNodeId : childNode.getId(); +// List conditionNodes = node.getConditionNodes(); +// Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); +// for (BpmSimpleModelNodeVO item : conditionNodes) { +// // 构建表达式 +// // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 +// String conditionExpression = buildConditionExpression(item); +// +// BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); +// // TODO @jason:isValidNode +// if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { +// // TODO @jason:会存在 item.name 未空的情况么?这个时候,要不要兜底处理拼接 +// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), +// item.getId(), item.getName(), conditionExpression); +// process.addFlowElement(sequenceFlow); +// // 递归调用后续节点 +// // TODO @jason:最好也有个例子,嘿嘿;S4 +// buildAndAddBpmnSequenceFlow(process, nextNodeOnCondition, sequenceFlowTargetId); +// } else { +// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId, +// item.getId(), item.getName(), conditionExpression); +// process.addFlowElement(sequenceFlow); +// } +// } +// // 递归调用后续节点 TODO @jason:最好有个例子哈 +// buildAndAddBpmnSequenceFlow(process, childNode, targetNodeId); +// break; +// } +// default: { +// // TODO 其它节点类型的实现 +// } +// } + } /** @@ -283,6 +333,7 @@ public class SimpleModelUtils { list.add(parallelGateway); break; } + case INCLUSIVE_BRANCH_FORK_NODE: { InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.TRUE); list.add(inclusiveGateway); From 8a3b6c3eb94ccc8ca1e08025be0157148275c4be Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 31 May 2024 08:57:08 +0800 Subject: [PATCH 026/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/vo/model/simple/BpmSimpleModelNodeVO.java | 3 ++- .../vo/model/simple/BpmSimpleModelUpdateReqVO.java | 3 +-- .../bpm/framework/flowable/core/util/SimpleModelUtils.java | 7 ++++--- .../module/bpm/service/definition/BpmModelServiceImpl.java | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 5c2148707..a32449c21 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -28,7 +28,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "模型节点名称", example = "领导审批") private String name; - // TODO @jason:要不改成 placeholder 和一般 Element-Plus 组件一致。占位符,用于展示。 + // TODO @jason:要不改成 placeholder 和一般 Element-Plus 组件一致。占位符,用于展示。@芋艿。这个不是 placeholder 占位符的含义。节点配置后。节点展示的内容,不知道取什么名字好??? @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") private String showText; @@ -42,6 +42,7 @@ public class BpmSimpleModelNodeVO { private Map attributes; // TODO @jason:建议是字段分拆下;类似说: // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 + // TODO @jason 后面和前端一起调整一下 // TODO @芋艿:审批人的选择; // TODO @芋艿:没有人的策略? // TODO @芋艿:审批拒绝的策略? diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java index 33d6a4248..fc72d3f67 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -15,10 +15,9 @@ public class BpmSimpleModelUpdateReqVO { @NotEmpty(message = "流程模型编号不能为空") private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 - // TODO @jason:simpleModel 要不? @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "仿钉钉流程设计模型对象不能为空") @Valid - private BpmSimpleModelNodeVO simpleModelBody; + private BpmSimpleModelNodeVO simpleModel; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index a5a25ca04..dc70962d2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -248,9 +248,10 @@ public class SimpleModelUtils { } private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) { - // TODO @jason:最好断言下,sourceId、targetId 必须存在! - // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? - // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? + Assert.notEmpty(sourceId, "sourceId 不能为空"); + Assert.notEmpty(targetId, "targetId 不能为空"); + // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? @芋艿: 貌似不需要,Flowable 会默认生成 + // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧 SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(conditionExpression)) { sequenceFlow.setConditionExpression(conditionExpression); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 4762fac4d..ef421a2e3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -231,11 +231,11 @@ public class BpmModelServiceImpl implements BpmModelService { throw exception(MODEL_NOT_EXISTS); } // 1.2 JSON 转换成 bpmnModel - BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); + BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); // 2.1 保存 Bpmn XML saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); // 2.2 保存 JSON 数据 - saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModelBody())); + saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel())); } From d9a2849ccec0613bf55365be1048b11cd55518c1 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 2 Jun 2024 18:11:10 +0800 Subject: [PATCH 027/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=82=E6=96=B0=E5=A2=9E=E5=B9=B6=E8=A1=8C=E5=88=86?= =?UTF-8?q?=E6=94=AF=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 8 ++- .../flowable/core/util/SimpleModelUtils.java | 68 +++++++++++++------ 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 0f59baf5b..85067269c 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -25,12 +25,14 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { APPROVE_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 COPY_NODE(2, "抄送人节点"), + CONDITION_NODE(3, "条件节点"), // 用于构建流转条件的表达式 CONDITION_BRANCH_NODE(4, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? - PARALLEL_BRANCH_FORK_NODE(5, "并行分支分叉节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 - PARALLEL_BRANCH_JOIN_NODE(6, "并行分支聚合节点"), + PARALLEL_BRANCH_NODE(5, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 +// PARALLEL_BRANCH_JOIN_NODE(6, "并行分支聚合节点"), INCLUSIVE_BRANCH_FORK_NODE(7, "包容网关分叉节点"), INCLUSIVE_BRANCH_JOIN_NODE(8, "包容网关聚合节点"), // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~ + // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。 ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); @@ -45,7 +47,7 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { */ public static boolean isBranchNode(Integer type) { return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) - || Objects.equals(PARALLEL_BRANCH_FORK_NODE.getType(), type) + || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) || Objects.equals(INCLUSIVE_BRANCH_FORK_NODE.getType(), type) ; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index dc70962d2..8901f63e7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -38,6 +38,11 @@ import static org.flowable.bpmn.constants.BpmnXMLConstants.*; */ public class SimpleModelUtils { + /** + * 聚合网关节点 Id 后缀 + */ + public static final String JOIN_GATE_WAY_NODE_ID_SUFFIX = "_join"; + public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}"; /** @@ -105,20 +110,20 @@ public class SimpleModelUtils { // } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); + + if (nodeType == END_NODE) { + return; + } BpmSimpleModelNodeVO childNode = node.getChildNode(); // 2.1 普通节点 if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { - // 2.1.1 结束节点退出递归 - if (nodeType == END_NODE) { - return; - } if (!isValidNode(childNode)) { - // 2.1.2 普通节点且无孩子节点。分两种情况 + // 2.1.1 普通节点且无孩子节点。分两种情况 // a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线。 SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); process.addFlowElement(sequenceFlow); } else { - // 2.1.3 普通节点且有孩子节点。建立连线 + // 2.1.2 普通节点且有孩子节点。建立连线 SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); process.addFlowElement(sequenceFlow); // 递归调用后续节点 @@ -128,31 +133,48 @@ public class SimpleModelUtils { // 2.2 分支节点 List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); - // 4.1 分支节点,遍历分支节点. 如下情况: + // 分支终点节点 Id + String branchEndNodeId = null; + if (nodeType == CONDITION_BRANCH_NODE) { // 条件分支 + // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点Id + branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; + } else if (nodeType == PARALLEL_BRANCH_NODE) { // 并行分支 + // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 + branchEndNodeId = node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX; + } + // TODO 包容网关待实现 + Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); + // 3.1 遍历分支节点. 如下情况: // 分支1、A->B->C->D->E 和 分支2、A->D->E。 A为分支节点, D为A孩子节点 - // 分支终点节点, 1. 分支节点有孩子节点时为孩子节点 2. 当分支节点孩子为无效节点时。分支嵌套时并且为分支最后一个节点 - String branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId ; for (BpmSimpleModelNodeVO item : conditionNodes) { // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 - // 构建表达式 + // @芋艿 这个是啥意思。 这里的 item 的节点类型为 BpmSimpleModelNodeType.CONDITION_NODE 类型,没有对应的 bpmn 的节点。 仅仅用于构建条件表达式。 + Assert.isTrue(Objects.equals(item.getType(), CONDITION_NODE.getType()), "条件节点类型不符合"); + // 构建表达式,可以为空. 并行分支为空 String conditionExpression = buildConditionExpression(item); BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); - // 4.2 分支有后续节点, 分支1: A->B->C->D + // 3.2 分支有后续节点, 分支1: A->B->C->D if (isValidNode(nextNodeOnCondition)) { - // 4.2.1 建立 A->B + // 3.2.1 建立 A->B SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), item.getId(), item.getName(), conditionExpression); process.addFlowElement(sequenceFlow); - // 4.2.2 递归调用后续节点连线。 建立 B->C->D 的连线 + // 3.2.2 递归调用后续节点连线。 建立 B->C->D 的连线 traverseNodeToBuildSequenceFlow(process, nextNodeOnCondition, branchEndNodeId); } else { - // 4.3 分支无后续节点 建立 A->D + // 3.3 分支无后续节点 建立 A->D SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId, item.getId(), item.getName(), conditionExpression); process.addFlowElement(sequenceFlow); } } - // 递归调用后续节点 继续递归建立 D->E 的连线 + // 如果是并行分支。由于是程序创建的聚合网关。需要手工创建聚合网关和下一个节点的连线 + if (nodeType == PARALLEL_BRANCH_NODE) { + String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId, null, null, null); + process.addFlowElement(sequenceFlow); + } + // 4.递归调用后续节点 继续递归建立 D->E 的连线 traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } @@ -328,10 +350,9 @@ public class SimpleModelUtils { list.add(exclusiveGateway); break; } - case PARALLEL_BRANCH_FORK_NODE: - case PARALLEL_BRANCH_JOIN_NODE: { - ParallelGateway parallelGateway = convertParallelBranchNode(node); - list.add(parallelGateway); + case PARALLEL_BRANCH_NODE: { + List parallelGateways = convertParallelBranchNode(node); + list.addAll(parallelGateways); break; } @@ -392,14 +413,17 @@ public class SimpleModelUtils { return boundaryEvent; } - private static ParallelGateway convertParallelBranchNode(BpmSimpleModelNodeVO node) { + private static List convertParallelBranchNode(BpmSimpleModelNodeVO node) { ParallelGateway parallelGateway = new ParallelGateway(); parallelGateway.setId(node.getId()); // TODO @jason:setName // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论 - // @芋艿 貌似并行网关没有条件的 - return parallelGateway; + // @芋艿 感觉聚合网关(合并网关)还是从前端传过来好理解一点。 + // 并行聚合网关 + ParallelGateway joinParallelGateway = new ParallelGateway(); + joinParallelGateway.setId(node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX); + return CollUtil.newArrayList(parallelGateway, joinParallelGateway); } private static ServiceTask convertCopyNode(BpmSimpleModelNodeVO node) { From c87bea5a7275779007f2959a247d6d6353414d99 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 3 Jun 2024 12:50:07 +0800 Subject: [PATCH 028/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20simple=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E3=80=81seq=20=E8=BF=9E=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/simple/BpmSimpleModelNodeVO.java | 1 + .../flowable/core/util/SimpleModelUtils.java | 69 ++----------------- .../definition/BpmModelServiceImpl.java | 1 - 3 files changed, 7 insertions(+), 64 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index a32449c21..e13253e99 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -29,6 +29,7 @@ public class BpmSimpleModelNodeVO { private String name; // TODO @jason:要不改成 placeholder 和一般 Element-Plus 组件一致。占位符,用于展示。@芋艿。这个不是 placeholder 占位符的含义。节点配置后。节点展示的内容,不知道取什么名字好??? + // TODO @jason:【回复】占位文本(showText)是指当一个文本框没有被 focus 的时候显示的是提示文字,当他被点击之后就显示空白。。。虽然不是完全精准,但是 placeholder 相对正式点~ @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") private String showText; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 8901f63e7..dcb829c16 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -95,27 +95,21 @@ public class SimpleModelUtils { return bpmnModel; } + // TODO @芋艿:在优化下这个注释 private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { - // 1.无效节点返回 + // 1.1 无效节点返回 if (!isValidNode(node)) { return; } - - // 如果是网关分支节点. 后续节点可能为 null。但不是 END_EVENT 节点 - // TODO-DONE @芋艿:这个要不要挪到 START_NODE - INCLUSIVE_BRANCH_JOIN_NODE 待定;感觉 switch 那最终是分三个情况;branch、子节点、结束了;(每种情况的注释,需要写的更完整) -// if (!BpmSimpleModelNodeType.isBranchNode(node.getType()) && (childNode == null || childNode.getId() == null)) { -// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); -// process.addFlowElement(sequenceFlow); -// return; -// } + // 1.2 END_NODE 直接返回 BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); - if (nodeType == END_NODE) { return; } + + // 2.1 情况一:普通节点 BpmSimpleModelNodeVO childNode = node.getChildNode(); - // 2.1 普通节点 if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { if (!isValidNode(childNode)) { // 2.1.1 普通节点且无孩子节点。分两种情况 @@ -130,7 +124,7 @@ public class SimpleModelUtils { traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } } else { - // 2.2 分支节点 + // 2.2 情况二:分支节点 List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); // 分支终点节点 Id @@ -177,57 +171,6 @@ public class SimpleModelUtils { // 4.递归调用后续节点 继续递归建立 D->E 的连线 traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } - - // TODO @jason:下面的 PARALLEL_BRANCH_FORK_NODE、CONDITION_BRANCH_NODE、INCLUSIVE_BRANCH_FORK_NODE 是不是就是 isBranchNode?如果是的话,貌似不用 swtich,而是 if else 分类处理呢。 -// switch (nodeType) { -// case START_NODE: -// case APPROVE_NODE: -// case COPY_NODE: -// case PARALLEL_BRANCH_JOIN_NODE: -// case INCLUSIVE_BRANCH_JOIN_NODE: { -// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); -// process.addFlowElement(sequenceFlow); -// // 递归调用后续节点 -// buildAndAddBpmnSequenceFlow(process, childNode, targetNodeId); -// break; -// } -// case PARALLEL_BRANCH_FORK_NODE: -// case CONDITION_BRANCH_NODE: -// case INCLUSIVE_BRANCH_FORK_NODE: { -// // TODO @jason:这里 sequenceFlowTargetId 不建议做这样的 default。万一可能有 bug 哈;直接弄到对应的 136- 146 会更安全一点。 -// String sequenceFlowTargetId = (childNode == null || childNode.getId() == null) ? targetNodeId : childNode.getId(); -// List conditionNodes = node.getConditionNodes(); -// Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); -// for (BpmSimpleModelNodeVO item : conditionNodes) { -// // 构建表达式 -// // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 -// String conditionExpression = buildConditionExpression(item); -// -// BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); -// // TODO @jason:isValidNode -// if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { -// // TODO @jason:会存在 item.name 未空的情况么?这个时候,要不要兜底处理拼接 -// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), -// item.getId(), item.getName(), conditionExpression); -// process.addFlowElement(sequenceFlow); -// // 递归调用后续节点 -// // TODO @jason:最好也有个例子,嘿嘿;S4 -// buildAndAddBpmnSequenceFlow(process, nextNodeOnCondition, sequenceFlowTargetId); -// } else { -// SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), sequenceFlowTargetId, -// item.getId(), item.getName(), conditionExpression); -// process.addFlowElement(sequenceFlow); -// } -// } -// // 递归调用后续节点 TODO @jason:最好有个例子哈 -// buildAndAddBpmnSequenceFlow(process, childNode, targetNodeId); -// break; -// } -// default: { -// // TODO 其它节点类型的实现 -// } -// } - } /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index ef421a2e3..b39ec7212 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -238,7 +238,6 @@ public class BpmModelServiceImpl implements BpmModelService { saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel())); } - /** * 校验流程表单已配置 * From 1ec94e5bbc1bb343ae9112684c466eb09e0926f5 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 6 Jun 2024 09:51:02 +0800 Subject: [PATCH 029/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=96=B0=E5=A2=9E=E4=BC=9A?= =?UTF-8?q?=E7=AD=BE=E6=97=B6=E9=80=9A=E8=BF=87=E5=8F=AA=E9=9C=80=E4=B8=80?= =?UTF-8?q?=E4=BA=BA=EF=BC=8C=E6=8B=92=E7=BB=9D=E9=9C=80=E8=A6=81=E6=89=80?= =?UTF-8?q?=E6=9C=89=E4=BA=BA=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 2 ++ .../definition/BpmApproveMethodEnum.java | 6 ++-- .../BpmUserTaskRejectHandlerType.java | 6 ++-- .../flowable/core/util/SimpleModelUtils.java | 7 +++- .../bpm/service/task/BpmTaskServiceImpl.java | 33 +++++++++++++++++-- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index daa4d8616..3d1010462 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -52,6 +52,8 @@ public interface ErrorCodeConstants { ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人"); ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); ErrorCode TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID = new ErrorCode(1_009_005_015, "回退任务未指定目标任务编号"); + ErrorCode TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR = new ErrorCode(1_009_005_016, "按拒绝人数比例终止流程只能用于会签任务"); + ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); // ========== 动态表单模块 1-009-010-000 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index 522ff45ea..8d8972bd8 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -16,8 +16,10 @@ public enum BpmApproveMethodEnum { SINGLE_PERSON_APPROVE(1, "单人审批"), ALL_APPROVE(2, "多人会签(需所有审批人同意)"), // 会签 - ANY_OF_APPROVE(3, "多人或签(一名审批人同意即可)"), // 或签 - SEQUENTIAL_APPROVE(4, "依次审批"); // 依次审批 + APPROVE_BY_RATIO(3, "多人会签(按比例投票)"), + ANY_APPROVE_ALL_REJECT(4, "多人会签(通过只需一人,拒绝需要全员)"), // 通过只需一人,拒绝需要全员 + ANY_APPROVE(5, "多人或签(一名审批人通过即可)"), // 或签 + SEQUENTIAL_APPROVE(6, "依次审批"); // 依次审批 /** * 审批方式 diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index 7a455f382..53bb2abc7 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -13,8 +13,10 @@ import lombok.Getter; @AllArgsConstructor public enum BpmUserTaskRejectHandlerType { - TERMINATION(1, "终止流程"), - RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"); + FINISH_PROCESS(1, "终止流程"), + RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"), + FINISH_PROCESS_BY_REJECT_RATIO(3, "按拒绝人数比例终止流程"), // 用于会签 + FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支 private final Integer type; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index dcb829c16..7b2f2ef81 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -474,7 +474,7 @@ public class SimpleModelUtils { if (bpmApproveMethodEnum == BpmApproveMethodEnum.ALL_APPROVE) { multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(false); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_OF_APPROVE) { + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE) { multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(false); userTask.setLoopCharacteristics(multiInstanceCharacteristics); @@ -483,7 +483,12 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT) { + // 这种情况。拒绝任务时候,不会终止或者完成任务 参见 BpmTaskService#rejectTask 方法 + multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); + multiInstanceCharacteristics.setSequential(false); } + // TODO 会签(按比例投票 ) userTask.setLoopCharacteristics(multiInstanceCharacteristics); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index c57232939..7047261d9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -34,6 +34,7 @@ import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; +import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; @@ -337,7 +338,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); // 3.1 解析用户任务的拒绝处理类型 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); - FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + UserTask flowElement = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE)); BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); // 3.2 类型为驳回到指定的任务节点 @@ -350,6 +351,30 @@ public class BpmTaskServiceImpl implements BpmTaskService { .setReason(reqVO.getReason()); returnTask(userId, returnReq); return; + } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_RATIO) { + // 3.3 按拒绝人数比例终止流程 + if(!flowElement.hasMultiInstanceLoopCharacteristics()) { + throw exception(TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR); + } + Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()) + .executionId(task.getExecutionId()).singleResult(); + // 获取并行任务总数 + Integer nrOfInstances = runtimeService.getVariable(execution.getParentId(), "nrOfInstances", Integer.class); + // 获取未完成任务列表 + List taskList = getAssignedTaskListByConditions(task.getProcessInstanceId(), null, task.getTaskDefinitionKey()); + // 获取已经拒绝的任务数 + int rejectNumber = 0; + for (Task item : taskList) { + if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), FlowableUtils.getTaskStatus(item))) { + rejectNumber ++; + } + } + // 拒绝任务后,任务分配人清空。但不能完成任务 + taskService.setAssignee(task.getId(), ""); + // 不是所有人拒绝返回。 TODO 后续需要做按拒绝人数比例来判断 + if(!Objects.equals(nrOfInstances, rejectNumber)) { + return; + } } // 3.3 其他情况 终止流程。 TODO 后续可能会增加处理类型 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); @@ -431,8 +456,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + if (processInstance != null) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + } } }); From dff1ff90a7c5eb1fbde851b6640735c4a9f07664 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 6 Jun 2024 20:17:53 +0800 Subject: [PATCH 030/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20=E5=BF=AB=E6=90=AD?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/enums/definition/BpmApproveMethodEnum.java | 6 +++--- .../module/bpm/service/task/BpmTaskServiceImpl.java | 13 +++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index 8d8972bd8..32d7cb088 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -16,9 +16,9 @@ public enum BpmApproveMethodEnum { SINGLE_PERSON_APPROVE(1, "单人审批"), ALL_APPROVE(2, "多人会签(需所有审批人同意)"), // 会签 - APPROVE_BY_RATIO(3, "多人会签(按比例投票)"), - ANY_APPROVE_ALL_REJECT(4, "多人会签(通过只需一人,拒绝需要全员)"), // 通过只需一人,拒绝需要全员 - ANY_APPROVE(5, "多人或签(一名审批人通过即可)"), // 或签 + APPROVE_BY_RATIO(3, "多人会签(按比例投票)"), // 会签(按比例投票) + ANY_APPROVE_ALL_REJECT(4, "多人会签(通过只需一人,拒绝需要全员)"), // 会签(通过只需一人,拒绝需要全员) + ANY_APPROVE(5, "多人或签(一名审批人通过即可)"), // 或签(通过只需一人,拒绝只需一人) SEQUENTIAL_APPROVE(6, "依次审批"); // 依次审批 /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 7047261d9..a25843416 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -353,26 +353,30 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_RATIO) { // 3.3 按拒绝人数比例终止流程 - if(!flowElement.hasMultiInstanceLoopCharacteristics()) { + // TODO @jason:建议抛出系统异常 + if (!flowElement.hasMultiInstanceLoopCharacteristics()) { throw exception(TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR); } Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()) .executionId(task.getExecutionId()).singleResult(); - // 获取并行任务总数 + // 获取并行任务总数 TODO @jason:这个注释,其实挪到 Execution execution 前面好,更有整体性。 Integer nrOfInstances = runtimeService.getVariable(execution.getParentId(), "nrOfInstances", Integer.class); // 获取未完成任务列表 - List taskList = getAssignedTaskListByConditions(task.getProcessInstanceId(), null, task.getTaskDefinitionKey()); + List taskList = getAssignedTaskListByConditions(task.getProcessInstanceId(), null, + task.getTaskDefinitionKey()); // 获取已经拒绝的任务数 int rejectNumber = 0; + // TODO @jason: CollectionUtils.getSumValue() 替代 for (Task item : taskList) { if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), FlowableUtils.getTaskStatus(item))) { rejectNumber ++; } } + // TODO @jason:如果这样的话,后续会不会在【已完成】里面查询不到哈?【重要!!!!】 // 拒绝任务后,任务分配人清空。但不能完成任务 taskService.setAssignee(task.getId(), ""); // 不是所有人拒绝返回。 TODO 后续需要做按拒绝人数比例来判断 - if(!Objects.equals(nrOfInstances, rejectNumber)) { + if (!Objects.equals(nrOfInstances, rejectNumber)) { return; } } @@ -478,6 +482,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); } + // TODO @jason:改成 getTaskListByProcessInstanceIdAndAssigned;其它条件就不写了。主要考虑,还是动名词 @Override public List getAssignedTaskListByConditions(String processInstanceId, String executionId, String defineKey) { Assert.notNull(processInstanceId, "processInstanceId 不能为空"); From 0db7796c62fceb766abbde09893ffb601c240f59 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 7 Jun 2024 13:09:41 +0800 Subject: [PATCH 031/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../listener/BpmTimerFiredEventListener.java | 2 +- .../bpm/service/task/BpmTaskService.java | 2 +- .../bpm/service/task/BpmTaskServiceImpl.java | 30 ++++++++----------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index 891d91409..facd40174 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -80,7 +80,7 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); if (userTaskTimeoutAction != null) { // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? - List taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefKey); + List taskList = bpmTaskService.getTaskListByProcessInstanceIdAndAssigned(processInstanceId, null, taskDefKey); taskList.forEach(task -> { // 自动提醒 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index f65e3333b..3426cfc69 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -135,7 +135,7 @@ public interface BpmTaskService { * @param executionId execution Id * @param taskDefineKey 任务定义 Key */ - List getAssignedTaskListByConditions(String processInstanceId, String executionId, String taskDefineKey); + List getTaskListByProcessInstanceIdAndAssigned(String processInstanceId, String executionId, String taskDefineKey); /** * 获取当前任务的可回退的 UserTask 集合 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index a25843416..281a42b08 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; @@ -353,34 +354,30 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_RATIO) { // 3.3 按拒绝人数比例终止流程 - // TODO @jason:建议抛出系统异常 if (!flowElement.hasMultiInstanceLoopCharacteristics()) { - throw exception(TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR); + log.error("[rejectTask] 用户任务拒绝处理类型配置错误, 按拒绝人数终止流程只能用于会签任务"); + throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); } + // 获取并行任务总数 Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()) .executionId(task.getExecutionId()).singleResult(); - // 获取并行任务总数 TODO @jason:这个注释,其实挪到 Execution execution 前面好,更有整体性。 Integer nrOfInstances = runtimeService.getVariable(execution.getParentId(), "nrOfInstances", Integer.class); // 获取未完成任务列表 - List taskList = getAssignedTaskListByConditions(task.getProcessInstanceId(), null, + List taskList = getTaskListByProcessInstanceIdAndAssigned(task.getProcessInstanceId(), null, task.getTaskDefinitionKey()); // 获取已经拒绝的任务数 - int rejectNumber = 0; - // TODO @jason: CollectionUtils.getSumValue() 替代 - for (Task item : taskList) { - if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), FlowableUtils.getTaskStatus(item))) { - rejectNumber ++; - } - } - // TODO @jason:如果这样的话,后续会不会在【已完成】里面查询不到哈?【重要!!!!】 - // 拒绝任务后,任务分配人清空。但不能完成任务 - taskService.setAssignee(task.getId(), ""); + Integer rejectNumber = getSumValue(taskList, + item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), FlowableUtils.getTaskStatus(item)) ? 1 : 0, + Integer::sum, 0); +// // TODO @jason:如果这样的话,后续会不会在【已完成】里面查询不到哈?【重要!!!!】 +// // 拒绝任务后,任务分配人清空。但不能完成任务 +// taskService.setAssignee(task.getId(), ""); // 不是所有人拒绝返回。 TODO 后续需要做按拒绝人数比例来判断 if (!Objects.equals(nrOfInstances, rejectNumber)) { return; } } - // 3.3 其他情况 终止流程。 TODO 后续可能会增加处理类型 + // 3.4 其他情况 终止流程。 TODO 后续可能会增加处理类型 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); } @@ -482,9 +479,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); } - // TODO @jason:改成 getTaskListByProcessInstanceIdAndAssigned;其它条件就不写了。主要考虑,还是动名词 @Override - public List getAssignedTaskListByConditions(String processInstanceId, String executionId, String defineKey) { + public List getTaskListByProcessInstanceIdAndAssigned(String processInstanceId, String executionId, String defineKey) { Assert.notNull(processInstanceId, "processInstanceId 不能为空"); TaskQuery taskQuery = taskService.createTaskQuery().taskAssigned().processInstanceId(processInstanceId).active() .includeTaskLocalVariables(); From 12108e7365a0655d53b486ffce9907210e0121e0 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 7 Jun 2024 22:07:47 +0800 Subject: [PATCH 032/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=9F=BA=E4=BA=8E=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E4=BB=BB=E5=8A=A1=E5=AE=9E=E7=8E=B0=E4=BC=9A=E7=AD=BE?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E6=8B=92=E7=BB=9D=E9=9C=80=E8=A6=81=E5=85=A8?= =?UTF-8?q?=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmUserTaskRejectHandlerType.java | 2 +- .../vo/model/simple/BpmSimpleModelNodeVO.java | 6 ++ .../core/enums/BpmnModelConstants.java | 12 +++- .../CompleteByRejectCountExpression.java | 66 ++++++++++++++++++ .../MultiInstanceServiceTaskExpression.java | 38 +++++++++++ .../flowable/core/util/SimpleModelUtils.java | 68 ++++++++++++++++--- .../bpm/service/task/BpmTaskServiceImpl.java | 28 ++------ 7 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index 53bb2abc7..0f298a255 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -15,7 +15,7 @@ public enum BpmUserTaskRejectHandlerType { FINISH_PROCESS(1, "终止流程"), RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"), - FINISH_PROCESS_BY_REJECT_RATIO(3, "按拒绝人数比例终止流程"), // 用于会签 + FINISH_PROCESS_BY_REJECT_NUMBER(3, "按拒绝人数终止流程"), // 用于会签 FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支 private final Integer type; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index e13253e99..aab93ed1b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -41,6 +41,12 @@ public class BpmSimpleModelNodeVO { @Schema(description = "节点的属性") private Map attributes; // TODO @jason:建议是字段分拆下;类似说: + + /** + * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 + * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。 + */ + private String attachNodeId; // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 // TODO @jason 后面和前端一起调整一下 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index a3b0bc0c8..ada89443d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -50,6 +50,16 @@ public interface BpmnModelConstants { */ String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; + /** + * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式 + */ + String USER_TASK_APPROVE_METHOD = "approveMethod"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记 服务任务附属的用户任务 Id + */ + String SERVICE_TASK_ATTACH_USER_TASK_ID = "attachUserTaskId"; + /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 */ @@ -75,6 +85,4 @@ public interface BpmnModelConstants { * 支持转仿钉钉设计模型的 Bpmn 节点 */ Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); - - String REJECT_POST_PROCESS_MESSAGE_NAME = "message_reject_post_process"; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java new file mode 100644 index 000000000..99d121b95 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.expression; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD; + +/** + * 按拒绝人数计算会签的完成条件的流程表达式实现 + * + * @author jason + */ +@Component +@Slf4j +public class CompleteByRejectCountExpression { + + /** + * 会签的完成条件 + */ + public boolean completionCondition(DelegateExecution execution) { + FlowElement flowElement = execution.getCurrentFlowElement(); + // 实例总数 + Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances"); + // 完成的实例数 + Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances"); + // 审批方式 + Integer approveMethod = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_METHOD)); + Assert.notNull(approveMethod, "审批方式不能空"); + // 计算拒绝的人数 + Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(), + item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0, + Integer::sum, 0); + // 同意的人数为 完成人数 - 拒绝人数 + int agreeCount = nrOfCompletedInstances - rejectCount; + // 1. 多人会签(通过只需一人,拒绝需要全员) + if (Objects.equals(ANY_APPROVE_ALL_REJECT.getMethod(), approveMethod)) { + // 1.1 一人同意. 会签任务完成 + if (agreeCount > 0) { + return true; + } else { + // 1.2 所有人都拒绝了。设置任务拒绝变量, 会签任务完成。 后续终止流程在 ServiceTask【MultiInstanceServiceTaskExpression】处理 + if (Objects.equals(nrOfInstances, rejectCount)) { + execution.setVariable(String.format("%s_reject",flowElement.getId()), Boolean.TRUE); + return true; + } + return false; + } + } + // TODO 多人会签(按比例投票) + log.error("[completionCondition] 按拒绝人数计算会签的完成条件的审批方式[{}],配置有误", approveMethod); + throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java new file mode 100644 index 000000000..5d2fc5522 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.expression; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.BooleanUtil; +import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +/** + * 处理会签 Service Task 代理表达式 + * + * @author jason + */ +@Component +public class MultiInstanceServiceTaskExpression implements JavaDelegate { + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Override + public void execute(DelegateExecution execution) { + String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(), + BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); + Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空"); + // 获取会签任务是否被拒绝 + Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class); + // 如果会签任务被拒绝, 终止流程 + if (BooleanUtil.isTrue(userTaskRejected)) { + processInstanceService.updateProcessInstanceReject(execution.getProcessInstanceId(), + BpmCommentTypeEnum.REJECT.formatComment("会签任务拒绝人数满足条件")); + } + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 7b2f2ef81..4ad525ca1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -26,6 +26,7 @@ import java.util.Objects; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; @@ -55,6 +56,11 @@ public class SimpleModelUtils { */ public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; + /** + * 按拒绝人数计算多实例完成条件的表达式 + */ + public static final String COMPLETE_BY_REJECT_COUNT_EXPRESSION = "${completeByRejectCountExpression.completionCondition(execution)}"; + // TODO-DONE @jason:建议方法名,改成 buildBpmnModel // TODO @yunai:注释需要完善下; @@ -71,10 +77,6 @@ public class SimpleModelUtils { // 不加这个 解析 Message 会报 NPE 异常 . bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace; // TODO 芋艿:后续在 review - // @芋艿 这个 Message 可以去掉 暂时用不上 - Message rejectPostProcessMsg = new Message(); - rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME); - bpmnModel.addMessage(rejectPostProcessMsg); Process process = new Process(); process.setId(processId); @@ -107,19 +109,30 @@ public class SimpleModelUtils { if (nodeType == END_NODE) { return; } - // 2.1 情况一:普通节点 BpmSimpleModelNodeVO childNode = node.getChildNode(); if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { if (!isValidNode(childNode)) { // 2.1.1 普通节点且无孩子节点。分两种情况 // a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线。 - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); - process.addFlowElement(sequenceFlow); + if (StrUtil.isNotEmpty(node.getAttachNodeId())) { + // 2.1.1.1 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线 + List sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), targetNodeId); + sequenceFlows.forEach(process::addFlowElement); + } else { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); + process.addFlowElement(sequenceFlow); + } } else { // 2.1.2 普通节点且有孩子节点。建立连线 - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); - process.addFlowElement(sequenceFlow); + if (StrUtil.isNotEmpty(node.getAttachNodeId())) { + // 2.1.1.2 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线 + List sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), childNode.getId()); + sequenceFlows.forEach(process::addFlowElement); + } else { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); + process.addFlowElement(sequenceFlow); + } // 递归调用后续节点 traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } @@ -173,6 +186,18 @@ public class SimpleModelUtils { } } + /** + * 构建有附加节点的连线 + * @param nodeId 当前节点 Id + * @param attachNodeId 附属节点 Id + * @param targetNodeId 目标节点 Id + */ + private static List buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null); + SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null); + return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow); + } + /** * 构造条件表达式 * @@ -331,9 +356,28 @@ public class SimpleModelUtils { BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); flowElements.add(boundaryEvent); } + // 如果按拒绝人数终止流程。需要添加附加的 ServiceTask 处理 + if (userTaskConfig.getRejectHandler() != null && + Objects.equals(FINISH_PROCESS_BY_REJECT_NUMBER.getType(), userTaskConfig.getRejectHandler().getType())) { + ServiceTask serviceTask = buildMultiInstanceServiceTask(node); + flowElements.add(serviceTask); + } return flowElements; } + private static ServiceTask buildMultiInstanceServiceTask(BpmSimpleModelNodeVO node) { + ServiceTask serviceTask = new ServiceTask(); + String id = String.format("Activity-%s", IdUtil.fastSimpleUUID()); + serviceTask.setId(id); + serviceTask.setName("会签服务任务"); + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + serviceTask.setImplementation("${multiInstanceServiceTaskExpression}"); + serviceTask.setAsynchronous(false); + addExtensionElement(serviceTask, SERVICE_TASK_ATTACH_USER_TASK_ID, node.getId()); + node.setAttachNodeId(id); + return serviceTask; + } + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); @@ -468,6 +512,9 @@ public class SimpleModelUtils { if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { return; } + // 添加审批方式的扩展属性 + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, + approveMethod == null ? null : approveMethod.toString()); MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); // 设置 collectionVariable。本系统用不到。会在 仅仅为了校验。 multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); @@ -484,8 +531,7 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT) { - // 这种情况。拒绝任务时候,不会终止或者完成任务 参见 BpmTaskService#rejectTask 方法 - multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); + multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION); multiInstanceCharacteristics.setSequential(false); } // TODO 会签(按比例投票 ) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 281a42b08..13e696f6a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -35,7 +35,6 @@ import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; -import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; @@ -352,30 +351,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { .setReason(reqVO.getReason()); returnTask(userId, returnReq); return; - } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_RATIO) { - // 3.3 按拒绝人数比例终止流程 + } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) { + // 3.3 按拒绝人数终止流程 if (!flowElement.hasMultiInstanceLoopCharacteristics()) { log.error("[rejectTask] 用户任务拒绝处理类型配置错误, 按拒绝人数终止流程只能用于会签任务"); throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); } - // 获取并行任务总数 - Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()) - .executionId(task.getExecutionId()).singleResult(); - Integer nrOfInstances = runtimeService.getVariable(execution.getParentId(), "nrOfInstances", Integer.class); - // 获取未完成任务列表 - List taskList = getTaskListByProcessInstanceIdAndAssigned(task.getProcessInstanceId(), null, - task.getTaskDefinitionKey()); - // 获取已经拒绝的任务数 - Integer rejectNumber = getSumValue(taskList, - item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), FlowableUtils.getTaskStatus(item)) ? 1 : 0, - Integer::sum, 0); -// // TODO @jason:如果这样的话,后续会不会在【已完成】里面查询不到哈?【重要!!!!】 -// // 拒绝任务后,任务分配人清空。但不能完成任务 -// taskService.setAssignee(task.getId(), ""); - // 不是所有人拒绝返回。 TODO 后续需要做按拒绝人数比例来判断 - if (!Objects.equals(nrOfInstances, rejectNumber)) { - return; - } + // 设置变量值为拒绝 + runtimeService.setVariableLocal(task.getExecutionId(), BpmConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus()); + // 完成任务 + taskService.complete(task.getId()); + return; } // 3.4 其他情况 终止流程。 TODO 后续可能会增加处理类型 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); From 5c2fcdce1554d1d6a9267bf308dbe8a289538e90 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 8 Jun 2024 08:20:20 +0800 Subject: [PATCH 033/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E4=BC=9A=E7=AD=BE=E6=8C=89?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=AF=94=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmApproveMethodEnum.java | 2 +- .../core/enums/BpmnModelConstants.java | 5 ++++ .../CompleteByRejectCountExpression.java | 24 ++++++++++++++++--- .../simple/SimpleModelUserTaskConfig.java | 7 +++++- .../flowable/core/util/SimpleModelUtils.java | 13 ++++++---- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index 32d7cb088..f2b61dbbe 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -16,7 +16,7 @@ public enum BpmApproveMethodEnum { SINGLE_PERSON_APPROVE(1, "单人审批"), ALL_APPROVE(2, "多人会签(需所有审批人同意)"), // 会签 - APPROVE_BY_RATIO(3, "多人会签(按比例投票)"), // 会签(按比例投票) + APPROVE_BY_RATIO(3, "多人会签(按通过比例)"), // 会签(按通过比例) ANY_APPROVE_ALL_REJECT(4, "多人会签(通过只需一人,拒绝需要全员)"), // 会签(通过只需一人,拒绝需要全员) ANY_APPROVE(5, "多人或签(一名审批人通过即可)"), // 或签(通过只需一人,拒绝只需一人) SEQUENTIAL_APPROVE(6, "依次审批"); // 依次审批 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index ada89443d..9c9176dd8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -55,6 +55,11 @@ public interface BpmnModelConstants { */ String USER_TASK_APPROVE_METHOD = "approveMethod"; + /** + * BPMN UserTask 的扩展属性,当审批方式为按通过比例时, 标记会签通过比例 + */ + String USER_TASK_APPROVE_RATIO = "approveRatio"; + /** * BPMN ExtensionElement 的扩展属性,用于标记 服务任务附属的用户任务 Id */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java index 99d121b95..027a950a1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java @@ -16,7 +16,9 @@ import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.APPROVE_BY_RATIO; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO; /** * 按拒绝人数计算会签的完成条件的流程表达式实现 @@ -28,7 +30,7 @@ import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnMode public class CompleteByRejectCountExpression { /** - * 会签的完成条件 + * 会签的完成条件 */ public boolean completionCondition(DelegateExecution execution) { FlowElement flowElement = execution.getCurrentFlowElement(); @@ -53,13 +55,29 @@ public class CompleteByRejectCountExpression { } else { // 1.2 所有人都拒绝了。设置任务拒绝变量, 会签任务完成。 后续终止流程在 ServiceTask【MultiInstanceServiceTaskExpression】处理 if (Objects.equals(nrOfInstances, rejectCount)) { - execution.setVariable(String.format("%s_reject",flowElement.getId()), Boolean.TRUE); + execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); return true; } return false; } + } else if (Objects.equals(APPROVE_BY_RATIO.getMethod(), approveMethod)) { + Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO)); + Assert.notNull(approveRatio, "通过比例不能空"); + double approvePct = approveRatio / (double) 100; + double realApprovePct = (double) agreeCount / nrOfInstances; + // 判断通过比例 + if (realApprovePct >= approvePct) { + return true; + } + double rejectPct = (100 - approveRatio) / (double) 100; + double realRejectPct = (double) rejectCount / nrOfInstances; + // 判断拒绝比例 + if (realRejectPct >= rejectPct) { + execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); + return true; + } + return false; } - // TODO 多人会签(按比例投票) log.error("[completionCondition] 按拒绝人数计算会签的完成条件的审批方式[{}],配置有误", approveMethod); throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java index 1ff3dd714..7523fd8b0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import lombok.Data; @@ -30,10 +31,14 @@ public class SimpleModelUserTaskConfig { private List> fieldsPermission; /** - * 审批方式 + * 审批方式 {@link BpmApproveMethodEnum } */ private Integer approveMethod; + /** + * 通过比例 当审批方式为 多人会签(按通过比例) 需设置 + */ + private Integer approveRatio; /** * 超时处理 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 4ad525ca1..a28eca0fa 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -493,7 +493,7 @@ public class SimpleModelUtils { // 添加表单字段权限属性元素 addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask); // 处理多实例 - processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTask); + processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTaskConfig.getApproveRatio(), userTask); // 添加任务被拒绝的处理元素 addTaskRejectElements(userTaskConfig.getRejectHandler(), userTask); return userTask; @@ -507,7 +507,7 @@ public class SimpleModelUtils { addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); } - private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, UserTask userTask) { + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { return; @@ -530,11 +530,16 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT) { + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT ){ multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION); multiInstanceCharacteristics.setSequential(false); + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) { + multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION); + multiInstanceCharacteristics.setSequential(false); + Assert.notNull(approveRatio, "通过比例不能为空"); + // 添加通过比例的扩展属性 + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_RATIO, approveRatio.toString()); } - // TODO 会签(按比例投票 ) userTask.setLoopCharacteristics(multiInstanceCharacteristics); } From 479d664a63f050bc9324b7c44c9f32bafaf7d25e Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 8 Jun 2024 11:03:06 +0800 Subject: [PATCH 034/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/simple/BpmSimpleModelNodeVO.java | 2 ++ .../core/custom/delegate/CopyUserDelegate.java} | 17 +++++++---------- .../MultiInstanceServiceTaskDelegate.java} | 7 ++++--- .../CompleteByRejectCountExpression.java | 2 +- .../flowable/core/util/SimpleModelUtils.java | 9 +++------ .../task/BpmProcessInstanceCopyServiceImpl.java | 2 +- 6 files changed, 18 insertions(+), 21 deletions(-) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/{service/task/BpmSimpleNodeService.java => framework/flowable/core/custom/delegate/CopyUserDelegate.java} (71%) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/{expression/MultiInstanceServiceTaskExpression.java => custom/delegate/MultiInstanceServiceTaskDelegate.java} (88%) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/{ => custom}/expression/CompleteByRejectCountExpression.java (98%) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index aab93ed1b..f226ce2c3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; @@ -46,6 +47,7 @@ public class BpmSimpleModelNodeVO { * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。 */ + @JsonIgnore private String attachNodeId; // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java similarity index 71% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java index b0233e03e..69e983507 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java @@ -1,38 +1,35 @@ -package cn.iocoder.yudao.module.bpm.service.task; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; import jakarta.annotation.Resource; import org.flowable.bpmn.model.FlowElement; import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; import org.springframework.stereotype.Service; import java.util.Set; /** - * 仿钉钉快搭各个节点 Service + * 处理抄送用户的代理 * * @author jason */ @Service -public class BpmSimpleNodeService { +public class CopyUserDelegate implements JavaDelegate { @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; @Resource private BpmProcessInstanceCopyService processInstanceCopyService; - /** - * 仿钉钉快搭抄送 - * - * @param execution 执行的任务(ScriptTask) - */ - public Boolean copy(DelegateExecution execution) { + @Override + public void execute(DelegateExecution execution) { // TODO @芋艿:可能要考虑,系统抄送,没有 taskId 的情况。 Set userIds = taskCandidateInvoker.calculateUsers(execution); FlowElement currentFlowElement = execution.getCurrentFlowElement(); processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), currentFlowElement.getId(), currentFlowElement.getName()); - return Boolean.TRUE; } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java similarity index 88% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java index 5d2fc5522..958c0b14e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/MultiInstanceServiceTaskExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.expression; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.BooleanUtil; @@ -12,12 +12,12 @@ import org.flowable.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; /** - * 处理会签 Service Task 代理表达式 + * 处理会签 Service Task 代理 * * @author jason */ @Component -public class MultiInstanceServiceTaskExpression implements JavaDelegate { +public class MultiInstanceServiceTaskDelegate implements JavaDelegate { @Resource private BpmProcessInstanceService processInstanceService; @@ -35,4 +35,5 @@ public class MultiInstanceServiceTaskExpression implements JavaDelegate { BpmCommentTypeEnum.REJECT.formatComment("会签任务拒绝人数满足条件")); } } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java similarity index 98% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java index 027a950a1..7208c96b7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/expression/CompleteByRejectCountExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.expression; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.expression; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index a28eca0fa..88ca59d5f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -44,8 +44,6 @@ public class SimpleModelUtils { */ public static final String JOIN_GATE_WAY_NODE_ID_SUFFIX = "_join"; - public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}"; - /** * 所有审批人同意的表达式 */ @@ -371,7 +369,7 @@ public class SimpleModelUtils { serviceTask.setId(id); serviceTask.setName("会签服务任务"); serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - serviceTask.setImplementation("${multiInstanceServiceTaskExpression}"); + serviceTask.setImplementation("${multiInstanceServiceTaskDelegate}"); serviceTask.setAsynchronous(false); addExtensionElement(serviceTask, SERVICE_TASK_ATTACH_USER_TASK_ID, node.getId()); node.setAttachNodeId(id); @@ -417,9 +415,8 @@ public class SimpleModelUtils { ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); - // TODO @jason:建议用 delegateExpression;原因是,直接走 bpmSimpleNodeService.copy(execution) 的话,万一后续抄送改实现,可能比较麻烦。最好是搞个独立的 bean,然后它去调用抄 bpmSimpleNodeService; - serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION); - serviceTask.setImplementation(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + serviceTask.setImplementation("${copyUserDelegate}"); // 添加抄送候选人元素 addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index e4d66f8c4..940327cb5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -47,7 +47,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy // TODO @芋艿:这里多加了一个 name; @Override public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { - // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行 + // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(UserTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行 // Task task = taskService.getTask(taskId); // if (ObjectUtil.isNull(task)) { // throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); From b0fe72d73513022e4ce7d9cfecc3813dd5e932a5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 12 Jun 2024 20:23:16 +0800 Subject: [PATCH 035/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9A=E4=BC=9A=E7=AD=BE=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java | 1 + .../core/custom/delegate/MultiInstanceServiceTaskDelegate.java | 2 ++ .../core/custom/expression/CompleteByRejectCountExpression.java | 2 ++ .../yudao/module/bpm/service/task/BpmTaskServiceImpl.java | 1 + 4 files changed, 6 insertions(+) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index f226ce2c3..38193175e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -49,6 +49,7 @@ public class BpmSimpleModelNodeVO { */ @JsonIgnore private String attachNodeId; + // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 // TODO @jason 后面和前端一起调整一下 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java index 958c0b14e..9c2482835 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java @@ -30,6 +30,8 @@ public class MultiInstanceServiceTaskDelegate implements JavaDelegate { // 获取会签任务是否被拒绝 Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class); // 如果会签任务被拒绝, 终止流程 + // TODO @jason:【重要】需要测试下,如果基于 createChangeActivityStateBuilder()、changeState 到结束节点,实现审批不通过; + // 注意:需要考虑 bpmn 的高亮问题;(不过这个,未来可能会废弃掉!) if (BooleanUtil.isTrue(userTaskRejected)) { processInstanceService.updateProcessInstanceReject(execution.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.formatComment("会签任务拒绝人数满足条件")); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java index 7208c96b7..14328a39d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java @@ -42,6 +42,7 @@ public class CompleteByRejectCountExpression { Integer approveMethod = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_METHOD)); Assert.notNull(approveMethod, "审批方式不能空"); // 计算拒绝的人数 + // TODO @jason:CollUtil.filter().size();貌似可以更简洁 Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(), item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0, Integer::sum, 0); @@ -81,4 +82,5 @@ public class CompleteByRejectCountExpression { log.error("[completionCondition] 按拒绝人数计算会签的完成条件的审批方式[{}],配置有误", approveMethod); throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 13e696f6a..5caaaf3de 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -353,6 +353,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) { // 3.3 按拒绝人数终止流程 + // TODO @jason:建议抛出系统异常。类似 throw new IllegalStateException() if (!flowElement.hasMultiInstanceLoopCharacteristics()) { log.error("[rejectTask] 用户任务拒绝处理类型配置错误, 按拒绝人数终止流程只能用于会签任务"); throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); From 7423f9ddad95ddf485554d26e9e843a7aa56757d Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 13 Jun 2024 23:07:32 +0800 Subject: [PATCH 036/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E4=BB=BB=E5=8A=A1=E6=8B=92?= =?UTF-8?q?=E7=BB=9D=EF=BC=8C=E8=B7=B3=E8=BD=AC=E5=88=B0=20EndEvent=20?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delegate/MultiInstanceServiceTaskDelegate.java | 8 +++----- .../listener/BpmProcessInstanceEventListener.java | 2 +- .../framework/flowable/core/util/BpmnModelUtils.java | 6 ++++++ .../bpm/service/task/BpmProcessInstanceService.java | 10 +++++++++- .../service/task/BpmProcessInstanceServiceImpl.java | 2 +- .../module/bpm/service/task/BpmTaskServiceImpl.java | 11 ++++------- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java index 9c2482835..27d6994fd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.BooleanUtil; -import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; @@ -24,17 +23,16 @@ public class MultiInstanceServiceTaskDelegate implements JavaDelegate { @Override public void execute(DelegateExecution execution) { + String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(), BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空"); // 获取会签任务是否被拒绝 Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class); - // 如果会签任务被拒绝, 终止流程 - // TODO @jason:【重要】需要测试下,如果基于 createChangeActivityStateBuilder()、changeState 到结束节点,实现审批不通过; - // 注意:需要考虑 bpmn 的高亮问题;(不过这个,未来可能会废弃掉!) + // 如果会签任务被拒绝, 终止流程, 跳转到 EndEvent 节点 if (BooleanUtil.isTrue(userTaskRejected)) { processInstanceService.updateProcessInstanceReject(execution.getProcessInstanceId(), - BpmCommentTypeEnum.REJECT.formatComment("会签任务拒绝人数满足条件")); + execution.getCurrentActivityId(), "会签任务未达到通过比例" ); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java index cf1506e8d..4a8d0c244 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java @@ -41,7 +41,7 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent @Override protected void processCompleted(FlowableEngineEntityEvent event) { - processInstanceService.updateProcessInstanceWhenApprove((ProcessInstance)event.getEntity()); + processInstanceService.updateProcessInstanceWhenCompleted((ProcessInstance)event.getEntity()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index cdaa155dc..0cb2c1b4e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -136,6 +136,12 @@ public class BpmnModelUtils { return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent); } + public static EndEvent getEndEvent(BpmnModel model) { + Process process = model.getMainProcess(); + // 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题 + return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent); + } + public static BpmnModel getBpmnModel(byte[] bpmnBytes) { if (ArrayUtil.isEmpty(bpmnBytes)) { return null; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 9ba4cb077..5baa554b2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -137,8 +137,16 @@ public interface BpmProcessInstanceService { * 更新 ProcessInstance 拓展记录为不通过 * * @param id 流程编号 + * @param currentActivityId 当前的活动Id * @param reason 理由。例如说,审批不通过时,需要传递该值 */ - void updateProcessInstanceReject(String id, String reason); + void updateProcessInstanceReject(String id, String currentActivityId, String reason); + + /** + * 当流程结束时候。 更新 ProcessInstance + * + * @param instance 流程任务 + */ + void updateProcessInstanceWhenCompleted(ProcessInstance instance); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index f7bc24223..531ccb538 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点, 结束流程 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, // 当前节点 endEvent.getId()) // 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 5caaaf3de..ba9b786c8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -5,7 +5,6 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; @@ -353,19 +352,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) { // 3.3 按拒绝人数终止流程 - // TODO @jason:建议抛出系统异常。类似 throw new IllegalStateException() if (!flowElement.hasMultiInstanceLoopCharacteristics()) { - log.error("[rejectTask] 用户任务拒绝处理类型配置错误, 按拒绝人数终止流程只能用于会签任务"); - throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); + log.error("[rejectTask] 按拒绝人数终止流程类型,只能用于会签任务. 当前任务【{}】不是会签任务", task.getId()); + throw new IllegalStateException("按拒绝人数终止流程类型,只能用于会签任务"); } // 设置变量值为拒绝 runtimeService.setVariableLocal(task.getExecutionId(), BpmConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus()); - // 完成任务 taskService.complete(task.getId()); return; } - // 3.4 其他情况 终止流程。 TODO 后续可能会增加处理类型 - processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); + // 3.4 其他情况 终止流程。 + processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), task.getTaskDefinitionKey(), reqVO.getReason()); } /** From d7e1b87b1bb4322ed67900988423c97448bbe805 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 14 Jun 2024 11:05:32 +0800 Subject: [PATCH 037/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E7=AE=80=E5=8C=96=E5=A4=9A?= =?UTF-8?q?=E4=BA=BA=E5=AE=A1=E6=89=B9=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmApproveMethodEnum.java | 12 ++--- .../CompleteByRejectCountExpression.java | 47 +++++++++---------- .../flowable/core/util/SimpleModelUtils.java | 10 +--- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index f2b61dbbe..e199a7437 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -6,7 +6,7 @@ import lombok.Getter; // TODO @芋艿:审批方式的名字,可能要看下; /** - * BPM 审批方式的枚举 + * BPM 多人审批方式的枚举 * * @author jason */ @@ -14,12 +14,10 @@ import lombok.Getter; @AllArgsConstructor public enum BpmApproveMethodEnum { - SINGLE_PERSON_APPROVE(1, "单人审批"), - ALL_APPROVE(2, "多人会签(需所有审批人同意)"), // 会签 - APPROVE_BY_RATIO(3, "多人会签(按通过比例)"), // 会签(按通过比例) - ANY_APPROVE_ALL_REJECT(4, "多人会签(通过只需一人,拒绝需要全员)"), // 会签(通过只需一人,拒绝需要全员) - ANY_APPROVE(5, "多人或签(一名审批人通过即可)"), // 或签(通过只需一人,拒绝只需一人) - SEQUENTIAL_APPROVE(6, "依次审批"); // 依次审批 + RANDOM_SELECT_ONE_APPROVE(1, "随机挑选一人审批"), + APPROVE_BY_RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) + ANY_APPROVE(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人) + SEQUENTIAL_APPROVE(4, "依次审批"); // 依次审批 /** * 审批方式 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java index 14328a39d..35e82ab21 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java @@ -15,7 +15,6 @@ import org.springframework.stereotype.Component; import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.APPROVE_BY_RATIO; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO; @@ -41,46 +40,46 @@ public class CompleteByRejectCountExpression { // 审批方式 Integer approveMethod = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_METHOD)); Assert.notNull(approveMethod, "审批方式不能空"); - // 计算拒绝的人数 - // TODO @jason:CollUtil.filter().size();貌似可以更简洁 + if (!Objects.equals(APPROVE_BY_RATIO.getMethod(), approveMethod)) { + log.error("[completionCondition] the execution is [{}] 审批方式[{}] 不匹配", execution, approveMethod); + throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); + } + // 获取拒绝人数 + // TODO @jason:CollUtil.filter().size();貌似可以更简洁 @芋艿 CollUtil.filter().size() 使用这个会报错,好坑了. Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(), item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0, Integer::sum, 0); - // 同意的人数为 完成人数 - 拒绝人数 + // 同意人数: 完成人数 - 拒绝人数 int agreeCount = nrOfCompletedInstances - rejectCount; - // 1. 多人会签(通过只需一人,拒绝需要全员) - if (Objects.equals(ANY_APPROVE_ALL_REJECT.getMethod(), approveMethod)) { - // 1.1 一人同意. 会签任务完成 - if (agreeCount > 0) { + // 多人会签(按通过比例) + Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO)); + Assert.notNull(approveRatio, "通过比例不能空"); + if (Objects.equals(100, approveRatio)) { + // 所有人都同意 + if (agreeCount == nrOfInstances) { return true; - } else { - // 1.2 所有人都拒绝了。设置任务拒绝变量, 会签任务完成。 后续终止流程在 ServiceTask【MultiInstanceServiceTaskExpression】处理 - if (Objects.equals(nrOfInstances, rejectCount)) { - execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); - return true; - } - return false; } - } else if (Objects.equals(APPROVE_BY_RATIO.getMethod(), approveMethod)) { - Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO)); - Assert.notNull(approveRatio, "通过比例不能空"); - double approvePct = approveRatio / (double) 100; - double realApprovePct = (double) agreeCount / nrOfInstances; + // 一个人拒绝了 + if (rejectCount > 0) { + execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); + return true; + } + } else { // 判断通过比例 + double approvePct = approveRatio / (double) 100; + double realApprovePct = (double) agreeCount / nrOfInstances; if (realApprovePct >= approvePct) { return true; } - double rejectPct = (100 - approveRatio) / (double) 100; + double rejectPct = (100 - approveRatio) / (double) 100; double realRejectPct = (double) rejectCount / nrOfInstances; // 判断拒绝比例 if (realRejectPct >= rejectPct) { execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); return true; } - return false; } - log.error("[completionCondition] 按拒绝人数计算会签的完成条件的审批方式[{}],配置有误", approveMethod); - throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); + return false; } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 88ca59d5f..8549ac82f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -506,7 +506,7 @@ public class SimpleModelUtils { private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); - if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { + if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM_SELECT_ONE_APPROVE) { return; } // 添加审批方式的扩展属性 @@ -515,10 +515,7 @@ public class SimpleModelUtils { MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); // 设置 collectionVariable。本系统用不到。会在 仅仅为了校验。 multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); - if (bpmApproveMethodEnum == BpmApproveMethodEnum.ALL_APPROVE) { - multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); - multiInstanceCharacteristics.setSequential(false); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE) { + if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE) { multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(false); userTask.setLoopCharacteristics(multiInstanceCharacteristics); @@ -527,9 +524,6 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE_ALL_REJECT ){ - multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION); - multiInstanceCharacteristics.setSequential(false); } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) { multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION); multiInstanceCharacteristics.setSequential(false); From 8585e05772dcf43aec30e4604689ac83bf01ccad Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 14 Jun 2024 21:53:57 +0800 Subject: [PATCH 038/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E4=BC=9A=E7=AD=BE=E6=8C=89?= =?UTF-8?q?=E6=AF=94=E4=BE=8B=E9=80=9A=E8=BF=87bug=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CompleteByRejectCountExpression.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java index 35e82ab21..acb689722 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java @@ -49,35 +49,23 @@ public class CompleteByRejectCountExpression { Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(), item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0, Integer::sum, 0); - // 同意人数: 完成人数 - 拒绝人数 + // 同意人数: 完成人数 - 拒绝人数 int agreeCount = nrOfCompletedInstances - rejectCount; // 多人会签(按通过比例) Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO)); Assert.notNull(approveRatio, "通过比例不能空"); - if (Objects.equals(100, approveRatio)) { - // 所有人都同意 - if (agreeCount == nrOfInstances) { - return true; - } - // 一个人拒绝了 - if (rejectCount > 0) { - execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); - return true; - } - } else { - // 判断通过比例 - double approvePct = approveRatio / (double) 100; - double realApprovePct = (double) agreeCount / nrOfInstances; - if (realApprovePct >= approvePct) { - return true; - } - double rejectPct = (100 - approveRatio) / (double) 100; - double realRejectPct = (double) rejectCount / nrOfInstances; - // 判断拒绝比例 - if (realRejectPct >= rejectPct) { - execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); - return true; - } + // 判断通过比例 + double approvePct = approveRatio / (double) 100; + double realApprovePct = (double) agreeCount / nrOfInstances; + if (realApprovePct >= approvePct) { + return true; + } + double rejectPct = (100 - approveRatio) / (double) 100; + double realRejectPct = (double) rejectCount / nrOfInstances; + // 判断拒绝比例 + if (realRejectPct > rejectPct) { + execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); + return true; } return false; } From 41b9ab2ba53b46d23cbc78b194639e8df70b23c2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 17 Jun 2024 18:45:54 +0800 Subject: [PATCH 039/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20=E5=BF=AB=E6=90=AD?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmApproveMethodEnum.java | 1 - .../definition/BpmFieldPermissionEnum.java | 1 + .../BpmUserTaskRejectHandlerType.java | 3 ++ .../admin/definition/BpmModelController.java | 1 + .../vo/model/simple/BpmSimpleModelNodeVO.java | 14 +++++---- .../simple/BpmSimpleModelUpdateReqVO.java | 1 + .../BpmTaskCandidateStartUserStrategy.java | 7 +---- .../custom/delegate/CopyUserDelegate.java | 13 ++++++-- .../MultiInstanceServiceTaskDelegate.java | 3 +- .../CompleteByRejectCountExpression.java | 1 + ...riableConvertByTypeExpressionFunction.java | 1 + .../core/enums/BpmnModelConstants.java | 1 + .../core/listener/BpmTaskEventListener.java | 1 + .../listener/BpmTimerFiredEventListener.java | 2 +- .../task/TodoTaskReminderProducer.java | 2 ++ .../SimpleModelConditionGroups.java | 2 +- .../SimpleModelUserTaskConfig.java | 9 +++--- .../flowable/core/util/BpmnFormUtils.java | 2 +- .../flowable/core/util/BpmnModelUtils.java | 2 ++ .../flowable/core/util/SimpleModelUtils.java | 20 ++++--------- .../service/definition/BpmModelService.java | 27 +---------------- .../definition/BpmModelServiceImpl.java | 30 +++++++++---------- .../task/BpmProcessInstanceCopyService.java | 2 ++ .../task/BpmProcessInstanceService.java | 8 ++--- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 1 + .../bpm/service/task/BpmTaskServiceImpl.java | 10 +++++-- 27 files changed, 78 insertions(+), 89 deletions(-) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/{simple => simplemodel}/SimpleModelConditionGroups.java (99%) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/{simple => simplemodel}/SimpleModelUserTaskConfig.java (95%) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index e199a7437..736b0ceed 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil; import lombok.AllArgsConstructor; import lombok.Getter; -// TODO @芋艿:审批方式的名字,可能要看下; /** * BPM 多人审批方式的枚举 * diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java index a71f1f51b..9a10621e7 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -13,6 +13,7 @@ import lombok.Getter; @AllArgsConstructor public enum BpmFieldPermissionEnum { + // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3) WRITE(1, "可编辑"), READ(2, "只读"), NONE(3, "隐藏"); diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index 0f298a255..7a2f50793 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -13,8 +13,10 @@ import lombok.Getter; @AllArgsConstructor public enum BpmUserTaskRejectHandlerType { + // TODO @jason:是不是收敛成 2 个:FINISH_PROCESS => 1. 直接结束流程;RETURN_PRE_USER_TASK => 2. 驳回到指定节点(RETURN_USER_TASK【去掉 PRE】) FINISH_PROCESS(1, "终止流程"), RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"), + FINISH_PROCESS_BY_REJECT_NUMBER(3, "按拒绝人数终止流程"), // 用于会签 FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支 @@ -24,4 +26,5 @@ public enum BpmUserTaskRejectHandlerType { public static BpmUserTaskRejectHandlerType typeOf(Integer type) { return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index c9ff059ff..cc3a4514f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -149,6 +149,7 @@ public class BpmModelController { // ========== 仿钉钉/飞书的精简模型 ========= + // TODO @jason:modelId => id 哈。一般属于自己的模块,可以简化命名。 @GetMapping("/simple/get") @Operation(summary = "获得仿钉钉流程设计模型") @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 38193175e..7a8686ef4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -43,6 +43,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "节点的属性") private Map attributes; // TODO @jason:建议是字段分拆下;类似说: + // TODO @jason:看看是不是可以简化; /** * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。 @@ -52,12 +53,13 @@ public class BpmSimpleModelNodeVO { // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 - // TODO @jason 后面和前端一起调整一下 - // TODO @芋艿:审批人的选择; - // TODO @芋艿:没有人的策略? - // TODO @芋艿:审批拒绝的策略? - // TODO @芋艿:配置的可操作列表? - // TODO @芋艿:超时配置;要支持指定时间点、指定时间间隔; + // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级 + // TODO @芋艿:① 审批人的选择; + // TODO @芋艿:⑥ 没有人的策略? + // TODO @芋艿:② 审批拒绝的策略? + // TODO @芋艿:③ 配置的可操作列表?(操作权限) + // TODO @芋艿:④ 表单的权限列表? + // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔; // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java index fc72d3f67..0fad3ffa3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -11,6 +11,7 @@ import lombok.Data; @Data public class BpmSimpleModelUpdateReqVO { + // TODO @jason:=> id @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotEmpty(message = "流程模型编号不能为空") private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java index 7341c6c60..38feaf599 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -28,9 +28,6 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate return BpmTaskCandidateStrategyEnum.START_USER; } - /** - * 无需校验参数 - */ @Override public void validateParam(String param) {} @@ -40,11 +37,9 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate return SetUtils.asSet(Long.valueOf(startUserId)); } - /** - * 不需要参数 - */ @Override public boolean isParamRequired() { return false; } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java index 69e983507..3be7bc6e5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; import jakarta.annotation.Resource; @@ -10,23 +11,29 @@ import org.springframework.stereotype.Service; import java.util.Set; +// TODO @jason:类名可以改成 BpmCopyTaskDelegate /** - * 处理抄送用户的代理 + * 处理抄送用户的 {@link JavaDelegate} 的实现类 * * @author jason */ -@Service +@Service // TODO @jason:这种注解,建议用 @Component public class CopyUserDelegate implements JavaDelegate { @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; + @Resource private BpmProcessInstanceCopyService processInstanceCopyService; @Override public void execute(DelegateExecution execution) { - // TODO @芋艿:可能要考虑,系统抄送,没有 taskId 的情况。 + // 1. 获得抄送人 Set userIds = taskCandidateInvoker.calculateUsers(execution); + if (CollUtil.isEmpty(userIds)) { + return; + } + // 2. 执行抄送 FlowElement currentFlowElement = execution.getCurrentFlowElement(); processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), currentFlowElement.getId(), currentFlowElement.getName()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java index 27d6994fd..7078e7c50 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java @@ -10,6 +10,7 @@ import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; +// TODO @jason:微信已经讨论,简化哈 /** * 处理会签 Service Task 代理 * @@ -25,7 +26,7 @@ public class MultiInstanceServiceTaskDelegate implements JavaDelegate { public void execute(DelegateExecution execution) { String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(), - BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); + BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); // TODO @jason:上面不需要加空行哈; Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空"); // 获取会签任务是否被拒绝 Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java index acb689722..53f1ebea7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java @@ -19,6 +19,7 @@ import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum. import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO; +// TODO @jason:微信已经讨论,简化哈 /** * 按拒绝人数计算会签的完成条件的流程表达式实现 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java index 5ecba588c..e2a7252b7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java @@ -4,6 +4,7 @@ import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction; import org.springframework.stereotype.Component; +// TODO @jason:这个自定义转换的原因是啥呀? /** * 根据流程变量 variable 的类型, 转换参数的值 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 9c9176dd8..239cc6e63 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -40,6 +40,7 @@ public interface BpmnModelConstants { */ String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; + // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;2)rejectHandlerType 改成 rejectHandler 哇? /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 89e685467..01d94035d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -73,6 +73,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { }); } + // TODO @jason:这块如果不需要,可以删除掉~~~ // @Override // protected void activityMessageReceived(FlowableMessageEvent event) { // BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index facd40174..f61bbdf6b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; +// TODO @芋艿:这块需要仔细再瞅瞅 /** * 监听定时器触发事件 * @@ -96,7 +97,6 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId()) .setReason("超时系统自动同意"); bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); - } // 自动拒绝 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REJECT) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java index 816e3a71f..67dfae83c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java @@ -7,6 +7,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; +// TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~ /** * 待办任务提醒 Producer * @@ -22,4 +23,5 @@ public class TodoTaskReminderProducer { public void sendReminderMessage(@Valid TodoTaskReminderMessage message) { applicationContext.publishEvent(message); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java similarity index 99% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java index ccf7af949..d8dffc8df 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel; import lombok.Data; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java similarity index 95% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java index 7523fd8b0..9e632fa1d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; @@ -18,12 +18,11 @@ public class SimpleModelUserTaskConfig { /** * 候选人策略 */ - private Integer candidateStrategy; - + private Integer candidateStrategy; /** * 候选人参数 */ - private String candidateParam; + private String candidateParam; /** * 字段权限 @@ -34,11 +33,11 @@ public class SimpleModelUserTaskConfig { * 审批方式 {@link BpmApproveMethodEnum } */ private Integer approveMethod; - /** * 通过比例 当审批方式为 多人会签(按通过比例) 需设置 */ private Integer approveRatio; + /** * 超时处理 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java index fb8be1ef4..e01f71fa5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java @@ -15,7 +15,7 @@ import java.util.Map; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE; - +// TODO @芋艿:这块去研究下! /** * Bpmn 流程表单相关工具方法 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 0cb2c1b4e..3937d34d8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -27,6 +27,7 @@ public class BpmnModelUtils { // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 if (candidateStrategy == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? NumberUtils.parseInt(element.getElementText()) : null; candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null)); } return candidateStrategy; @@ -37,6 +38,7 @@ public class BpmnModelUtils { BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); if (candidateParam == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? element.getElementText() : null; candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null); } return candidateParam; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 8549ac82f..ee72d9629 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -12,9 +12,9 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig.RejectHandler; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelUserTaskConfig; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelUserTaskConfig.RejectHandler; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -253,33 +253,26 @@ public class SimpleModelUtils { return sequenceFlow; } - // TODO-DONE @jason:要不改成 recursionNode 递归节点,然后把 build 名字让出来,专门用于构建各种 Node - // @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow - // TODO-DONE @jason:node 改成 node,process 改成 process;更符合递归的感觉哈,处理当前节点 + // TODO @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { // 判断是否有效节点 - // TODO-DONE @jason:是不是写个 isValidNode 方法:判断是否为有效节点; if (!isValidNode(node)) { return; } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); - // TODO-DONE @jason:要不抽个 buildNode 方法,然后返回一个 List,之后这个方法 addFlowElement;原因是,让当前这个方法,有主干逻辑;不然现在太长了; List flowElements = buildFlowNode(node, nodeType); flowElements.forEach(process::addFlowElement); // 如果不是网关类型的接口, 并且chileNode为空退出 - // TODO-DONE @jason:建议这个判断去掉,可以更简洁一点;因为往下走;如果不成功,本身也就会结束哈;主要是,这里多了一个这样的判断,增加了理解成本; // 如果是“分支”节点,则递归处理条件 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { - // TODO-DONE @jason:可以搞成 stream 写成一行哈 node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); } // 如果有“子”节点,则递归处理子节点 - // TODO-DONE @jason:这个,是不是不写判断,直接继续调用;因为本身 buildAndAddBpmnFlowNode 就会最开始判断了哈,就不重复判断了; traverseNodeToBuildFlowNode(node.getChildNode(), process); } @@ -291,16 +284,13 @@ public class SimpleModelUtils { List list = new ArrayList<>(); switch (nodeType) { case START_NODE: { - // TODO-DONE @jason:每个 nodeType,buildXXX 方法要不更明确,并且去掉 Bpmn; // @芋艿 改成 convert 是不是好理解一点 StartEvent startEvent = convertStartNode(node); list.add(startEvent); break; } case APPROVE_NODE: { - // TODO-DONE @jason:这个,搞成一个 buildUserTask,然后把下面这 2 种节点,搞在一起实现类;这样 buildNode 里面可以更简洁; - // TODO-DONE @jason:这里还有个想法,是不是可以所有的都叫 buildXXXNode,然后里面有一些是 bpmn 相关的构建,叫做 buildBpmnUserTask,用于区分; - // @芋艿 改成 convertXXXNode, , 方面里面使用 buildBpmnXXXNode. 是否更好理解 + // TODO @芋艿 改成 convertXXXNode, , 方面里面使用 buildBpmnXXXNode. 是否更好理解 // 转换审批节点 List flowElements = convertApproveNode(node); list.addAll(flowElements); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index 3b9646f73..2e625f54c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -50,31 +50,6 @@ public interface BpmModelService { */ byte[] getModelBpmnXML(String id); - /** - * 保存流程模型的 BPMN XML - * - * @param id 编号 - * @param xmlBytes BPMN XML bytes - */ - // TODO @芋艿:感觉可以不修改这个方法,而是额外加一个方法;传入 id,bpmn,json; - void saveModelBpmnXml(String id, byte[] xmlBytes); - - /** - * 获得仿钉钉快搭模型的 JSON 数据 - * - * @param id 编号 - * @return JSON bytes - */ - byte[] getModelSimpleJson(String id); - - /** - * 保存仿钉钉快搭模型的 JSON 数据 - * - * @param id 编号 - * @param jsonBytes JSON bytes - */ - void saveModelSimpleJson(String id, byte[] jsonBytes); - /** * 修改流程模型 * @@ -129,6 +104,6 @@ public interface BpmModelService { */ void updateSimpleModel(@Valid BpmSimpleModelUpdateReqVO reqVO); - // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案? + // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案?【重要】 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index b39ec7212..ef8097741 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -107,7 +107,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 保存流程定义 repositoryService.saveModel(model); // 保存 BPMN XML - saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(bpmnXml)); + saveModelBpmnXml(model.getId(), bpmnXml); return model.getId(); } @@ -125,7 +125,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 更新模型 repositoryService.saveModel(model); // 更新 BPMN XML - saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(updateReqVO.getBpmnXml())); + saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml()); } @Override @@ -218,23 +218,24 @@ public class BpmModelServiceImpl implements BpmModelService { if (model == null) { throw exception(MODEL_NOT_EXISTS); } - // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ 获取 仿钉钉快搭模型的JSON 数据 + // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 byte[] jsonBytes = getModelSimpleJson(model.getId()); return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); } @Override public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) { - // 1.1 校验流程模型存在 + // 1. 校验流程模型存在 Model model = getModel(reqVO.getModelId()); if (model == null) { throw exception(MODEL_NOT_EXISTS); } - // 1.2 JSON 转换成 bpmnModel + + // 2.1 JSON 转换成 bpmnModel BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); - // 2.1 保存 Bpmn XML - saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); - // 2.2 保存 JSON 数据 + // 2.2 保存 Bpmn XML + saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); + // 2.3 保存 JSON 数据 saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel())); } @@ -266,21 +267,18 @@ public class BpmModelServiceImpl implements BpmModelService { } } - @Override - public void saveModelBpmnXml(String id, byte[] xmlBytes) { - if (ArrayUtil.isEmpty(xmlBytes)) { + private void saveModelBpmnXml(String id, String bpmnXml) { + if (StrUtil.isEmpty(bpmnXml)) { return; } - repositoryService.addModelEditorSource(id, xmlBytes); + repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); } - @Override - public byte[] getModelSimpleJson(String id) { + private byte[] getModelSimpleJson(String id) { return repositoryService.getModelEditorSourceExtra(id); } - @Override - public void saveModelSimpleJson(String id, byte[] jsonBytes) { + private void saveModelSimpleJson(String id, byte[] jsonBytes) { if (ArrayUtil.isEmpty(jsonBytes)) { return; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index 94df76d4d..7416a87e9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -13,6 +13,8 @@ import java.util.Collection; */ public interface BpmProcessInstanceCopyService { + // TODO @jason:要不把 createProcessInstanceCopy 搞 2 个方法,一个方法参数是之前的 userIds、taskId;一个方法是现在 userIds、processInstanceId、taskId、taskName; + /** * 流程实例的抄送 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 5baa554b2..4404faa6a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -127,23 +127,23 @@ public interface BpmProcessInstanceService { void updateProcessInstanceWhenCancel(FlowableCancelledEvent event); /** - * 更新 ProcessInstance 拓展记录为完成 + * 更新 ProcessInstance 为完成 * * @param instance 流程任务 */ void updateProcessInstanceWhenApprove(ProcessInstance instance); /** - * 更新 ProcessInstance 拓展记录为不通过 + * 更新 ProcessInstance 为不通过 * * @param id 流程编号 - * @param currentActivityId 当前的活动Id + * @param currentActivityId 当前的活动编号 * @param reason 理由。例如说,审批不通过时,需要传递该值 */ void updateProcessInstanceReject(String id, String currentActivityId, String reason); /** - * 当流程结束时候。 更新 ProcessInstance + * 当流程结束时候,更新 ProcessInstance 为通过 * * @param instance 流程任务 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 531ccb538..f23aa57c2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点, 结束流程 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, // 当前节点 endEvent.getId()) // 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 3426cfc69..d4c597ba7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -129,6 +129,7 @@ public interface BpmTaskService { */ Task getTask(String id); + // TODO @jason:jason:这个貌似可以去掉了。 /** * 根据条件查询已经分配的用户任务列表 * @param processInstanceId 流程实例编号,不允许为空 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index ba9b786c8..bcd98a7da 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -335,14 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.2 添加评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); + // 3.1 解析用户任务的拒绝处理类型 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + // TODO @jason:342 到 344 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy UserTask flowElement = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE)); BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); - // 3.2 类型为驳回到指定的任务节点 + // 3.2 类型为驳回到指定的任务节点 TODO @jason:下面这种判断,最好是 JSON if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { + // TODO @jason:348 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy String returnTaskId = BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); + // TODO @jason:这里如果找不到,直接抛出系统异常;因为说白了,已经不是业务异常啦。 if (returnTaskId == null) { throw exception(TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID); } @@ -351,6 +355,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { returnTask(userId, returnReq); return; } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) { + // TODO @jason:微信沟通,去掉类似的逻辑; // 3.3 按拒绝人数终止流程 if (!flowElement.hasMultiInstanceLoopCharacteristics()) { log.error("[rejectTask] 按拒绝人数终止流程类型,只能用于会签任务. 当前任务【{}】不是会签任务", task.getId()); @@ -362,7 +367,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } // 3.4 其他情况 终止流程。 - processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), task.getTaskDefinitionKey(), reqVO.getReason()); + processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), + task.getTaskDefinitionKey(), reqVO.getReason()); } /** From 633a7c50ae151c1a5047fa73758f5cd5e02d6726 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 18 Jun 2024 00:04:10 +0800 Subject: [PATCH 040/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E7=AE=80=E5=8C=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E6=8B=92=E7=BB=9D=E6=B5=81=E7=A8=8B,=20code=20review?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 3 - .../definition/BpmFieldPermissionEnum.java | 1 + .../BpmUserTaskRejectHandlerType.java | 6 +- .../admin/definition/BpmModelController.java | 3 +- .../vo/model/simple/BpmSimpleModelNodeVO.java | 3 +- .../simple/BpmSimpleModelUpdateReqVO.java | 3 +- ...Delegate.java => BpmCopyTaskDelegate.java} | 7 +- .../MultiInstanceServiceTaskDelegate.java | 40 ---------- .../CompleteByRejectCountExpression.java | 74 ------------------- .../core/enums/BpmnModelConstants.java | 10 --- .../core/listener/BpmTaskEventListener.java | 44 ----------- .../flowable/core/util/BpmnModelUtils.java | 18 +++-- .../flowable/core/util/SimpleModelUtils.java | 38 +++------- .../definition/BpmModelServiceImpl.java | 2 +- .../task/BpmProcessInstanceCopyService.java | 8 +- .../BpmProcessInstanceCopyServiceImpl.java | 22 ++++-- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskServiceImpl.java | 36 ++------- 18 files changed, 61 insertions(+), 259 deletions(-) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/{CopyUserDelegate.java => BpmCopyTaskDelegate.java} (85%) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index 3d1010462..e344a2145 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -51,9 +51,6 @@ public interface ErrorCodeConstants { ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务"); ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人"); ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); - ErrorCode TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID = new ErrorCode(1_009_005_015, "回退任务未指定目标任务编号"); - ErrorCode TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR = new ErrorCode(1_009_005_016, "按拒绝人数比例终止流程只能用于会签任务"); - ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); // ========== 动态表单模块 1-009-010-000 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java index 9a10621e7..6cfae78bc 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -14,6 +14,7 @@ import lombok.Getter; public enum BpmFieldPermissionEnum { // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3) + // @芋艿 我看钉钉页面的顺序 是 可编辑 只读 隐藏 WRITE(1, "可编辑"), READ(2, "只读"), NONE(3, "隐藏"); diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index 7a2f50793..2ccab36fc 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -13,12 +13,8 @@ import lombok.Getter; @AllArgsConstructor public enum BpmUserTaskRejectHandlerType { - // TODO @jason:是不是收敛成 2 个:FINISH_PROCESS => 1. 直接结束流程;RETURN_PRE_USER_TASK => 2. 驳回到指定节点(RETURN_USER_TASK【去掉 PRE】) FINISH_PROCESS(1, "终止流程"), - RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"), - - FINISH_PROCESS_BY_REJECT_NUMBER(3, "按拒绝人数终止流程"), // 用于会签 - FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支 + RETURN_USER_TASK(2, "驳回到指定任务节点"); private final Integer type; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index cc3a4514f..4f7e3ccf3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -149,11 +149,10 @@ public class BpmModelController { // ========== 仿钉钉/飞书的精简模型 ========= - // TODO @jason:modelId => id 哈。一般属于自己的模块,可以简化命名。 @GetMapping("/simple/get") @Operation(summary = "获得仿钉钉流程设计模型") @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") - public CommonResult getSimpleModel(@RequestParam("modelId") String modelId){ + public CommonResult getSimpleModel(@RequestParam("id") String modelId){ return success(modelService.getSimpleModel(modelId)); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 7a8686ef4..3a5538684 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -43,10 +43,9 @@ public class BpmSimpleModelNodeVO { @Schema(description = "节点的属性") private Map attributes; // TODO @jason:建议是字段分拆下;类似说: - // TODO @jason:看看是不是可以简化; + // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到 /** * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 - * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。 */ @JsonIgnore private String attachNodeId; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java index 0fad3ffa3..10d967261 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -11,10 +11,9 @@ import lombok.Data; @Data public class BpmSimpleModelUpdateReqVO { - // TODO @jason:=> id @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotEmpty(message = "流程模型编号不能为空") - private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 + private String id; // 对应 Flowable act_re_model 表 ID_ 字段 @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "仿钉钉流程设计模型对象不能为空") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java similarity index 85% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java index 3be7bc6e5..96937b559 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java @@ -7,18 +7,17 @@ import jakarta.annotation.Resource; import org.flowable.bpmn.model.FlowElement; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import java.util.Set; -// TODO @jason:类名可以改成 BpmCopyTaskDelegate /** * 处理抄送用户的 {@link JavaDelegate} 的实现类 * * @author jason */ -@Service // TODO @jason:这种注解,建议用 @Component -public class CopyUserDelegate implements JavaDelegate { +@Component +public class BpmCopyTaskDelegate implements JavaDelegate { @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java deleted file mode 100644 index 7078e7c50..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.BooleanUtil; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; -import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; -import org.flowable.engine.delegate.JavaDelegate; -import org.springframework.stereotype.Component; - -// TODO @jason:微信已经讨论,简化哈 -/** - * 处理会签 Service Task 代理 - * - * @author jason - */ -@Component -public class MultiInstanceServiceTaskDelegate implements JavaDelegate { - - @Resource - private BpmProcessInstanceService processInstanceService; - - @Override - public void execute(DelegateExecution execution) { - - String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(), - BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); // TODO @jason:上面不需要加空行哈; - Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空"); - // 获取会签任务是否被拒绝 - Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class); - // 如果会签任务被拒绝, 终止流程, 跳转到 EndEvent 节点 - if (BooleanUtil.isTrue(userTaskRejected)) { - processInstanceService.updateProcessInstanceReject(execution.getProcessInstanceId(), - execution.getCurrentActivityId(), "会签任务未达到通过比例" ); - } - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java deleted file mode 100644 index 53f1ebea7..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java +++ /dev/null @@ -1,74 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.expression; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.FlowElement; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import java.util.Objects; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.APPROVE_BY_RATIO; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO; - -// TODO @jason:微信已经讨论,简化哈 -/** - * 按拒绝人数计算会签的完成条件的流程表达式实现 - * - * @author jason - */ -@Component -@Slf4j -public class CompleteByRejectCountExpression { - - /** - * 会签的完成条件 - */ - public boolean completionCondition(DelegateExecution execution) { - FlowElement flowElement = execution.getCurrentFlowElement(); - // 实例总数 - Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances"); - // 完成的实例数 - Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances"); - // 审批方式 - Integer approveMethod = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_METHOD)); - Assert.notNull(approveMethod, "审批方式不能空"); - if (!Objects.equals(APPROVE_BY_RATIO.getMethod(), approveMethod)) { - log.error("[completionCondition] the execution is [{}] 审批方式[{}] 不匹配", execution, approveMethod); - throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION); - } - // 获取拒绝人数 - // TODO @jason:CollUtil.filter().size();貌似可以更简洁 @芋艿 CollUtil.filter().size() 使用这个会报错,好坑了. - Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(), - item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0, - Integer::sum, 0); - // 同意人数: 完成人数 - 拒绝人数 - int agreeCount = nrOfCompletedInstances - rejectCount; - // 多人会签(按通过比例) - Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO)); - Assert.notNull(approveRatio, "通过比例不能空"); - // 判断通过比例 - double approvePct = approveRatio / (double) 100; - double realApprovePct = (double) agreeCount / nrOfInstances; - if (realApprovePct >= approvePct) { - return true; - } - double rejectPct = (100 - approveRatio) / (double) 100; - double realRejectPct = (double) rejectCount / nrOfInstances; - // 判断拒绝比例 - if (realRejectPct > rejectPct) { - execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE); - return true; - } - return false; - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 239cc6e63..20d6d9d9d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -56,16 +56,6 @@ public interface BpmnModelConstants { */ String USER_TASK_APPROVE_METHOD = "approveMethod"; - /** - * BPMN UserTask 的扩展属性,当审批方式为按通过比例时, 标记会签通过比例 - */ - String USER_TASK_APPROVE_RATIO = "approveRatio"; - - /** - * BPMN ExtensionElement 的扩展属性,用于标记 服务任务附属的用户任务 Id - */ - String SERVICE_TASK_ATTACH_USER_TASK_ID = "attachUserTaskId"; - /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 01d94035d..c6434f707 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -73,48 +73,4 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { }); } - // TODO @jason:这块如果不需要,可以删除掉~~~ -// @Override -// protected void activityMessageReceived(FlowableMessageEvent event) { -// BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId()); -// FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId()); -// if (element instanceof BoundaryEvent) { -// BoundaryEvent boundaryEvent = (BoundaryEvent) element; -// String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); -// // 如果自定义类型为拒绝后处理,进行拒绝处理 -// if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) { -// String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE); -// rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType)); -// } -// } -// } -// -// private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) { -// BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); -// if (userTaskRejectHandlerType != null) { -// List taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey); -// taskList.forEach(task -> { -// Integer taskStatus = FlowableUtils.getTaskStatus(task); -// // 只有处于拒绝状态下才处理 -// if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) { -// // 终止流程 -// if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) { -// processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task)); -// return; -// } -// // 驳回 -// if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { -// String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID); -// if (returnTaskId != null) { -// BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId()) -// .setTargetTaskDefinitionKey(returnTaskId) -// .setReason("任务拒绝回退"); -// taskService.returnTask(getLoginUserId(), reqVO); -// } -// } -// } -// }); -// } -// } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 3937d34d8..5bdea7bb9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; @@ -27,8 +28,7 @@ public class BpmnModelUtils { // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 if (candidateStrategy == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); - // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? NumberUtils.parseInt(element.getElementText()) : null; - candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null)); + candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null; } return candidateStrategy; } @@ -38,18 +38,26 @@ public class BpmnModelUtils { BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); if (candidateParam == null) { ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); - // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? element.getElementText() : null; - candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null); + candidateParam = element != null ? element.getElementText() : null; } return candidateParam; } + public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { + Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); + return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); + } + + public static String parseReturnTaskId(FlowElement flowElement) { + return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); + } + public static String parseExtensionElement(FlowElement flowElement, String elementName) { if (flowElement == null) { return null; } ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); - return Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null); + return element != null ? element.getElementText() : null; } // TODO @jason:貌似这个没地方调用??? @芋艿 在 BpmTaskConvert里面。暂时注释掉了。 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index ee72d9629..bc6a48426 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -26,7 +26,6 @@ import java.util.Objects; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; @@ -55,9 +54,9 @@ public class SimpleModelUtils { public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; /** - * 按拒绝人数计算多实例完成条件的表达式 + * 按通过比例完成表达式 */ - public static final String COMPLETE_BY_REJECT_COUNT_EXPRESSION = "${completeByRejectCountExpression.completionCondition(execution)}"; + public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; // TODO-DONE @jason:建议方法名,改成 buildBpmnModel // TODO @yunai:注释需要完善下; @@ -185,8 +184,9 @@ public class SimpleModelUtils { } /** - * 构建有附加节点的连线 - * @param nodeId 当前节点 Id + * 构建有附加节点的连线 + * + * @param nodeId 当前节点 Id * @param attachNodeId 附属节点 Id * @param targetNodeId 目标节点 Id */ @@ -344,28 +344,9 @@ public class SimpleModelUtils { BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); flowElements.add(boundaryEvent); } - // 如果按拒绝人数终止流程。需要添加附加的 ServiceTask 处理 - if (userTaskConfig.getRejectHandler() != null && - Objects.equals(FINISH_PROCESS_BY_REJECT_NUMBER.getType(), userTaskConfig.getRejectHandler().getType())) { - ServiceTask serviceTask = buildMultiInstanceServiceTask(node); - flowElements.add(serviceTask); - } return flowElements; } - private static ServiceTask buildMultiInstanceServiceTask(BpmSimpleModelNodeVO node) { - ServiceTask serviceTask = new ServiceTask(); - String id = String.format("Activity-%s", IdUtil.fastSimpleUUID()); - serviceTask.setId(id); - serviceTask.setName("会签服务任务"); - serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - serviceTask.setImplementation("${multiInstanceServiceTaskDelegate}"); - serviceTask.setAsynchronous(false); - addExtensionElement(serviceTask, SERVICE_TASK_ATTACH_USER_TASK_ID, node.getId()); - node.setAttachNodeId(id); - return serviceTask; - } - private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); @@ -406,7 +387,7 @@ public class SimpleModelUtils { serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - serviceTask.setImplementation("${copyUserDelegate}"); + serviceTask.setImplementation("${bpmCopyTaskDelegate}"); // 添加抄送候选人元素 addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), @@ -515,11 +496,10 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) { - multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION); - multiInstanceCharacteristics.setSequential(false); Assert.notNull(approveRatio, "通过比例不能为空"); - // 添加通过比例的扩展属性 - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_RATIO, approveRatio.toString()); + double approvePct = approveRatio / (double) 100; + multiInstanceCharacteristics.setCompletionCondition(String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approvePct))); + multiInstanceCharacteristics.setSequential(false); } userTask.setLoopCharacteristics(multiInstanceCharacteristics); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index ef8097741..d81bbcae5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -226,7 +226,7 @@ public class BpmModelServiceImpl implements BpmModelService { @Override public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) { // 1. 校验流程模型存在 - Model model = getModel(reqVO.getModelId()); + Model model = getModel(reqVO.getId()); if (model == null) { throw exception(MODEL_NOT_EXISTS); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index 7416a87e9..bd9e531ce 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -13,7 +13,13 @@ import java.util.Collection; */ public interface BpmProcessInstanceCopyService { - // TODO @jason:要不把 createProcessInstanceCopy 搞 2 个方法,一个方法参数是之前的 userIds、taskId;一个方法是现在 userIds、processInstanceId、taskId、taskName; + /** + * 流程实例的抄送 + * + * @param userIds 抄送的用户编号 + * @param taskId 流程任务编号 + */ + void createProcessInstanceCopy(Collection userIds, String taskId); /** * 流程实例的抄送 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index 940327cb5..1edbd39a7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.service.task; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; @@ -10,6 +11,7 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -44,21 +46,25 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @Lazy // 延迟加载,避免循环依赖 private BpmProcessDefinitionService processDefinitionService; + @Override + public void createProcessInstanceCopy(Collection userIds, String taskId) { + Task task = taskService.getTask(taskId); + if (ObjectUtil.isNull(task)) { + throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); + } + String processInstanceId = task.getProcessInstanceId(); + createProcessInstanceCopy(userIds, processInstanceId, task.getId(), task.getName()); + } + // TODO @芋艿:这里多加了一个 name; @Override public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { - // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(UserTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行 -// Task task = taskService.getTask(taskId); -// if (ObjectUtil.isNull(task)) { -// throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); -// } - // 1.2 校验流程实例存在 -// String processInstanceId = task.getProcessInstanceId(); + // 1.1 校验流程实例存在 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } - // 1.3 校验流程定义存在 + // 1.2 校验流程定义存在 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( processInstance.getProcessDefinitionId()); if (processDefinition == null) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index f23aa57c2..7bc6304e9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 // @芋艿,如果是未完成的会签任务。会被取消, 流程会结束。 但是貌似并行任务的时候。流程是不会结束的。任务还是在进行中的 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index bcd98a7da..25de22be1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -54,8 +54,6 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID; /** * 流程任务实例 Service 实现类 @@ -189,8 +187,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2. 抄送用户 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(), - reqVO.getId(), task.getName()); + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); } // 情况一:被委派的任务,不调用 complete 去完成任务 @@ -338,35 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 3.1 解析用户任务的拒绝处理类型 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); - // TODO @jason:342 到 344 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy - UserTask flowElement = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE)); - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); - // 3.2 类型为驳回到指定的任务节点 TODO @jason:下面这种判断,最好是 JSON - if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) { - // TODO @jason:348 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy - String returnTaskId = BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); - // TODO @jason:这里如果找不到,直接抛出系统异常;因为说白了,已经不是业务异常啦。 - if (returnTaskId == null) { - throw exception(TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID); - } + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); + // 3.2 类型为驳回到指定的任务节点 + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { + String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); + Assert.notNull(returnTaskId, "回退的节点不能为空"); BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId) .setReason(reqVO.getReason()); returnTask(userId, returnReq); return; - } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) { - // TODO @jason:微信沟通,去掉类似的逻辑; - // 3.3 按拒绝人数终止流程 - if (!flowElement.hasMultiInstanceLoopCharacteristics()) { - log.error("[rejectTask] 按拒绝人数终止流程类型,只能用于会签任务. 当前任务【{}】不是会签任务", task.getId()); - throw new IllegalStateException("按拒绝人数终止流程类型,只能用于会签任务"); - } - // 设置变量值为拒绝 - runtimeService.setVariableLocal(task.getExecutionId(), BpmConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus()); - taskService.complete(task.getId()); - return; } - // 3.4 其他情况 终止流程。 + // 3.3 其他情况 终止流程。 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), task.getTaskDefinitionKey(), reqVO.getReason()); } From 4d49952c52a299e9ead659dbc415df08c8d2994b Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 19 Jun 2024 17:06:48 +0800 Subject: [PATCH 041/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=8A=A0=E7=AD=BE=E6=8B=92?= =?UTF-8?q?=E7=BB=9D=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmFieldPermissionEnum.java | 6 +- .../bpm/enums/task/BpmCommentTypeEnum.java | 1 + .../bpm/enums/task/BpmDeleteReasonEnum.java | 1 + .../core/listener/BpmTaskEventListener.java | 33 ++++----- .../listener/BpmTimerFiredEventListener.java | 3 +- .../task/BpmProcessInstanceService.java | 7 +- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 7 +- .../bpm/service/task/BpmTaskServiceImpl.java | 69 +++++++++++++++---- 9 files changed, 85 insertions(+), 44 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java index 6cfae78bc..5a9b4b26a 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -13,10 +13,8 @@ import lombok.Getter; @AllArgsConstructor public enum BpmFieldPermissionEnum { - // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3) - // @芋艿 我看钉钉页面的顺序 是 可编辑 只读 隐藏 - WRITE(1, "可编辑"), - READ(2, "只读"), + READ(1, "只读"), + WRITE(2, "可编辑"), NONE(3, "隐藏"); /** diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java index 8b7b7ce11..f0a607c5a 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java @@ -22,6 +22,7 @@ public enum BpmCommentTypeEnum { TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"), ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"), SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), + REJECT_BY_ADD_SIGN_TASK_REJECT("10", "不通过","系统自动不通过,原因是:加签任务不通过") ; /** diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java index 802b9d890..fb02d6d65 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java @@ -22,6 +22,7 @@ public enum BpmDeleteReasonEnum { // ========== 流程任务的独有原因 ========== CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 + AUTO_REJECT_BY_ADD_SIGN_REJECT("系统自动拒绝,原因:加签任务被拒绝") // 加签任务审批不通过,导致任务不通过 ; private final String reason; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index c6434f707..38c9bd2c4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; @@ -11,12 +9,10 @@ import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; -import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Set; /** @@ -38,8 +34,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { public static final Set TASK_EVENTS = ImmutableSet.builder() .add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_ASSIGNED) - //.add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 -// .add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED) +// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 .add(FlowableEngineEventType.ACTIVITY_CANCELLED) .build(); @@ -59,18 +54,18 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { - List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); - if (CollUtil.isEmpty(activityList)) { - log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); - return; - } - // 遍历处理 - activityList.forEach(activity -> { - if (StrUtil.isEmpty(activity.getTaskId())) { - return; - } - taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); - }); + // @芋艿。 这里是不是就可以不要了, 取消的任务状态,在rejectTask 里面做了, 如果在 updateTaskStatusWhenCanceled 里面修改会报错。 +// List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); +// if (CollUtil.isEmpty(activityList)) { +// log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); +// return; +// } +// // 遍历处理 +// activityList.forEach(activity -> { +// if (StrUtil.isEmpty(activity.getTaskId())) { +// return; +// } +// taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); +// }); } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index f61bbdf6b..1076f13d0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -81,7 +81,8 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); if (userTaskTimeoutAction != null) { // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? - List taskList = bpmTaskService.getTaskListByProcessInstanceIdAndAssigned(processInstanceId, null, taskDefKey); + List taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, + null, taskDefKey); taskList.forEach(task -> { // 自动提醒 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 4404faa6a..b1cfae3ae 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -136,11 +136,12 @@ public interface BpmProcessInstanceService { /** * 更新 ProcessInstance 为不通过 * - * @param id 流程编号 - * @param currentActivityId 当前的活动编号 + * @param processInstance 流程实例 + * @param activityIds 当前未完成活动节点 Id + * @param endId 结束节点 Id * @param reason 理由。例如说,审批不通过时,需要传递该值 */ - void updateProcessInstanceReject(String id, String currentActivityId, String reason); + void updateProcessInstanceReject(ProcessInstance processInstance, List activityIds, String endId, String reason); /** * 当流程结束时候,更新 ProcessInstance 为通过 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 7bc6304e9..e5cb92cca 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 // @芋艿,如果是未完成的会签任务。会被取消, 流程会结束。 但是貌似并行任务的时候。流程是不会结束的。任务还是在进行中的 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, List activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index d4c597ba7..3adb56858 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -129,14 +129,15 @@ public interface BpmTaskService { */ Task getTask(String id); - // TODO @jason:jason:这个貌似可以去掉了。 /** - * 根据条件查询已经分配的用户任务列表 + * 根据条件查询正在进行中的任务 + * * @param processInstanceId 流程实例编号,不允许为空 + * @param assigned 是否分配了审批人 * @param executionId execution Id * @param taskDefineKey 任务定义 Key */ - List getTaskListByProcessInstanceIdAndAssigned(String processInstanceId, String executionId, String taskDefineKey); + List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String executionId, String taskDefineKey); /** * 获取当前任务的可回退的 UserTask 集合 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 25de22be1..4f6cdfaba 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -1,11 +1,9 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; @@ -28,6 +26,7 @@ import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; @@ -54,6 +53,8 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum.REJECT_BY_ADD_SIGN_TASK_REJECT; +import static cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum.AUTO_REJECT_BY_ADD_SIGN_REJECT; /** * 流程任务实例 Service 实现类 @@ -326,18 +327,40 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (instance == null) { throw exception(PROCESS_INSTANCE_NOT_EXISTS); } - - // 2.1 更新流程实例为不通过 + // 2.1 更新流程任务为不通过 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); // 2.2 添加评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); - // 3.1 解析用户任务的拒绝处理类型 + // 3.1 如果是被加签任务且是后加签。 更新加签任务状态为取消 + if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) { + List childTaskList = getTaskListByParentTaskId(task.getId()); + updateTaskStatusWhenCanceled(childTaskList, reqVO.getReason()); + } + // 3.2 如果是加签的任务 + if (StrUtil.isNotEmpty(task.getParentTaskId())) { + Task signTask = validateTaskExist(task.getParentTaskId()); + // 3.2.1 更新被加签的任务为不通过 + if (BpmTaskSignTypeEnum.BEFORE.getType().equals(signTask.getScopeType())) { + updateTaskStatusAndReason(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus(), AUTO_REJECT_BY_ADD_SIGN_REJECT.getReason()); + } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(signTask.getScopeType())) { + updateTaskStatus(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus()); + // 后加签 不添加拒绝意见。因为会把原来的意见覆盖. + } + // 3.2.2 添加评论 + taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), + BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(REJECT_BY_ADD_SIGN_TASK_REJECT)); + // 3.2.3 更新还在进行中的加签任务状态为取消 + List addSignTaskList = getTaskListByParentTaskId(task.getParentTaskId()); + updateTaskStatusWhenCanceled(CollectionUtils.filterList(addSignTaskList, item -> !item.getId().equals(task.getId())), + reqVO.getReason()); + } + + // 4.1 驳回到指定的任务节点 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); - // 3.2 类型为驳回到指定的任务节点 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); Assert.notNull(returnTaskId, "回退的节点不能为空"); @@ -346,9 +369,25 @@ public class BpmTaskServiceImpl implements BpmTaskService { returnTask(userId, returnReq); return; } - // 3.3 其他情况 终止流程。 - processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), - task.getTaskDefinitionKey(), reqVO.getReason()); + + // 4.2.1 更新其它正在运行的任务状态为取消。需要过滤掉当前任务和被加签的任务 + List taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), false, null, null); + updateTaskStatusWhenCanceled(CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())), + reqVO.getReason()); + // 4.2.2 终止流程 + List activityIds = convertList(taskList, Task::getTaskDefinitionKey); + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); + Assert.notNull(endEvent, "结束节点不能未空"); + processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason()); + } + + private void updateTaskStatusWhenCanceled(List taskList, String reason) { + taskList.forEach(task -> { + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); + taskService.addComment(task.getId(), task.getProcessInstanceId(), + BpmCommentTypeEnum.CANCEL.getType(), BpmCommentTypeEnum.CANCEL.formatComment(reason)); + + }); } /** @@ -399,6 +438,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public void updateTaskStatusWhenCanceled(String taskId) { + // @芋艿。这里是不是可以不要了,要不然。 updateTaskStatusAndReason 会报错 task 已经删除了。 Task task = getTask(taskId); // 1. 可能只是活动,不是任务,所以查询不到 if (task == null) { @@ -450,10 +490,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { } @Override - public List getTaskListByProcessInstanceIdAndAssigned(String processInstanceId, String executionId, String defineKey) { + public List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String executionId, String defineKey) { Assert.notNull(processInstanceId, "processInstanceId 不能为空"); - TaskQuery taskQuery = taskService.createTaskQuery().taskAssigned().processInstanceId(processInstanceId).active() + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active() .includeTaskLocalVariables(); + if (BooleanUtil.isTrue(assigned)) { + taskQuery.taskAssigned(); + } if (StrUtil.isNotEmpty(executionId)) { taskQuery.executionId(executionId); } From 9e41576dc47bcf9a7d94fc61dcece8dbec966f21 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 19 Jun 2024 23:51:54 +0800 Subject: [PATCH 042/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=A8=A1=E5=9E=8B=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E7=BB=93=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmApproveMethodEnum.java | 12 +++- .../BpmUserTaskRejectHandlerType.java | 11 +++- .../BpmUserTaskTimeoutActionEnum.java | 12 +++- .../vo/model/simple/BpmSimpleModelNodeVO.java | 63 +++++++++++++++++++ .../enums/BpmTaskCandidateStrategyEnum.java | 12 +++- .../flowable/core/util/SimpleModelUtils.java | 27 ++++---- .../bpm/service/task/BpmTaskServiceImpl.java | 6 +- 7 files changed, 122 insertions(+), 21 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index 736b0ceed..b57cb85ff 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.bpm.enums.definition; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * BPM 多人审批方式的枚举 * @@ -11,7 +14,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BpmApproveMethodEnum { +public enum BpmApproveMethodEnum implements IntArrayValuable { RANDOM_SELECT_ONE_APPROVE(1, "随机挑选一人审批"), APPROVE_BY_RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) @@ -22,13 +25,20 @@ public enum BpmApproveMethodEnum { * 审批方式 */ private final Integer method; + /** * 名字 */ private final String name; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmApproveMethodEnum::getMethod).toArray(); + public static BpmApproveMethodEnum valueOf(Integer method) { return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); } + @Override + public int[] array() { + return ARRAYS; + } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index 2ccab36fc..6c80645e8 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.bpm.enums.definition; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * BPM 用户任务拒绝处理类型枚举 * @@ -11,7 +14,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BpmUserTaskRejectHandlerType { +public enum BpmUserTaskRejectHandlerType implements IntArrayValuable { FINISH_PROCESS(1, "终止流程"), RETURN_USER_TASK(2, "驳回到指定任务节点"); @@ -19,8 +22,14 @@ public enum BpmUserTaskRejectHandlerType { private final Integer type; private final String name; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray(); + public static BpmUserTaskRejectHandlerType typeOf(Integer type) { return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); } + @Override + public int[] array() { + return ARRAYS; + } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java index cc4d06d9d..fb955808d 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.bpm.enums.definition; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * 用户任务超时处理执行动作枚举 * @@ -11,7 +14,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BpmUserTaskTimeoutActionEnum { +public enum BpmUserTaskTimeoutActionEnum implements IntArrayValuable { AUTO_REMINDER(1,"自动提醒"), AUTO_APPROVE(2, "自动同意"), @@ -20,7 +23,14 @@ public enum BpmUserTaskTimeoutActionEnum { private final Integer action; private final String name; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutActionEnum::getAction).toArray(); + public static BpmUserTaskTimeoutActionEnum actionOf(Integer action) { return ArrayUtil.firstMatch(item -> item.getAction().equals(action), values()); } + + @Override + public int[] array() { + return ARRAYS; + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 3a5538684..8248d5658 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -1,7 +1,11 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; @@ -50,6 +54,64 @@ public class BpmSimpleModelNodeVO { @JsonIgnore private String attachNodeId; + @Schema(description = "候选人策略", example = "30") + @InEnum(BpmTaskCandidateStrategyEnum.class) + private Integer candidateStrategy; // 用于审批,抄送节点 + + @Schema(description = "候选人参数") + private String candidateParam; // 用于审批,抄送节点 + + @Schema(description = "多人审批方式", example = "1") + @InEnum(BpmApproveMethodEnum.class) // 用于审批节点 + private Integer approveMethod; + + @Schema(description = "表单权限", example = "[]") + private List> fieldsPermission; + + @Schema(description = "通过比例", example = "100") + private Integer approveRatio; // 通过比例 当多人审批方式为:多人会签(按通过比例) 需要设置 + + /** + * 审批节点拒绝处理 + */ + private RejectHandler rejectHandler; + + /** + * 审批节点超时处理 + */ + private TimeoutHandler timeoutHandler; + + @Data + @Schema(description = "审批节点拒绝处理策略") + public static class RejectHandler { + + @Schema(description = "拒绝处理类型", example = "1") + @InEnum(BpmUserTaskRejectHandlerType.class) + private Integer type; + + @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1") + private String returnNodeId; + } + + @Data + @Schema(description = "审批节点超时处理策略") + public static class TimeoutHandler { + + @Schema(description = "是否开启超时处理", example = "false") + private Boolean enable; + + @Schema(description = "任务超时未处理的行为", example = "1") + @InEnum(BpmUserTaskTimeoutActionEnum.class) + private Integer action; + + @Schema(description = "超时时间", example = "PT6H") + private String timeDuration; + + @Schema(description = "最大提醒次数", example = "1") + private Integer maxRemindCount; + } + + // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级 @@ -61,4 +123,5 @@ public class BpmSimpleModelNodeVO { // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔; // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index 596ff73f1..78628d0da 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * BPM 任务的候选人策略枚举 * @@ -13,7 +16,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BpmTaskCandidateStrategyEnum { +public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { ROLE(10, "角色"), DEPT_MEMBER(20, "部门的成员"), // 包括负责人 @@ -26,6 +29,8 @@ public enum BpmTaskCandidateStrategyEnum { EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager ; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray(); + /** * 类型 */ @@ -39,4 +44,9 @@ public enum BpmTaskCandidateStrategyEnum { return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values()); } + @Override + public int[] array() { + return ARRAYS; + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index bc6a48426..8e3ce9ed9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -8,13 +8,12 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelUserTaskConfig; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelUserTaskConfig.RejectHandler; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -24,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; @@ -336,18 +336,17 @@ public class SimpleModelUtils { private static List convertApproveNode(BpmSimpleModelNodeVO node) { List flowElements = new ArrayList<>(); - SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(node.getAttributes(), SimpleModelUserTaskConfig.class); - UserTask userTask = buildBpmnUserTask(node, userTaskConfig); + UserTask userTask = buildBpmnUserTask(node); flowElements.add(userTask); - if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 - BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); + BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); flowElements.add(boundaryEvent); } return flowElements; } - private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); boundaryEvent.setId("Event-" + IdUtil.fastUUID()); @@ -444,26 +443,26 @@ public class SimpleModelUtils { return inclusiveGateway; } - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) { + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); // 设置审批任务的截止时间 - if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { - userTask.setDueDate(userTaskConfig.getTimeoutHandler().getTimeDuration()); + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { + userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); } // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批 // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? // 添加候选人元素 - addCandidateElements(userTaskConfig.getCandidateStrategy(), userTaskConfig.getCandidateParam(), userTask); + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); // 添加表单字段权限属性元素 - addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask); + addFormFieldsPermission(node.getFieldsPermission(), userTask); // 处理多实例 - processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTaskConfig.getApproveRatio(), userTask); + processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); // 添加任务被拒绝的处理元素 - addTaskRejectElements(userTaskConfig.getRejectHandler(), userTask); + addTaskRejectElements(node.getRejectHandler(), userTask); return userTask; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 4f6cdfaba..53611f99e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -350,7 +350,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 3.2.2 添加评论 taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), - BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(REJECT_BY_ADD_SIGN_TASK_REJECT)); + BpmCommentTypeEnum.REJECT.getType(), REJECT_BY_ADD_SIGN_TASK_REJECT.getComment()); // 3.2.3 更新还在进行中的加签任务状态为取消 List addSignTaskList = getTaskListByParentTaskId(task.getParentTaskId()); updateTaskStatusWhenCanceled(CollectionUtils.filterList(addSignTaskList, item -> !item.getId().equals(task.getId())), @@ -375,10 +375,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { updateTaskStatusWhenCanceled(CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())), reqVO.getReason()); // 4.2.2 终止流程 - List activityIds = convertList(taskList, Task::getTaskDefinitionKey); + Set activityIds = convertSet(taskList, Task::getTaskDefinitionKey); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能未空"); - processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason()); + processInstanceService.updateProcessInstanceReject(instance, CollUtil.newArrayList(activityIds), endEvent.getId(), reqVO.getReason()); } private void updateTaskStatusWhenCanceled(List taskList, String reason) { From 234df7bda1539ba567182ce76ab8a69244f10490 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 21 Jun 2024 10:01:13 +0800 Subject: [PATCH 043/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=8A=84=E9=80=81=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E7=BB=93=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/flowable/core/util/SimpleModelUtils.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 8e3ce9ed9..240f2bd2a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -389,16 +389,11 @@ public class SimpleModelUtils { serviceTask.setImplementation("${bpmCopyTaskDelegate}"); // 添加抄送候选人元素 - addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM), - serviceTask); + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); // 添加表单字段权限属性元素 // TODO @芋艿:这块关注下哈; - List> fieldsPermissions = MapUtil.get(node.getAttributes(), - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { - }); - addFormFieldsPermission(fieldsPermissions, serviceTask); + addFormFieldsPermission(node.getFieldsPermission(), serviceTask); return serviceTask; } From 5519fdcd4cbc67c0c458de801b727bf06ebf2fbf Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Jun 2024 11:28:08 +0800 Subject: [PATCH 044/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9Areview=20=E5=BF=AB=E6=90=AD?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/task/BpmCommentTypeEnum.java | 1 + .../module/bpm/enums/task/BpmDeleteReasonEnum.java | 1 + .../vo/model/simple/BpmSimpleModelNodeVO.java | 11 +++++------ .../flowable/core/listener/BpmTaskEventListener.java | 1 + .../task/BpmProcessInstanceCopyServiceImpl.java | 1 - .../module/bpm/service/task/BpmTaskServiceImpl.java | 4 +++- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java index f0a607c5a..498b601c2 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java @@ -22,6 +22,7 @@ public enum BpmCommentTypeEnum { TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"), ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"), SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), + // TODO @芋艿:这个枚举状态,需要关注下! REJECT_BY_ADD_SIGN_TASK_REJECT("10", "不通过","系统自动不通过,原因是:加签任务不通过") ; diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java index fb02d6d65..1c2382c79 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java @@ -22,6 +22,7 @@ public enum BpmDeleteReasonEnum { // ========== 流程任务的独有原因 ========== CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 + // TODO @芋艿:这个枚举状态,需要关注下! AUTO_REJECT_BY_ADD_SIGN_REJECT("系统自动拒绝,原因:加签任务被拒绝") // 加签任务审批不通过,导致任务不通过 ; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 8248d5658..d0857c13d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -56,20 +56,20 @@ public class BpmSimpleModelNodeVO { @Schema(description = "候选人策略", example = "30") @InEnum(BpmTaskCandidateStrategyEnum.class) - private Integer candidateStrategy; // 用于审批,抄送节点 + private Integer candidateStrategy; // 用于审批,抄送节点 @Schema(description = "候选人参数") - private String candidateParam; // 用于审批,抄送节点 + private String candidateParam; // 用于审批,抄送节点 @Schema(description = "多人审批方式", example = "1") - @InEnum(BpmApproveMethodEnum.class) // 用于审批节点 - private Integer approveMethod; + @InEnum(BpmApproveMethodEnum.class) + private Integer approveMethod; // 用于审批节点 @Schema(description = "表单权限", example = "[]") private List> fieldsPermission; @Schema(description = "通过比例", example = "100") - private Integer approveRatio; // 通过比例 当多人审批方式为:多人会签(按通过比例) 需要设置 + private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置 /** * 审批节点拒绝处理 @@ -123,5 +123,4 @@ public class BpmSimpleModelNodeVO { // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔; // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 38c9bd2c4..a8669bd3e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -54,6 +54,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { + // TODO @jason:如果用户主动取消,可能需要考虑这个 // @芋艿。 这里是不是就可以不要了, 取消的任务状态,在rejectTask 里面做了, 如果在 updateTaskStatusWhenCanceled 里面修改会报错。 // List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); // if (CollUtil.isEmpty(activityList)) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index 1edbd39a7..2b110d11d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -56,7 +56,6 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy createProcessInstanceCopy(userIds, processInstanceId, task.getId(), task.getName()); } - // TODO @芋艿:这里多加了一个 name; @Override public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { // 1.1 校验流程实例存在 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 53611f99e..e459844a9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -371,8 +371,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 4.2.1 更新其它正在运行的任务状态为取消。需要过滤掉当前任务和被加签的任务 + // TODO @jason:如果过滤掉被加签的任务,这些任务被对应的审批人看到是啥状态哈? List taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), false, null, null); - updateTaskStatusWhenCanceled(CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())), + updateTaskStatusWhenCanceled( + CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())), reqVO.getReason()); // 4.2.2 终止流程 Set activityIds = convertSet(taskList, Task::getTaskDefinitionKey); From 053e03d068e17889f28017e889062acda132b263 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 27 Jun 2024 09:21:22 +0800 Subject: [PATCH 045/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=A1=A8=E5=8D=95=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=9D=83=E9=99=90=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/task/vo/task/BpmTaskRespVO.java | 2 ++ .../bpm/convert/task/BpmTaskConvert.java | 21 ++++++------- .../core/listener/BpmTaskEventListener.java | 30 +++++++++++-------- .../flowable/core/util/BpmnModelUtils.java | 7 ++--- .../bpm/service/task/BpmTaskServiceImpl.java | 4 ++- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java index 7f5177b94..4e17bf974 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -67,6 +67,8 @@ public class BpmTaskRespVO { private List formFields; @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) private Map formVariables; + @Schema(description = "表单字段权限值") + private Map fieldsPermission; @Data @Schema(description = "流程实例") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 2a04d712d..87f59f518 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -9,6 +9,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; @@ -86,7 +88,8 @@ public interface BpmTaskConvert { BpmnModel bpmnModel) { List taskVOList = CollectionUtils.convertList(taskList, task -> { BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); - taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); + Integer taskStatus = FlowableUtils.getTaskStatus(task); + taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); // 流程实例 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); @@ -94,12 +97,6 @@ public interface BpmTaskConvert { // 表单信息 BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); if (form != null) { - // 测试一下权限处理 - // TODO @jason:这里是不是还没实现完哈? - // TODO @芋艿 测试了一下。 暂时注释掉。 前端不知道要怎样改, 可能需要讨论一下如何改 -// List afterChangedFields = BpmnFormUtils.changeCreateFormFiledPermissionRule(form.getFields(), -// BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); - taskVO.setFormId(form.getId()).setFormName(form.getName()).setFormConf(form.getConf()) .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); } @@ -114,7 +111,11 @@ public interface BpmTaskConvert { taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); } - return taskVO; + if(BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ + // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限 + taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); + } + return taskVO; }); // 拼接父子关系 @@ -159,12 +160,12 @@ public interface BpmTaskConvert { /** * 将父任务的属性,拷贝到子任务(加签任务) - * + *

* 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。 * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。 * * @param parentTask 父任务 - * @param childTask 加签任务 + * @param childTask 加签任务 */ default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) { childTask.setName(parentTask.getName()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index a8669bd3e..a72e2924c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; @@ -9,10 +11,12 @@ import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; +import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Set; /** @@ -55,18 +59,20 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { // TODO @jason:如果用户主动取消,可能需要考虑这个 + // TODO @芋艿 如果在 rejectTask 处理了。 这里又要查询一次。感觉有点多余。 主动取消是不是也要处理一下。要不然有加签的任务也会报错 // @芋艿。 这里是不是就可以不要了, 取消的任务状态,在rejectTask 里面做了, 如果在 updateTaskStatusWhenCanceled 里面修改会报错。 -// List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); -// if (CollUtil.isEmpty(activityList)) { -// log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); -// return; -// } -// // 遍历处理 -// activityList.forEach(activity -> { -// if (StrUtil.isEmpty(activity.getTaskId())) { -// return; -// } -// taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); -// }); + List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); + if (CollUtil.isEmpty(activityList)) { + log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); + return; + } + // 遍历处理 + activityList.forEach(activity -> { + if (StrUtil.isEmpty(activity.getTaskId())) { + return; + } + taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); + }); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 5bdea7bb9..e0c9d6ae2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -60,19 +60,18 @@ public class BpmnModelUtils { return element != null ? element.getElementText() : null; } - // TODO @jason:貌似这个没地方调用??? @芋艿 在 BpmTaskConvert里面。暂时注释掉了。 - public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { + public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); if (flowElement == null) { return null; } - Map fieldsPermission = MapUtil.newHashMap(); + Map fieldsPermission = MapUtil.newHashMap(); List extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); extensionElements.forEach(element -> { String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { - fieldsPermission.put(field, Integer.parseInt(permission)); + fieldsPermission.put(field, permission); } }); return fieldsPermission; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index e459844a9..7f1e08582 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -213,6 +213,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 if (CollUtil.isNotEmpty(reqVO.getVariables())) { Map variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); + // 修改表单的值需要存储到 ProcessInstance 变量 + runtimeService.setVariables(task.getProcessInstanceId(), variables); taskService.complete(task.getId(), variables, true); } else { taskService.complete(task.getId()); @@ -371,7 +373,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 4.2.1 更新其它正在运行的任务状态为取消。需要过滤掉当前任务和被加签的任务 - // TODO @jason:如果过滤掉被加签的任务,这些任务被对应的审批人看到是啥状态哈? + // TODO @jason:如果过滤掉被加签的任务,这些任务被对应的审批人看到是啥状态哈? @芋艿 为不通过状态。 List taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), false, null, null); updateTaskStatusWhenCanceled( CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())), From 58e250c4eb39924ce6daf6aed64e5348276792ab Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 13 Jul 2024 14:34:07 +0800 Subject: [PATCH 046/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=93=8D=E4=BD=9C=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=9D=83=E9=99=90=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/simple/BpmSimpleModelNodeVO.java | 20 +++++++++++-- .../admin/task/vo/task/BpmTaskRespVO.java | 13 +++++++++ .../bpm/convert/task/BpmTaskConvert.java | 2 ++ .../core/enums/BpmnModelConstants.java | 20 +++++++++++++ .../flowable/core/util/BpmnModelUtils.java | 28 ++++++++++++++++++- .../flowable/core/util/SimpleModelUtils.java | 20 +++++++++++++ 6 files changed, 100 insertions(+), 3 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index d0857c13d..fa216bb3f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -65,11 +65,14 @@ public class BpmSimpleModelNodeVO { @InEnum(BpmApproveMethodEnum.class) private Integer approveMethod; // 用于审批节点 + @Schema(description = "通过比例", example = "100") + private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置 + @Schema(description = "表单权限", example = "[]") private List> fieldsPermission; - @Schema(description = "通过比例", example = "100") - private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置 + @Schema(description = "操作按钮设置", example = "[]") + private List buttonsSetting; // 用于审批节点 /** * 审批节点拒绝处理 @@ -111,6 +114,19 @@ public class BpmSimpleModelNodeVO { private Integer maxRemindCount; } + @Data + @Schema(description = "操作按钮设置") + public static class OperationButtonSetting { + + @Schema(description = "按钮 Id", example = "1") + private Integer id; + + @Schema(description = "显示名称", example = "审批") + private String displayName; + + @Schema(description = "是否启用", example = "true") + private Boolean enable; + } // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 // Integer approveMethod; 审批方式;仅审批节点会使用 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java index 4e17bf974..985c1ed02 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -69,6 +69,8 @@ public class BpmTaskRespVO { private Map formVariables; @Schema(description = "表单字段权限值") private Map fieldsPermission; + @Schema(description = "操作按钮设置值") + private Map buttonsSetting; @Data @Schema(description = "流程实例") @@ -93,4 +95,15 @@ public class BpmTaskRespVO { } + @Data + @Schema(description = "操作按钮设置") + public static class OperationButtonSetting { + + @Schema(description = "显示名称", example = "审批") + private String displayName; + + @Schema(description = "是否启用", example = "true") + private Boolean enable; + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 87f59f518..3a8e60701 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -114,6 +114,8 @@ public interface BpmTaskConvert { if(BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限 taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); + // 操作按钮设置 + taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey())); } return taskVO; }); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 20d6d9d9d..556560f8d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -71,6 +71,26 @@ public interface BpmnModelConstants { */ String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; + /** + * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 + */ + String BUTTON_SETTING_ELEMENT = "buttonsSettings"; + + /** + * BPMN ExtensionElement Attribute, 用于标记按钮编号 + */ + String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id"; + + /** + * BPMN ExtensionElement Attribute, 用于标记按钮显示名称 + */ + String BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE = "displayName"; + + /** + * BPMN ExtensionElement Attribute, 用于标记按钮是否启用 + */ + String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; + // TODO @芋艿:这里后面得关注下; /** * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index e0c9d6ae2..56043923f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import org.flowable.bpmn.converter.BpmnXMLConverter; @@ -65,8 +66,11 @@ public class BpmnModelUtils { if (flowElement == null) { return null; } - Map fieldsPermission = MapUtil.newHashMap(); List extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); + if (CollUtil.isEmpty(extensionElements)) { + return null; + } + Map fieldsPermission = MapUtil.newHashMap(); extensionElements.forEach(element -> { String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); @@ -77,6 +81,28 @@ public class BpmnModelUtils { return fieldsPermission; } + public static Map parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return null; + } + List extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT); + if (CollUtil.isEmpty(extensionElements)) { + return null; + } + Map buttonSettings = MapUtil.newHashMap(16); + extensionElements.forEach(element -> { + String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); + String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); + String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE); + if (StrUtil.isNotEmpty(id)) { + BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting(); + buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable))); + } + }); + return buttonSettings; + } + /** * 根据节点,获取入口连线 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 240f2bd2a..5f529e859 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; @@ -454,6 +455,8 @@ public class SimpleModelUtils { addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node.getFieldsPermission(), userTask); + // 添加操作按钮配置属性元素 + addButtonsSetting(node.getButtonsSetting(), userTask); // 处理多实例 processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); // 添加任务被拒绝的处理元素 @@ -461,6 +464,7 @@ public class SimpleModelUtils { return userTask; } + private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { if (rejectHandler == null) { return; @@ -498,6 +502,22 @@ public class SimpleModelUtils { userTask.setLoopCharacteristics(multiInstanceCharacteristics); } + /** + * 给节点添加操作按钮设置元素 + */ + private static void addButtonsSetting(List buttonsSetting, UserTask userTask) { + if (CollUtil.isNotEmpty(buttonsSetting)) { + List> list = CollectionUtils.convertList(buttonsSetting, item -> { + Map settingMap = MapUtil.newHashMap(16); + settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId())); + settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName()); + settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable())); + return settingMap; + }); + list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item)); + } + } + /** * 给节点添加表单字段权限元素 */ From 5bafc0ce57cb3f01a803b779dfcc75534b076454 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 11 Aug 2024 00:09:39 +0800 Subject: [PATCH 047/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E5=BF=AB?= =?UTF-8?q?=E6=90=AD=E9=83=A8=E5=88=86=E7=9A=84=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/enums/definition/BpmApproveMethodEnum.java | 8 ++++---- .../definition/BpmUserTaskRejectHandlerType.java | 2 +- .../definition/BpmUserTaskTimeoutActionEnum.java | 7 ++++--- .../controller/admin/task/vo/task/BpmTaskRespVO.java | 8 +++++--- .../module/bpm/convert/task/BpmTaskConvert.java | 4 ++-- .../strategy/BpmTaskCandidateStartUserStrategy.java | 4 +++- .../flowable/core/enums/BpmnModelConstants.java | 10 +++++----- .../core/listener/BpmTimerFiredEventListener.java | 6 +++--- .../core/simplemodel/SimpleModelUserTaskConfig.java | 1 + .../framework/flowable/core/util/BpmnFormUtils.java | 2 ++ .../flowable/core/util/SimpleModelUtils.java | 12 ++++++------ .../service/task/BpmProcessInstanceServiceImpl.java | 2 +- .../module/bpm/service/task/BpmTaskService.java | 5 ++++- 13 files changed, 41 insertions(+), 30 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java index b57cb85ff..b3c1413bc 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java @@ -16,10 +16,10 @@ import java.util.Arrays; @AllArgsConstructor public enum BpmApproveMethodEnum implements IntArrayValuable { - RANDOM_SELECT_ONE_APPROVE(1, "随机挑选一人审批"), - APPROVE_BY_RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) - ANY_APPROVE(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人) - SEQUENTIAL_APPROVE(4, "依次审批"); // 依次审批 + RANDOM(1, "随机挑选一人审批"), + RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) + ANY(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人) + SEQUENTIAL(4, "依次审批"); // 依次审批 /** * 审批方式 diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java index 6c80645e8..f2d48f7d9 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -16,7 +16,7 @@ import java.util.Arrays; @AllArgsConstructor public enum BpmUserTaskRejectHandlerType implements IntArrayValuable { - FINISH_PROCESS(1, "终止流程"), + FINISH_PROCESS_INSTANCE(1, "终止流程"), RETURN_USER_TASK(2, "驳回到指定任务节点"); private final Integer type; diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java index fb955808d..50f265221 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java @@ -7,6 +7,7 @@ import lombok.Getter; import java.util.Arrays; +// TODO @jason:BpmUserTaskTimeoutHandlerTypeEnum 会不会更匹配哈 /** * 用户任务超时处理执行动作枚举 * @@ -16,9 +17,9 @@ import java.util.Arrays; @AllArgsConstructor public enum BpmUserTaskTimeoutActionEnum implements IntArrayValuable { - AUTO_REMINDER(1,"自动提醒"), - AUTO_APPROVE(2, "自动同意"), - AUTO_REJECT(3, "自动拒绝"); + REMINDER(1,"自动提醒"), + APPROVE(2, "自动同意"), + REJECT(3, "自动拒绝"); private final Integer action; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java index 985c1ed02..6d1dff28e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -61,16 +61,18 @@ public class BpmTaskRespVO { private Long formId; @Schema(description = "表单名字", example = "请假表单") private String formName; - @Schema(description = "表单的配置-JSON 字符串") + @Schema(description = "表单的配置,JSON 字符串") private String formConf; @Schema(description = "表单项的数组") private List formFields; @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) private Map formVariables; + // TODO @jason:fieldsPermissions @Schema(description = "表单字段权限值") - private Map fieldsPermission; + private Map fieldsPermission; + // TODO @jason:buttonsSettings @Schema(description = "操作按钮设置值") - private Map buttonsSetting; + private Map buttonsSetting; @Data @Schema(description = "流程实例") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 3a8e60701..dc3677260 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -111,8 +111,8 @@ public interface BpmTaskConvert { taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); } - if(BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ - // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限 + if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ + // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置 taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); // 操作按钮设置 taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey())); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java index 38feaf599..266e229d5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -12,7 +12,9 @@ import org.springframework.stereotype.Component; import java.util.Set; /** - * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类。 用于需要发起人信息复核等场景 + * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类 + * + * 适合场景:用于需要发起人信息复核等场景 * * @author jason */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 556560f8d..2c9902a00 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -35,6 +35,7 @@ public interface BpmnModelConstants { */ String BOUNDARY_EVENT_TYPE = "boundaryEventType"; + // TODO @jason:这个命名,应该也要改哈 /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 */ @@ -45,7 +46,6 @@ public interface BpmnModelConstants { * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 */ String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; - /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id */ @@ -56,6 +56,7 @@ public interface BpmnModelConstants { */ String USER_TASK_APPROVE_METHOD = "approveMethod"; + // TODO @jason:这个命名,可能有个 fieldsPermissions 更合适点。可能 formPermissions 会更更合适。 /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 */ @@ -65,7 +66,6 @@ public interface BpmnModelConstants { * BPMN ExtensionElement Attribute, 用于标记表单字段 */ String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field"; - /** * BPMN ExtensionElement Attribute, 用于标记表单权限 */ @@ -75,12 +75,10 @@ public interface BpmnModelConstants { * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 */ String BUTTON_SETTING_ELEMENT = "buttonsSettings"; - /** * BPMN ExtensionElement Attribute, 用于标记按钮编号 */ String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id"; - /** * BPMN ExtensionElement Attribute, 用于标记按钮显示名称 */ @@ -91,14 +89,16 @@ public interface BpmnModelConstants { */ String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; - // TODO @芋艿:这里后面得关注下; + // TODO @jason:这个是不是可以删除啦 /** * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 */ String END_EVENT_ID = "EndEvent_1"; + // TODO @jason:这个是不是可以删除啦 /** * 支持转仿钉钉设计模型的 Bpmn 节点 */ Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index 1076f13d0..9f2c40d21 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -85,13 +85,13 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe null, taskDefKey); taskList.forEach(task -> { // 自动提醒 - if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.REMINDER) { TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); todoTaskReminderProducer.sendReminderMessage(message); } // 自动同意 - if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_APPROVE) { + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.APPROVE) { // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); TenantContextHolder.setIgnore(false); @@ -100,7 +100,7 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); } // 自动拒绝 - if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REJECT) { + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.REJECT) { // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); TenantContextHolder.setIgnore(false); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java index 9e632fa1d..ec9ad8e9c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java @@ -7,6 +7,7 @@ import lombok.Data; import java.util.List; import java.util.Map; +// TODO @jason:这个貌似没用到,是不是可以删除啦 /** * 仿钉钉流程设计器审批节点配置 Model * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java index e01f71fa5..e8f8345f7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java @@ -26,8 +26,10 @@ public class BpmnFormUtils { private static final String CREATE_FORM_DISPLAY_ATTRIBUTE = "display"; private static final String CREATE_FORM_DISABLED_ATTRIBUTE = "disabled"; + // TODO @jason:这个方法,还要哇? /** * 修改 form-create 表单组件字段权限规则: 包括可编辑、只读、隐藏规则 + * * @param fields 字段规则 * @param fieldsPermission 字段权限 * @return 修改权限后的字段规则 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 5f529e859..0727ad3b8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -27,7 +27,7 @@ import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.s import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; @@ -356,7 +356,7 @@ public class SimpleModelUtils { boundaryEvent.setAttachedToRef(userTask); TimerEventDefinition eventDefinition = new TimerEventDefinition(); eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); - if (Objects.equals(AUTO_REMINDER.getAction(), timeoutHandler.getAction()) && + if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { // 最大提醒次数 eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); @@ -475,7 +475,7 @@ public class SimpleModelUtils { private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); - if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM_SELECT_ONE_APPROVE) { + if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { return; } // 添加审批方式的扩展属性 @@ -484,16 +484,16 @@ public class SimpleModelUtils { MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); // 设置 collectionVariable。本系统用不到。会在 仅仅为了校验。 multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); - if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY_APPROVE) { + if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY) { multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(false); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.SEQUENTIAL_APPROVE) { + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.SEQUENTIAL) { multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) { + } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.RATIO) { Assert.notNull(approveRatio, "通过比例不能为空"); double approvePct = approveRatio / (double) 100; multiInstanceCharacteristics.setCompletionCondition(String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approvePct))); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index e5cb92cca..585dcf860 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, List activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // TODO @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, List activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 3adb56858..c3c2c3ac4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -137,7 +137,10 @@ public interface BpmTaskService { * @param executionId execution Id * @param taskDefineKey 任务定义 Key */ - List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String executionId, String taskDefineKey); + List getRunningTaskListByProcessInstanceId(String processInstanceId, + Boolean assigned, + String executionId, + String taskDefineKey); /** * 获取当前任务的可回退的 UserTask 集合 From 521cc3deb4bd9296f623708c06034ea6848db1bc Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 11 Aug 2024 13:00:01 +0800 Subject: [PATCH 048/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9Atask=20?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=B8=8D=E9=80=9A=E8=BF=87=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E9=92=88=E5=AF=B9=E5=8A=A0=E7=AD=BE=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 4 - .../bpm/enums/task/BpmCommentTypeEnum.java | 2 - .../bpm/enums/task/BpmDeleteReasonEnum.java | 2 - .../core/listener/BpmTaskEventListener.java | 2 - .../task/BpmProcessInstanceService.java | 3 +- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 6 +- .../bpm/service/task/BpmTaskServiceImpl.java | 102 ++++++++++-------- 8 files changed, 66 insertions(+), 57 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index e344a2145..ec167719c 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -75,8 +75,4 @@ public interface ErrorCodeConstants { // ========== BPM 流程表达式 1-009-014-000 ========== ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); - // ========== BPM 仿钉钉流程设计器 1-009-015-000 ========== - // TODO @芋艿:这个错误码,需要关注下 - ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程"); - } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java index 498b601c2..8b7b7ce11 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java @@ -22,8 +22,6 @@ public enum BpmCommentTypeEnum { TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"), ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"), SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), - // TODO @芋艿:这个枚举状态,需要关注下! - REJECT_BY_ADD_SIGN_TASK_REJECT("10", "不通过","系统自动不通过,原因是:加签任务不通过") ; /** diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java index 1c2382c79..802b9d890 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java @@ -22,8 +22,6 @@ public enum BpmDeleteReasonEnum { // ========== 流程任务的独有原因 ========== CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 - // TODO @芋艿:这个枚举状态,需要关注下! - AUTO_REJECT_BY_ADD_SIGN_REJECT("系统自动拒绝,原因:加签任务被拒绝") // 加签任务审批不通过,导致任务不通过 ; private final String reason; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index a72e2924c..a2af341b3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -59,8 +59,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { // TODO @jason:如果用户主动取消,可能需要考虑这个 - // TODO @芋艿 如果在 rejectTask 处理了。 这里又要查询一次。感觉有点多余。 主动取消是不是也要处理一下。要不然有加签的任务也会报错 - // @芋艿。 这里是不是就可以不要了, 取消的任务状态,在rejectTask 里面做了, 如果在 updateTaskStatusWhenCanceled 里面修改会报错。 List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); if (CollUtil.isEmpty(activityList)) { log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index b1cfae3ae..d73a11115 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -10,6 +10,7 @@ import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -141,7 +142,7 @@ public interface BpmProcessInstanceService { * @param endId 结束节点 Id * @param reason 理由。例如说,审批不通过时,需要传递该值 */ - void updateProcessInstanceReject(ProcessInstance processInstance, List activityIds, String endId, String reason); + void updateProcessInstanceReject(ProcessInstance processInstance, Collection activityIds, String endId, String reason); /** * 当流程结束时候,更新 ProcessInstance 为通过 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 585dcf860..12bf8d09a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // TODO @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, List activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // TODO @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, Collection activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index c3c2c3ac4..b70fc1194 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -133,9 +133,9 @@ public interface BpmTaskService { * 根据条件查询正在进行中的任务 * * @param processInstanceId 流程实例编号,不允许为空 - * @param assigned 是否分配了审批人 - * @param executionId execution Id - * @param taskDefineKey 任务定义 Key + * @param assigned 是否分配了审批人,允许空 + * @param executionId execution Id,允许空 + * @param taskDefineKey 任务定义 Key,允许空 */ List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 7f1e08582..fb61cd9c7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; @@ -53,8 +52,6 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum.REJECT_BY_ADD_SIGN_TASK_REJECT; -import static cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum.AUTO_REJECT_BY_ADD_SIGN_REJECT; /** * 流程任务实例 Service 实现类 @@ -329,35 +326,41 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (instance == null) { throw exception(PROCESS_INSTANCE_NOT_EXISTS); } + + // 2. 处理当前任务 // 2.1 更新流程任务为不通过 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); // 2.2 添加评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); - // 3.1 如果是被加签任务且是后加签。 更新加签任务状态为取消 - if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) { - List childTaskList = getTaskListByParentTaskId(task.getId()); - updateTaskStatusWhenCanceled(childTaskList, reqVO.getReason()); + // 3. 处理其他进行中的任务 + // 3.1 如果当前任务时被加签的,则加它的根任务也标记成未通过 + // 疑问:为什么要标记未通过呢? + // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 + if (task.getParentTaskId() != null) { + String rootParentId = getTaskRootParentId(task); + updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(), + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); + taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); } - // 3.2 如果是加签的任务 - if (StrUtil.isNotEmpty(task.getParentTaskId())) { - Task signTask = validateTaskExist(task.getParentTaskId()); - // 3.2.1 更新被加签的任务为不通过 - if (BpmTaskSignTypeEnum.BEFORE.getType().equals(signTask.getScopeType())) { - updateTaskStatusAndReason(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus(), AUTO_REJECT_BY_ADD_SIGN_REJECT.getReason()); - } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(signTask.getScopeType())) { - updateTaskStatus(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus()); - // 后加签 不添加拒绝意见。因为会把原来的意见覆盖. + // 3.2 其它未结束的任务,直接取消 + // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? + // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 + List taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), null, null, null); + taskList.forEach(otherTask -> { + if (!otherTask.getId().equals(task.getId())) { // 不需要处理当前任务 + return; } - // 3.2.2 添加评论 - taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), - BpmCommentTypeEnum.REJECT.getType(), REJECT_BY_ADD_SIGN_TASK_REJECT.getComment()); - // 3.2.3 更新还在进行中的加签任务状态为取消 - List addSignTaskList = getTaskListByParentTaskId(task.getParentTaskId()); - updateTaskStatusWhenCanceled(CollectionUtils.filterList(addSignTaskList, item -> !item.getId().equals(task.getId())), - reqVO.getReason()); - } + Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { + return; + } + updateTaskStatusWhenCanceled(otherTask.getId()); + }); + taskList.stream().filter(otherTask -> !otherTask.getId().equals(task.getId())) // 需要排除当前任务 + .forEach(otherTask -> updateTaskStatusWhenCanceled(otherTask.getId())); // 4.1 驳回到指定的任务节点 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); @@ -372,26 +375,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } - // 4.2.1 更新其它正在运行的任务状态为取消。需要过滤掉当前任务和被加签的任务 - // TODO @jason:如果过滤掉被加签的任务,这些任务被对应的审批人看到是啥状态哈? @芋艿 为不通过状态。 - List taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), false, null, null); - updateTaskStatusWhenCanceled( - CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())), - reqVO.getReason()); - // 4.2.2 终止流程 + // 4.2 终止流程 Set activityIds = convertSet(taskList, Task::getTaskDefinitionKey); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能未空"); - processInstanceService.updateProcessInstanceReject(instance, CollUtil.newArrayList(activityIds), endEvent.getId(), reqVO.getReason()); - } - - private void updateTaskStatusWhenCanceled(List taskList, String reason) { - taskList.forEach(task -> { - updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); - taskService.addComment(task.getId(), task.getProcessInstanceId(), - BpmCommentTypeEnum.CANCEL.getType(), BpmCommentTypeEnum.CANCEL.formatComment(reason)); - - }); + processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason()); } /** @@ -440,9 +428,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); } + /** + * 重要补充说明:该方法目前主要有两个情况会调用到: + * + * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 + * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 + */ @Override public void updateTaskStatusWhenCanceled(String taskId) { - // @芋艿。这里是不是可以不要了,要不然。 updateTaskStatusAndReason 会报错 task 已经删除了。 Task task = getTask(taskId); // 1. 可能只是活动,不是任务,所以查询不到 if (task == null) { @@ -500,6 +493,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { .includeTaskLocalVariables(); if (BooleanUtil.isTrue(assigned)) { taskQuery.taskAssigned(); + } else if (BooleanUtil.isFalse(assigned)) { + taskQuery.taskUnassigned(); } if (StrUtil.isNotEmpty(executionId)) { taskQuery.executionId(executionId); @@ -888,6 +883,29 @@ public class BpmTaskServiceImpl implements BpmTaskService { return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); } + /** + * 获得任务根任务的父任务编号 + * + * @param task 任务 + * @return 根任务的父任务编号 + */ + private String getTaskRootParentId(Task task) { + if (task == null || task.getParentTaskId() == null) { + return null; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + Task parentTask = getTask(task.getParentTaskId()); + if (parentTask == null) { + return null; + } + if (parentTask.getParentTaskId() == null) { + return parentTask.getId(); + } + task = parentTask; + } + throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId())); + } + @Override public Map getTaskNameByTaskIds(Collection taskIds) { if (CollUtil.isEmpty(taskIds)) { From 32804d3e0bf1844c5be658a20c9a893772dacf8f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 11 Aug 2024 19:01:56 +0800 Subject: [PATCH 049/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=B5=81=E7=A8=8B=E4=B8=8D=E9=80=9A=E8=BF=87=E3=80=81?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E7=9A=84=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E5=85=A8=E8=BD=AC=E5=90=91=20flowable=20?= =?UTF-8?q?=E7=9A=84=20moveActivityIdsToSingleActivityId=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...leteReasonEnum.java => BpmReasonEnum.java} | 10 +-- .../task/BpmProcessInstanceConvert.java | 5 -- .../flowable/core/enums/BpmConstants.java | 8 ++ .../BpmProcessInstanceEventListener.java | 19 ++--- .../task/BpmProcessInstanceService.java | 22 +----- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 9 ++- .../bpm/service/task/BpmTaskServiceImpl.java | 73 ++++++++++--------- 8 files changed, 66 insertions(+), 82 deletions(-) rename yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/{BpmDeleteReasonEnum.java => BpmReasonEnum.java} (81%) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java similarity index 81% rename from yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index 802b9d890..c3c10629a 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -5,13 +5,13 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * 流程实例/任务的删除原因枚举 + * 流程实例/任务的的处理原因枚举 * * @author 芋道源码 */ @Getter @AllArgsConstructor -public enum BpmDeleteReasonEnum { +public enum BpmReasonEnum { // ========== 流程实例的独有原因 ========== @@ -36,10 +36,4 @@ public enum BpmDeleteReasonEnum { return StrUtil.format(reason, args); } - // ========== 逻辑 ========== - - public static boolean isRejectReason(String reason) { - return StrUtil.startWith(reason, "审批不通过任务,原因:"); - } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java index f6aea33ec..b797c612b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -89,11 +89,6 @@ public interface BpmProcessInstanceConvert { @Mapping(source = "from.id", target = "to.id", ignore = true) void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, HistoricProcessInstance instance, Integer status) { - return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) - .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); - } - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {; return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java index e965d2281..d5cd53885 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java @@ -15,6 +15,14 @@ public class BpmConstants { * @see ProcessInstance#getProcessVariables() */ public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; + /** + * 流程实例的变量 - 理由 + * + * 例如说:审批不通过的理由(目前审核通过暂时不会记录) + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON"; /** * 流程实例的变量 - 发起用户选择的审批人 Map * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java index 4a8d0c244..01d43335d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java @@ -6,7 +6,6 @@ import jakarta.annotation.Resource; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; -import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -21,24 +20,18 @@ import java.util.Set; @Component public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { - @Resource - @Lazy - private BpmProcessInstanceService processInstanceService; - public static final Set PROCESS_INSTANCE_EVENTS = ImmutableSet.builder() - .add(FlowableEngineEventType.PROCESS_CANCELLED) - .add(FlowableEngineEventType.PROCESS_COMPLETED) - .build(); + .add(FlowableEngineEventType.PROCESS_COMPLETED) + .build(); + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; public BpmProcessInstanceEventListener(){ super(PROCESS_INSTANCE_EVENTS); } - @Override - protected void processCancelled(FlowableCancelledEvent event) { - processInstanceService.updateProcessInstanceWhenCancel(event); - } - @Override protected void processCompleted(FlowableEngineEntityEvent event) { processInstanceService.updateProcessInstanceWhenCompleted((ProcessInstance)event.getEntity()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index d73a11115..3bf323e4b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -6,11 +6,9 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import jakarta.validation.Valid; -import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -120,32 +118,16 @@ public interface BpmProcessInstanceService { */ void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); - /** - * 更新 ProcessInstance 拓展记录为取消 - * - * @param event 流程取消事件 - */ - void updateProcessInstanceWhenCancel(FlowableCancelledEvent event); - - /** - * 更新 ProcessInstance 为完成 - * - * @param instance 流程任务 - */ - void updateProcessInstanceWhenApprove(ProcessInstance instance); - /** * 更新 ProcessInstance 为不通过 * * @param processInstance 流程实例 - * @param activityIds 当前未完成活动节点 Id - * @param endId 结束节点 Id * @param reason 理由。例如说,审批不通过时,需要传递该值 */ - void updateProcessInstanceReject(ProcessInstance processInstance, Collection activityIds, String endId, String reason); + void updateProcessInstanceReject(ProcessInstance processInstance, String reason); /** - * 当流程结束时候,更新 ProcessInstance 为通过 + * 处理 ProcessInstance 完成(审批通过、不通过、取消) * * @param instance 流程任务 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 12bf8d09a..d881e80a9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // TODO @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, Collection activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index b70fc1194..a7c9a2c85 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -90,8 +90,6 @@ public interface BpmTaskService { */ void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); - - /** * 将流程任务分配给指定用户 * @@ -100,6 +98,13 @@ public interface BpmTaskService { */ void transferTask(Long userId, BpmTaskTransferReqVO reqVO); + /** + * 将指定流程实例的、进行中的流程任务,移动到结束节点 + * + * @param processInstanceId 流程编号 + */ + void moveTaskToEnd(String processInstanceId); + /** * 更新 Task 状态,在创建时 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index fb61cd9c7..d07534e01 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; -import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; @@ -327,15 +327,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { throw exception(PROCESS_INSTANCE_NOT_EXISTS); } - // 2. 处理当前任务 // 2.1 更新流程任务为不通过 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); - // 2.2 添加评论 + // 2.2 添加流程评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); - - // 3. 处理其他进行中的任务 - // 3.1 如果当前任务时被加签的,则加它的根任务也标记成未通过 + // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 // 疑问:为什么要标记未通过呢? // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 if (task.getParentTaskId() != null) { @@ -345,41 +342,22 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); } - // 3.2 其它未结束的任务,直接取消 - // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? - // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 - List taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), null, null, null); - taskList.forEach(otherTask -> { - if (!otherTask.getId().equals(task.getId())) { // 不需要处理当前任务 - return; - } - Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); - if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { - return; - } - updateTaskStatusWhenCanceled(otherTask.getId()); - }); - taskList.stream().filter(otherTask -> !otherTask.getId().equals(task.getId())) // 需要排除当前任务 - .forEach(otherTask -> updateTaskStatusWhenCanceled(otherTask.getId())); - // 4.1 驳回到指定的任务节点 + // 3. 根据不同的 RejectHandler 处理策略 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 3.1 情况一:驳回到指定的任务节点 BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); Assert.notNull(returnTaskId, "回退的节点不能为空"); - BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId) - .setReason(reqVO.getReason()); - returnTask(userId, returnReq); + returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) + .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); return; } - - // 4.2 终止流程 - Set activityIds = convertSet(taskList, Task::getTaskDefinitionKey); - EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); - Assert.notNull(endEvent, "结束节点不能未空"); - processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason()); + // 3.2 情况二:直接结束,审批不通过 + processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason()); // 标记不通过 + moveTaskToEnd(task.getProcessInstanceId()); // 结束流程 } /** @@ -449,7 +427,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); return; } - updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); + updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 } @@ -665,6 +643,35 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); } + @Override + public void moveTaskToEnd(String processInstanceId) { + List taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null, null); + if (CollUtil.isEmpty(taskList)) { + return; + } + + // 1. 其它未结束的任务,直接取消 + // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? + // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 + taskList.forEach(task -> { + Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { + return; + } + updateTaskStatusWhenCanceled(task.getId()); + }); + + // 2. 终止流程 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); + List activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); + Assert.notNull(endEvent, "结束节点不能未空"); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstanceId) + .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId()) + .changeState(); + } + @Override @Transactional(rollbackFor = Exception.class) public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) { From 6c69eeba094c1da3a0ee973563eaa8cacdbb14e7 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 11 Aug 2024 22:07:11 +0800 Subject: [PATCH 050/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20code=20review=20=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ava => BpmUserTaskTimeoutHandlerType.java} | 11 ++- .../vo/model/simple/BpmSimpleModelNodeVO.java | 4 +- .../core/enums/BpmnModelConstants.java | 19 ---- .../core/listener/BpmTaskEventListener.java | 1 - .../listener/BpmTimerFiredEventListener.java | 10 +-- .../SimpleModelUserTaskConfig.java | 90 ------------------- .../flowable/core/util/BpmnFormUtils.java | 73 --------------- .../flowable/core/util/SimpleModelUtils.java | 2 +- 8 files changed, 13 insertions(+), 197 deletions(-) rename yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/{BpmUserTaskTimeoutActionEnum.java => BpmUserTaskTimeoutHandlerType.java} (65%) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java similarity index 65% rename from yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java index 50f265221..d1c32158e 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java @@ -7,15 +7,14 @@ import lombok.Getter; import java.util.Arrays; -// TODO @jason:BpmUserTaskTimeoutHandlerTypeEnum 会不会更匹配哈 /** - * 用户任务超时处理执行动作枚举 + * 用户任务超时处理类型枚举 * * @author jason */ @Getter @AllArgsConstructor -public enum BpmUserTaskTimeoutActionEnum implements IntArrayValuable { +public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable { REMINDER(1,"自动提醒"), APPROVE(2, "自动同意"), @@ -24,10 +23,10 @@ public enum BpmUserTaskTimeoutActionEnum implements IntArrayValuable { private final Integer action; private final String name; - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutActionEnum::getAction).toArray(); + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerType::getAction).toArray(); - public static BpmUserTaskTimeoutActionEnum actionOf(Integer action) { - return ArrayUtil.firstMatch(item -> item.getAction().equals(action), values()); + public static BpmUserTaskTimeoutHandlerType typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getAction().equals(type), values()); } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index fa216bb3f..ecc59ba2c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -104,7 +104,7 @@ public class BpmSimpleModelNodeVO { private Boolean enable; @Schema(description = "任务超时未处理的行为", example = "1") - @InEnum(BpmUserTaskTimeoutActionEnum.class) + @InEnum(BpmUserTaskTimeoutHandlerType.class) private Integer action; @Schema(description = "超时时间", example = "PT6H") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 2c9902a00..aa3878a2c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -1,12 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; -import com.google.common.collect.ImmutableSet; -import org.flowable.bpmn.model.EndEvent; -import org.flowable.bpmn.model.FlowNode; -import org.flowable.bpmn.model.UserTask; - -import java.util.Set; - /** * BPMN XML 常量信息 * @@ -89,16 +82,4 @@ public interface BpmnModelConstants { */ String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; - // TODO @jason:这个是不是可以删除啦 - /** - * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 - */ - String END_EVENT_ID = "EndEvent_1"; - - // TODO @jason:这个是不是可以删除啦 - /** - * 支持转仿钉钉设计模型的 Bpmn 节点 - */ - Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index a2af341b3..645d4e03f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -58,7 +58,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { - // TODO @jason:如果用户主动取消,可能需要考虑这个 List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); if (CollUtil.isEmpty(activityList)) { log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index 9f2c40d21..f52e999ec 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer; @@ -78,20 +78,20 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe } private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) { - BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); + BpmUserTaskTimeoutHandlerType userTaskTimeoutAction = BpmUserTaskTimeoutHandlerType.typeOf(timeoutAction); if (userTaskTimeoutAction != null) { // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? List taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, null, taskDefKey); taskList.forEach(task -> { // 自动提醒 - if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.REMINDER) { + if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REMINDER) { TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); todoTaskReminderProducer.sendReminderMessage(message); } // 自动同意 - if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.APPROVE) { + if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.APPROVE) { // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); TenantContextHolder.setIgnore(false); @@ -100,7 +100,7 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); } // 自动拒绝 - if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.REJECT) { + if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REJECT) { // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); TenantContextHolder.setIgnore(false); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java deleted file mode 100644 index ec9ad8e9c..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel; - -import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; -import lombok.Data; - -import java.util.List; -import java.util.Map; - -// TODO @jason:这个貌似没用到,是不是可以删除啦 -/** - * 仿钉钉流程设计器审批节点配置 Model - * - * @author jason - */ -@Data -public class SimpleModelUserTaskConfig { - - /** - * 候选人策略 - */ - private Integer candidateStrategy; - /** - * 候选人参数 - */ - private String candidateParam; - - /** - * 字段权限 - */ - private List> fieldsPermission; - - /** - * 审批方式 {@link BpmApproveMethodEnum } - */ - private Integer approveMethod; - /** - * 通过比例 当审批方式为 多人会签(按通过比例) 需设置 - */ - private Integer approveRatio; - - /** - * 超时处理 - */ - private TimeoutHandler timeoutHandler; - - /** - * 用户任务拒绝处理 - */ - private RejectHandler rejectHandler; - - @Data - public static class TimeoutHandler { - - /** - * 是否开启超时处理 - */ - private Boolean enable; - - /** - * 超时执行的动作 - */ - private Integer action; - - /** - * 超时时间设置 - */ - private String timeDuration; - - /** - * 如果执行动作是自动提醒, 最大提醒次数 - */ - private Integer maxRemindCount; - } - - @Data - public static class RejectHandler { - - /** - * 用户任务拒绝处理类型 {@link BpmUserTaskRejectHandlerType} - */ - private Integer type; - - /** - * 用户任务拒绝后驳回的节点 Id - */ - private String returnNodeId; - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java deleted file mode 100644 index e8f8345f7..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmFieldPermissionEnum; -import com.fasterxml.jackson.core.type.TypeReference; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE; - -// TODO @芋艿:这块去研究下! -/** - * Bpmn 流程表单相关工具方法 - * - * @author jason - */ -public class BpmnFormUtils { - - private static final String CREATE_FORM_DISPLAY_ATTRIBUTE = "display"; - private static final String CREATE_FORM_DISABLED_ATTRIBUTE = "disabled"; - - // TODO @jason:这个方法,还要哇? - /** - * 修改 form-create 表单组件字段权限规则: 包括可编辑、只读、隐藏规则 - * - * @param fields 字段规则 - * @param fieldsPermission 字段权限 - * @return 修改权限后的字段规则 - */ - public static List changeCreateFormFiledPermissionRule(List fields, Map fieldsPermission) { - if ( CollUtil.isEmpty(fields) || MapUtil.isEmpty(fieldsPermission)) { - return fields; - } - List afterChangedFields = new ArrayList<>(fields.size()); - fields.forEach( f-> { - Map fieldMap = JsonUtils.parseObject(f, new TypeReference<>() {}); - String field = ObjUtil.defaultIfNull(fieldMap.get(FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE), Object::toString, ""); - if (StrUtil.isEmpty(field) || !fieldsPermission.containsKey(field)) { - afterChangedFields.add(f); - return; - } - BpmFieldPermissionEnum fieldPermission = BpmFieldPermissionEnum.valueOf(fieldsPermission.get(field)); - Assert.notNull(fieldPermission, "字段权限不匹配"); - if (BpmFieldPermissionEnum.NONE == fieldPermission) { - fieldMap.put(CREATE_FORM_DISPLAY_ATTRIBUTE, Boolean.FALSE); - } else if (BpmFieldPermissionEnum.WRITE == fieldPermission){ - Map props = MapUtil.get(fieldMap, "props", new cn.hutool.core.lang.TypeReference<>() {}); - if (props == null) { - props = MapUtil.newHashMap(); - fieldMap.put("props", props); - } - props.put(CREATE_FORM_DISABLED_ATTRIBUTE, Boolean.FALSE); - } else if (BpmFieldPermissionEnum.READ == fieldPermission) { - Map props = MapUtil.get(fieldMap, "props", new cn.hutool.core.lang.TypeReference<>() {}); - if (props == null) { - props = MapUtil.newHashMap(); - fieldMap.put("props", props); - } - props.put(CREATE_FORM_DISABLED_ATTRIBUTE, Boolean.TRUE); - } - afterChangedFields.add(JsonUtils.toJsonString(fieldMap)); - }); - return afterChangedFields; - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 0727ad3b8..59d095d85 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -27,7 +27,7 @@ import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.s import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.REMINDER; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; From 36a77251af93c0bc0013d2d9969f7c25a7c2711f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 12 Aug 2024 09:03:49 +0800 Subject: [PATCH 051/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9ABpmTaskSe?= =?UTF-8?q?rvice=E3=80=81BpmProcessInstanceService=20=E5=8C=BA=E5=88=86?= =?UTF-8?q?=E8=AF=BB=E6=96=B9=E6=B3=95=E3=80=81=E5=86=99=E3=80=81event=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=9A=84=E5=8C=BA=E5=9F=9F=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E5=8F=AF=E9=98=85=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmProcessInstanceEventListener.java | 2 +- .../core/listener/BpmTaskEventListener.java | 7 +- .../listener/BpmTimerFiredEventListener.java | 6 +- .../task/BpmProcessInstanceService.java | 10 +- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 126 +++--- .../bpm/service/task/BpmTaskServiceImpl.java | 401 +++++++++--------- 7 files changed, 282 insertions(+), 272 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java index 01d43335d..a0ec3e40a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java @@ -34,7 +34,7 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent @Override protected void processCompleted(FlowableEngineEntityEvent event) { - processInstanceService.updateProcessInstanceWhenCompleted((ProcessInstance)event.getEntity()); + processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index a2af341b3..e20a6b64a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -48,17 +48,16 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void taskCreated(FlowableEngineEntityEvent event) { - taskService.updateTaskStatusWhenCreated((Task) event.getEntity()); + taskService.processTaskCreated((Task) event.getEntity()); } @Override protected void taskAssigned(FlowableEngineEntityEvent event) { - taskService.updateTaskExtAssign((Task) event.getEntity()); + taskService.processTaskAssigned((Task) event.getEntity()); } @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { - // TODO @jason:如果用户主动取消,可能需要考虑这个 List activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); if (CollUtil.isEmpty(activityList)) { log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); @@ -69,7 +68,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { if (StrUtil.isEmpty(activity.getTaskId())) { return; } - taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); + taskService.processTaskCanceled(activity.getTaskId()); }); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index 9f2c40d21..ffa578fbb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -42,7 +42,6 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe @Resource @Lazy // 延迟加载,避免循环依赖 private BpmModelService bpmModelService; - @Resource @Lazy // 延迟加载,避免循环依赖 private BpmTaskService bpmTaskService; @@ -67,7 +66,7 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe // 如果是定时器边界事件 if (element instanceof BoundaryEvent) { BoundaryEvent boundaryEvent = (BoundaryEvent) element; - String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); // 类型为用户任务超时未处理的情况 if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) { @@ -81,8 +80,7 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); if (userTaskTimeoutAction != null) { // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? - List taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, - null, taskDefKey); + List taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefKey); taskList.forEach(task -> { // 自动提醒 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.REMINDER) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 3bf323e4b..0c7266f8f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -22,6 +22,8 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. */ public interface BpmProcessInstanceService { + // ========== Query 查询相关方法 ========== + /** * 获得流程实例 * @@ -74,6 +76,8 @@ public interface BpmProcessInstanceService { return convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId); } + // ========== Update 写入相关方法 ========== + /** * 获得流程实例的分页 * @@ -126,11 +130,13 @@ public interface BpmProcessInstanceService { */ void updateProcessInstanceReject(ProcessInstance processInstance, String reason); + // ========== Event 事件相关方法 ========== + /** - * 处理 ProcessInstance 完成(审批通过、不通过、取消) + * 处理 ProcessInstance 完成事件,例如说:审批通过、不通过、取消 * * @param instance 流程任务 */ - void updateProcessInstanceWhenCompleted(ProcessInstance instance); + void processProcessInstanceCompleted(ProcessInstance instance); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index d881e80a9..8cee79442 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index a7c9a2c85..9858f6889 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -20,6 +20,8 @@ import java.util.Map; */ public interface BpmTaskService { + // ========== Query 查询相关方法 ========== + /** * 获得待办的流程任务分页 * @@ -74,6 +76,51 @@ public interface BpmTaskService { */ List getTaskListByProcessInstanceId(String processInstanceId); + /** + * 获取任务 + * + * @param id 任务编号 + * @return 任务 + */ + Task getTask(String id); + + /** + * 根据条件查询正在进行中的任务 + * + * @param processInstanceId 流程实例编号,不允许为空 + * @param assigned 是否分配了审批人,允许空 + * @param taskDefineKey 任务定义 Key,允许空 + */ + List getRunningTaskListByProcessInstanceId(String processInstanceId, + Boolean assigned, + String taskDefineKey); + + /** + * 获取当前任务的可回退的 UserTask 集合 + * + * @param id 当前的任务 ID + * @return 可以回退的节点列表 + */ + List getUserTaskListByReturn(String id); + + /** + * 获取指定任务的子任务列表 + * + * @param parentTaskId 父任务ID + * @return 子任务列表 + */ + List getTaskListByParentTaskId(String parentTaskId); + + /** + * 通过任务 ID,查询任务名 Map + * + * @param taskIds 任务 ID + * @return 任务 ID 与名字的 Map + */ + Map getTaskNameByTaskIds(Collection taskIds); + + // ========== Update 写入相关方法 ========== + /** * 通过任务 * @@ -105,56 +152,6 @@ public interface BpmTaskService { */ void moveTaskToEnd(String processInstanceId); - /** - * 更新 Task 状态,在创建时 - * - * @param task 任务实体 - */ - void updateTaskStatusWhenCreated(Task task); - - /** - * 更新 Task 状态,在取消时 - * - * @param taskId 任务的编号 - */ - void updateTaskStatusWhenCanceled(String taskId); - - /** - * 更新 Task 拓展记录,并发送通知 - * - * @param task 任务实体 - */ - void updateTaskExtAssign(Task task); - - /** - * 获取任务 - * - * @param id 任务编号 - * @return 任务 - */ - Task getTask(String id); - - /** - * 根据条件查询正在进行中的任务 - * - * @param processInstanceId 流程实例编号,不允许为空 - * @param assigned 是否分配了审批人,允许空 - * @param executionId execution Id,允许空 - * @param taskDefineKey 任务定义 Key,允许空 - */ - List getRunningTaskListByProcessInstanceId(String processInstanceId, - Boolean assigned, - String executionId, - String taskDefineKey); - - /** - * 获取当前任务的可回退的 UserTask 集合 - * - * @param id 当前的任务 ID - * @return 可以回退的节点列表 - */ - List getUserTaskListByReturn(String id); - /** * 将任务回退到指定的 targetDefinitionKey 位置 * @@ -187,20 +184,27 @@ public interface BpmTaskService { */ void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); - /** - * 获取指定任务的子任务列表 - * - * @param parentTaskId 父任务ID - * @return 子任务列表 - */ - List getTaskListByParentTaskId(String parentTaskId); + // ========== Event 事件相关方法 ========== /** - * 通过任务 ID,查询任务名 Map + * 处理 Task 创建事件,目前是更新它的状态为审批中 * - * @param taskIds 任务 ID - * @return 任务 ID 与名字的 Map + * @param task 任务实体 */ - Map getTaskNameByTaskIds(Collection taskIds); + void processTaskCreated(Task task); + + /** + * 处理 Task 取消事件,目前是更新它的状态为已取消 + * + * @param taskId 任务的编号 + */ + void processTaskCanceled(String taskId); + + /** + * 处理 Task 设置审批人事件,目前是发送审批消息 + * + * @param task 任务实体 + */ + void processTaskAssigned(Task task); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index d07534e01..79f43cd15 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -84,6 +84,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Resource private AdminUserApi adminUserApi; + // ========== Query 查询相关方法 ========== + @Override public PageResult getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) { TaskQuery taskQuery = taskService.createTaskQuery() @@ -172,6 +174,156 @@ public class BpmTaskServiceImpl implements BpmTaskService { return tasks; } + /** + * 校验任务是否存在,并且是否是分配给自己的任务 + * + * @param userId 用户 id + * @param taskId task id + */ + private Task validateTask(Long userId, String taskId) { + Task task = validateTaskExist(taskId); + if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { + throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); + } + return task; + } + + private Task validateTaskExist(String id) { + Task task = getTask(id); + if (task == null) { + throw exception(TASK_NOT_EXISTS); + } + return task; + } + + @Override + public Task getTask(String id) { + return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); + } + + @Override + public List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) { + Assert.notNull(processInstanceId, "processInstanceId 不能为空"); + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active() + .includeTaskLocalVariables(); + if (BooleanUtil.isTrue(assigned)) { + taskQuery.taskAssigned(); + } else if (BooleanUtil.isFalse(assigned)) { + taskQuery.taskUnassigned(); + } + if (StrUtil.isNotEmpty(defineKey)) { + taskQuery.taskDefinitionKey(defineKey); + } + return taskQuery.list(); + } + + private HistoricTaskInstance getHistoricTask(String id) { + return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); + } + + @Override + public List getUserTaskListByReturn(String id) { + // 1.1 校验当前任务 task 存在 + Task task = validateTaskExist(id); + // 1.2 根据流程定义获取流程模型信息 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + if (source == null) { + throw exception(TASK_NOT_EXISTS); + } + + // 2.1 查询该任务的前置任务节点的 key 集合 + List previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); + if (CollUtil.isEmpty(previousUserList)) { + return Collections.emptyList(); + } + // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 + previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); + return previousUserList; + } + + /** + * 获得所有子任务列表 + * + * @param parentTask 父任务 + * @return 所有子任务列表 + */ + private List getAllChildTaskList(Task parentTask) { + List result = new ArrayList<>(); + // 1. 递归获取子级 + Stack stack = new Stack<>(); + stack.push(parentTask); + // 2. 递归遍历 + for (int i = 0; i < Short.MAX_VALUE; i++) { + if (stack.isEmpty()) { + break; + } + // 2.1 获取子任务们 + Task task = stack.pop(); + List childTaskList = getTaskListByParentTaskId(task.getId()); + // 2.2 如果非空,则添加到 stack 进一步递归 + if (CollUtil.isNotEmpty(childTaskList)) { + stack.addAll(childTaskList); + result.addAll(childTaskList); + } + } + return result; + } + + @Override + public List getTaskListByParentTaskId(String parentTaskId) { + String tableName = managementService.getTableName(TaskEntity.class); + // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 + String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); + } + + /** + * 获取子任务个数 + * + * @param parentTaskId 父任务 ID + * @return 剩余子任务个数 + */ + private Long getTaskCountByParentTaskId(String parentTaskId) { + String tableName = managementService.getTableName(TaskEntity.class); + String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); + } + + /** + * 获得任务根任务的父任务编号 + * + * @param task 任务 + * @return 根任务的父任务编号 + */ + private String getTaskRootParentId(Task task) { + if (task == null || task.getParentTaskId() == null) { + return null; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + Task parentTask = getTask(task.getParentTaskId()); + if (parentTask == null) { + return null; + } + if (parentTask.getParentTaskId() == null) { + return parentTask.getId(); + } + task = parentTask; + } + throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId())); + } + + @Override + public Map getTaskNameByTaskIds(Collection taskIds) { + if (CollUtil.isEmpty(taskIds)) { + return Collections.emptyMap(); + } + List tasks = taskService.createTaskQuery().taskIds(taskIds).list(); + return convertMap(tasks, Task::getId, Task::getName); + } + + // ========== Update 写入相关方法 ========== + @Override @Transactional(rollbackFor = Exception.class) public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) { @@ -382,132 +534,6 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason); } - /** - * 校验任务是否存在,并且是否是分配给自己的任务 - * - * @param userId 用户 id - * @param taskId task id - */ - private Task validateTask(Long userId, String taskId) { - Task task = validateTaskExist(taskId); - if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { - throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); - } - return task; - } - - @Override - public void updateTaskStatusWhenCreated(Task task) { - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); - if (status != null) { - log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); - return; - } - updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); - } - - /** - * 重要补充说明:该方法目前主要有两个情况会调用到: - * - * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 - * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 - */ - @Override - public void updateTaskStatusWhenCanceled(String taskId) { - Task task = getTask(taskId); - // 1. 可能只是活动,不是任务,所以查询不到 - if (task == null) { - log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); - return; - } - - // 2. 更新 task 状态 + 原因 - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); - if (BpmTaskStatusEnum.isEndStatus(status)) { - log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); - return; - } - updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); - // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 - } - - @Override - public void updateTaskExtAssign(Task task) { - // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - - @Override - public void afterCommit() { - if (StrUtil.isEmpty(task.getAssignee())) { - return; - } - ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); - if (processInstance != null) { - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); - } - } - - }); - } - - private Task validateTaskExist(String id) { - Task task = getTask(id); - if (task == null) { - throw exception(TASK_NOT_EXISTS); - } - return task; - } - - @Override - public Task getTask(String id) { - return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); - } - - @Override - public List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String executionId, String defineKey) { - Assert.notNull(processInstanceId, "processInstanceId 不能为空"); - TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active() - .includeTaskLocalVariables(); - if (BooleanUtil.isTrue(assigned)) { - taskQuery.taskAssigned(); - } else if (BooleanUtil.isFalse(assigned)) { - taskQuery.taskUnassigned(); - } - if (StrUtil.isNotEmpty(executionId)) { - taskQuery.executionId(executionId); - } - if (StrUtil.isNotEmpty(defineKey)) { - taskQuery.taskDefinitionKey(defineKey); - } - return taskQuery.list(); - } - - private HistoricTaskInstance getHistoricTask(String id) { - return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); - } - - @Override - public List getUserTaskListByReturn(String id) { - // 1.1 校验当前任务 task 存在 - Task task = validateTaskExist(id); - // 1.2 根据流程定义获取流程模型信息 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); - FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - if (source == null) { - throw exception(TASK_NOT_EXISTS); - } - - // 2.1 查询该任务的前置任务节点的 key 集合 - List previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); - if (CollUtil.isEmpty(previousUserList)) { - return Collections.emptyList(); - } - // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 - previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); - return previousUserList; - } - @Override @Transactional(rollbackFor = Exception.class) public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) { @@ -645,7 +671,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public void moveTaskToEnd(String processInstanceId) { - List taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null, null); + List taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null); if (CollUtil.isEmpty(taskList)) { return; } @@ -658,7 +684,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { return; } - updateTaskStatusWhenCanceled(task.getId()); + processTaskCanceled(task.getId()); }); // 2. 终止流程 @@ -842,84 +868,61 @@ public class BpmTaskServiceImpl implements BpmTaskService { return task; } - /** - * 获得所有子任务列表 - * - * @param parentTask 父任务 - * @return 所有子任务列表 - */ - private List getAllChildTaskList(Task parentTask) { - List result = new ArrayList<>(); - // 1. 递归获取子级 - Stack stack = new Stack<>(); - stack.push(parentTask); - // 2. 递归遍历 - for (int i = 0; i < Short.MAX_VALUE; i++) { - if (stack.isEmpty()) { - break; - } - // 2.1 获取子任务们 - Task task = stack.pop(); - List childTaskList = getTaskListByParentTaskId(task.getId()); - // 2.2 如果非空,则添加到 stack 进一步递归 - if (CollUtil.isNotEmpty(childTaskList)) { - stack.addAll(childTaskList); - result.addAll(childTaskList); - } + // ========== Event 事件相关方法 ========== + + @Override + public void processTaskCreated(Task task) { + Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + if (status != null) { + log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); + return; } - return result; + updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); + } + + /** + * 重要补充说明:该方法目前主要有两个情况会调用到: + * + * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 + * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 + */ + @Override + public void processTaskCanceled(String taskId) { + Task task = getTask(taskId); + // 1. 可能只是活动,不是任务,所以查询不到 + if (task == null) { + log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); + return; + } + + // 2. 更新 task 状态 + 原因 + Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + if (BpmTaskStatusEnum.isEndStatus(status)) { + log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); + return; + } + updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); + // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 } @Override - public List getTaskListByParentTaskId(String parentTaskId) { - String tableName = managementService.getTableName(TaskEntity.class); - // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 - String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; - return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); - } + public void processTaskAssigned(Task task) { + // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - /** - * 获取子任务个数 - * - * @param parentTaskId 父任务 ID - * @return 剩余子任务个数 - */ - private Long getTaskCountByParentTaskId(String parentTaskId) { - String tableName = managementService.getTableName(TaskEntity.class); - String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; - return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); - } - - /** - * 获得任务根任务的父任务编号 - * - * @param task 任务 - * @return 根任务的父任务编号 - */ - private String getTaskRootParentId(Task task) { - if (task == null || task.getParentTaskId() == null) { - return null; - } - for (int i = 0; i < Short.MAX_VALUE; i++) { - Task parentTask = getTask(task.getParentTaskId()); - if (parentTask == null) { - return null; + @Override + public void afterCommit() { + if (StrUtil.isEmpty(task.getAssignee())) { + return; + } + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance != null) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + } } - if (parentTask.getParentTaskId() == null) { - return parentTask.getId(); - } - task = parentTask; - } - throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId())); - } - @Override - public Map getTaskNameByTaskIds(Collection taskIds) { - if (CollUtil.isEmpty(taskIds)) { - return Collections.emptyMap(); - } - List tasks = taskService.createTaskQuery().taskIds(taskIds).list(); - return convertMap(tasks, Task::getId, Task::getName); + }); } } From fe3ca8deba506690d60ac771101807c16dc562b9 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 16 Aug 2024 23:28:59 +0800 Subject: [PATCH 052/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91BPM=EF=BC=9A=E5=AE=A1=E6=89=B9=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E6=8F=90=E9=86=92=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmBoundaryEventType.java | 1 + .../BpmUserTaskTimeoutHandlerType.java | 1 + .../bpm/enums/message/BpmMessageEnum.java | 3 +- .../vo/model/simple/BpmSimpleModelNodeVO.java | 3 + .../core/listener/BpmTaskEventListener.java | 40 +++++++ .../listener/BpmTimerFiredEventListener.java | 111 ------------------ .../SysNotifyTodoTaskReminderConsumer.java | 42 ------- .../message/task/TodoTaskReminderMessage.java | 34 ------ .../task/TodoTaskReminderProducer.java | 27 ----- .../flowable/core/util/FlowableUtils.java | 13 ++ .../flowable/core/util/SimpleModelUtils.java | 9 +- .../service/message/BpmMessageService.java | 9 +- .../message/BpmMessageServiceImpl.java | 11 ++ .../BpmMessageSendWhenTaskTimeoutReqDTO.java | 41 +++++++ .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 9 ++ .../bpm/service/task/BpmTaskServiceImpl.java | 41 +++++++ 17 files changed, 179 insertions(+), 218 deletions(-) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java index f824dfaac..dd10fae8a 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil; import lombok.AllArgsConstructor; import lombok.Getter; +// TODO @jason:这个是不是可以去掉了哈? /** * BPM 边界事件 (boundary event) 自定义类型枚举 * diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java index d1c32158e..328630575 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java @@ -20,6 +20,7 @@ public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable { APPROVE(2, "自动同意"), REJECT(3, "自动拒绝"); + // TODO @jason:type 是不是更合适哈; private final Integer action; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java index 79001fccd..abec70276 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java @@ -14,7 +14,8 @@ public enum BpmMessageEnum { PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 - TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 + TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 + TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 /** * 短信模板的标识 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index ecc59ba2c..1b9747c86 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -96,6 +96,7 @@ public class BpmSimpleModelNodeVO { private String returnNodeId; } + // TODO @芋艿:参数校验 @Data @Schema(description = "审批节点超时处理策略") public static class TimeoutHandler { @@ -103,6 +104,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "是否开启超时处理", example = "false") private Boolean enable; + // TODO @jason:type 是不是更合适哈; @Schema(description = "任务超时未处理的行为", example = "1") @InEnum(BpmUserTaskTimeoutHandlerType.class) private Integer action; @@ -112,6 +114,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "最大提醒次数", example = "1") private Integer maxRemindCount; + } @Data diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index e20a6b64a..f6019ca20 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -1,17 +1,27 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.job.api.Job; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -28,6 +38,9 @@ import java.util.Set; @Slf4j public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmModelService modelService; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @@ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { .add(FlowableEngineEventType.TASK_ASSIGNED) // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 .add(FlowableEngineEventType.ACTIVITY_CANCELLED) + .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 .build(); public BpmTaskEventListener() { @@ -72,4 +86,30 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { }); } + @Override + protected void timerFired(FlowableEngineEntityEvent event) { + // 1.1 只处理 BoundaryEvent 边界计时时间 + String processDefinitionId = event.getProcessDefinitionId(); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); + Job entity = (Job) event.getEntity(); + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); + if (!(element instanceof BoundaryEvent)) { + return; + } + // 1.2 判断是否为超时处理 + BoundaryEvent boundaryEvent = (BoundaryEvent) element; + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, + BpmnModelConstants.BOUNDARY_EVENT_TYPE); + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); + if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) { + return; + } + + // 2. 处理超时 + String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); + String taskKey = boundaryEvent.getAttachedToRefId(); + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutAction)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java deleted file mode 100644 index 37626d6ef..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ /dev/null @@ -1,111 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; - -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; -import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; -import com.google.common.collect.ImmutableSet; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.BoundaryEvent; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.FlowElement; -import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; -import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; -import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; -import org.flowable.job.api.Job; -import org.flowable.task.api.Task; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Set; - -// TODO @芋艿:这块需要仔细再瞅瞅 -/** - * 监听定时器触发事件 - * - * @author jason - */ -@Component -@Slf4j -public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener { - - @Resource - @Lazy // 延迟加载,避免循环依赖 - private BpmModelService bpmModelService; - @Resource - @Lazy // 延迟加载,避免循环依赖 - private BpmTaskService bpmTaskService; - - @Resource - private TodoTaskReminderProducer todoTaskReminderProducer; - - public static final Set TIME_EVENTS = ImmutableSet.builder() - .add(FlowableEngineEventType.TIMER_FIRED) - .build(); - - public BpmTimerFiredEventListener() { - super(TIME_EVENTS); - } - - @Override - protected void timerFired(FlowableEngineEntityEvent event) { - String processDefinitionId = event.getProcessDefinitionId(); - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); - Job entity = (Job) event.getEntity(); - FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); - // 如果是定时器边界事件 - if (element instanceof BoundaryEvent) { - BoundaryEvent boundaryEvent = (BoundaryEvent) element; - String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); - BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); - // 类型为用户任务超时未处理的情况 - if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) { - String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); - userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction)); - } - } - } - - private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) { - BpmUserTaskTimeoutHandlerType userTaskTimeoutAction = BpmUserTaskTimeoutHandlerType.typeOf(timeoutAction); - if (userTaskTimeoutAction != null) { - // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? - List taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefKey); - taskList.forEach(task -> { - // 自动提醒 - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REMINDER) { - TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) - .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); - todoTaskReminderProducer.sendReminderMessage(message); - } - // 自动同意 - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.APPROVE) { - // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 - TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); - TenantContextHolder.setIgnore(false); - BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId()) - .setReason("超时系统自动同意"); - bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); - } - // 自动拒绝 - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REJECT) { - // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 - TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); - TenantContextHolder.setIgnore(false); - BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"); - bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req); - } - }); - } - } -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java deleted file mode 100644 index d0dd51e93..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; -import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * 待办任务提醒 - 站内信的消费者 - * - * @author jason - */ -@Component -@Slf4j -public class SysNotifyTodoTaskReminderConsumer { - - private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind"; - - @Resource - private NotifyMessageSendApi notifyMessageSendApi; - - @EventListener - @Async - public void onMessage(TodoTaskReminderMessage message) { - log.info("站内信消费者接收到消息 [消息内容({})] ", message); - TenantUtils.execute(message.getTenantId(), ()-> { - Map templateParams = MapUtil.newHashMap(); - templateParams.put("name", message.getTaskName()); - NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId()) - .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams); - notifyMessageSendApi.sendSingleMessageToAdmin(req); - }); - } -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java deleted file mode 100644 index f91b67327..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * 待办任务提醒消息 - * - * @author jason - */ -@Data -public class TodoTaskReminderMessage { - - /** - * 租户 Id - */ - @NotNull(message = "租户 Id 不能未空") - private Long tenantId; - - /** - * 用户Id - */ - @NotNull(message = "用户 Id 不能未空") - private Long userId; - - /** - * 任务名称 - */ - @NotEmpty(message = "任务名称不能未空") - private String taskName; - - // TODO 暂时只有站内信通知. 后面可以增加 -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java deleted file mode 100644 index 67dfae83c..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task; - -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -// TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~ -/** - * 待办任务提醒 Producer - * - * @author jason - */ -@Component -@Validated -public class TodoTaskReminderProducer { - - @Resource - private ApplicationContext applicationContext; - - public void sendReminderMessage(@Valid TodoTaskReminderMessage message) { - applicationContext.publishEvent(message); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java index a8ee4e7f9..d2810fa85 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.variable.VariableContainer; @@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Flowable 相关的工具方法 @@ -39,6 +42,16 @@ public class FlowableUtils { return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; } + public static void execute(String tenantIdStr, Runnable runnable) { + if (ObjectUtil.isEmpty(tenantIdStr) + || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { + runnable.run(); + } else { + Long tenantId = Long.valueOf(tenantIdStr); + TenantUtils.execute(tenantId, runnable); + } + } + // ========== Execution 相关的工具方法 ========== /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 59d095d85..c93036cf5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -59,7 +59,6 @@ public class SimpleModelUtils { */ public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; - // TODO-DONE @jason:建议方法名,改成 buildBpmnModel // TODO @yunai:注释需要完善下; /** @@ -347,6 +346,13 @@ public class SimpleModelUtils { return flowElements; } + /** + * 添加 UserTask 用户审批的 BoundaryEvent 超时事件 + * + * @param userTask 审批任务 + * @param timeoutHandler 超时处理器 + * @return + */ private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); @@ -362,6 +368,7 @@ public class SimpleModelUtils { eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); } boundaryEvent.addEventDefinition(eventDefinition); + // 添加定时器边界事件类型 addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); // 添加超时执行动作元素 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java index 0de2664cb..268be727c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.message; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; - +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; import jakarta.validation.Valid; /** @@ -36,4 +36,11 @@ public interface BpmMessageService { */ void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); + /** + * 发送任务审批超时的消息 + * + * @param reqDTO 发送信息 + */ + void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java index 62f050098..c9889adb8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService { BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); } + @Override + public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { + Map templateParams = new HashMap<>(); + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); + templateParams.put("taskName", reqDTO.getTaskName()); + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), + BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)); + } + private String getProcessInstanceDetailUrl(String taskId) { return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java new file mode 100644 index 000000000..22d86ed65 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.bpm.service.message.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * BPM 发送任务审批超时 Request DTO + */ +@Data +public class BpmMessageSendWhenTaskTimeoutReqDTO { + + /** + * 流程实例的编号 + */ + @NotEmpty(message = "流程实例的编号不能为空") + private String processInstanceId; + /** + * 流程实例的名字 + */ + @NotEmpty(message = "流程实例的名字不能为空") + private String processInstanceName; + + /** + * 流程任务的编号 + */ + @NotEmpty(message = "流程任务的编号不能为空") + private String taskId; + /** + * 流程任务的名字 + */ + @NotEmpty(message = "流程任务的名字不能为空") + private String taskName; + + /** + * 审批人的用户编号 + */ + @NotNull(message = "审批人的用户编号不能为空") + private Long assigneeUserId; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 8cee79442..284739cb8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 9858f6889..e5cb96bf1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -207,4 +207,13 @@ public interface BpmTaskService { */ void processTaskAssigned(Task task); + /** + * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 + * + * @param processInstanceId 流程示例编号 + * @param taskDefineKey 任务 Key + * @param taskAction 处理类型 + */ + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 79f43cd15..4e1e9b538 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; @@ -19,6 +20,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -925,4 +927,43 @@ public class BpmTaskServiceImpl implements BpmTaskService { }); } + @Override + @Transactional(rollbackFor = Exception.class) + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); + if (processInstance == null) { + log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); + return; + } + List taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey); + // TODO 优化:未来需要考虑加签的情况 + if (CollUtil.isEmpty(taskList)) { + log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey); + return; + } + + taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { + // 情况一:自动提醒 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) { + messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) + .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); + return; + } + + // 情况二:自动同意 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) { + approveTask(Long.parseLong(task.getAssignee()), + new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意")); + return; + } + + // 情况三:自动拒绝 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) { + rejectTask(Long.parseLong(task.getAssignee()), + new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝")); + } + })); + } + } From 71e42cb0a1d350b37308ccccf3ac68c687a497c2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 10:19:20 +0800 Subject: [PATCH 053/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=AE=A1=E6=89=B9=E8=8A=82=E7=82=B9=E7=9A=84?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=BA=BA=E4=B8=8E=E5=8F=91=E8=B5=B7=E4=BA=BA?= =?UTF-8?q?=E7=9B=B8=E5=90=8C=E6=97=B6=EF=BC=8C=E5=AF=B9=E5=BA=94=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=E7=B1=BB=E5=9E=8B=E7=9A=84=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmBoundaryEventType.java | 5 +-- ...serTaskAssignStartUserHandlerTypeEnum.java | 27 ++++++++++++ .../vo/model/simple/BpmSimpleModelNodeVO.java | 16 +++---- .../bpm/convert/task/BpmTaskConvert.java | 1 - .../core/enums/BpmnModelConstants.java | 7 +++- .../flowable/core/util/SimpleModelUtils.java | 37 +++++++++------- .../bpm/service/task/BpmTaskServiceImpl.java | 42 +++++++++++++++---- 7 files changed, 96 insertions(+), 39 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java index dd10fae8a..537e03e03 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java @@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil; import lombok.AllArgsConstructor; import lombok.Getter; -// TODO @jason:这个是不是可以去掉了哈? /** * BPM 边界事件 (boundary event) 自定义类型枚举 * @@ -14,8 +13,7 @@ import lombok.Getter; @AllArgsConstructor public enum BpmBoundaryEventType { - USER_TASK_TIMEOUT(1,"用户任务超时"), - USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理"); + USER_TASK_TIMEOUT(1,"用户任务超时"); private final Integer type; private final String name; @@ -23,4 +21,5 @@ public enum BpmBoundaryEventType { public static BpmBoundaryEventType typeOf(Integer type) { return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java new file mode 100644 index 000000000..27a923be0 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * BPM 用户任务的审批人与发起人相同时,处理类型枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable { + + START_USER_AUDIT(1), // 由发起人对自己审批 + SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 + ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批 + + private final Integer type; + + @Override + public int[] array() { + return new int[0]; + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 1b9747c86..a4072750e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -1,10 +1,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; +import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -84,6 +81,10 @@ public class BpmSimpleModelNodeVO { */ private TimeoutHandler timeoutHandler; + @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") + @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) + private Integer assignStartUserHandlerType; + @Data @Schema(description = "审批节点拒绝处理策略") public static class RejectHandler { @@ -132,14 +133,7 @@ public class BpmSimpleModelNodeVO { } // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 - // Integer approveMethod; 审批方式;仅审批节点会使用 - // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级 - // TODO @芋艿:① 审批人的选择; // TODO @芋艿:⑥ 没有人的策略? - // TODO @芋艿:② 审批拒绝的策略? - // TODO @芋艿:③ 配置的可操作列表?(操作权限) - // TODO @芋艿:④ 表单的权限列表? - // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔; // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index dc3677260..23c922541 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -176,7 +176,6 @@ public interface BpmTaskConvert { childTask.setParentTaskId(parentTask.getId()); childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); -// childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错) childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); childTask.setPriority(parentTask.getPriority()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index aa3878a2c..102880a42 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -29,12 +29,17 @@ public interface BpmnModelConstants { String BOUNDARY_EVENT_TYPE = "boundaryEventType"; // TODO @jason:这个命名,应该也要改哈 + // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler; /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 */ String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; - // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;2)rejectHandlerType 改成 rejectHandler 哇? + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 + */ + String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; + /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index c93036cf5..27c7ad383 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; @@ -25,7 +26,6 @@ import java.util.Objects; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; @@ -338,8 +338,9 @@ public class SimpleModelUtils { List flowElements = new ArrayList<>(); UserTask userTask = buildBpmnUserTask(node); flowElements.add(userTask); + + // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { - // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); flowElements.add(boundaryEvent); } @@ -347,31 +348,31 @@ public class SimpleModelUtils { } /** - * 添加 UserTask 用户审批的 BoundaryEvent 超时事件 + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 * * @param userTask 审批任务 * @param timeoutHandler 超时处理器 - * @return + * @return BoundaryEvent 超时事件 */ private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { - // 定时器边界事件 + // 1.1 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); boundaryEvent.setId("Event-" + IdUtil.fastUUID()); - // 设置关联的任务为不会被中断 - boundaryEvent.setCancelActivity(false); + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 boundaryEvent.setAttachedToRef(userTask); + // 1.2 定义超时时间、最大提醒次数 TimerEventDefinition eventDefinition = new TimerEventDefinition(); eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { - // 最大提醒次数 - eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); + eventDefinition.setTimeCycle(String.format("R%d/%s", + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); } boundaryEvent.addEventDefinition(eventDefinition); - // 添加定时器边界事件类型 - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); - // 添加超时执行动作元素 + // 2.1 添加定时器边界事件类型 + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString()); + // 2.2 添加超时执行动作元素 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); return boundaryEvent; } @@ -455,8 +456,6 @@ public class SimpleModelUtils { userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); } - // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批 - // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? // 添加候选人元素 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); @@ -468,10 +467,11 @@ public class SimpleModelUtils { processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); // 添加任务被拒绝的处理元素 addTaskRejectElements(node.getRejectHandler(), userTask); + // 添加用户任务的审批人与发起人相同时的处理元素 + addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); return userTask; } - private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { if (rejectHandler == null) { return; @@ -480,6 +480,13 @@ public class SimpleModelUtils { addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); } + private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { + if (assignStartUserHandlerType == null) { + return; + } + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); + } + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 4e1e9b538..b269f7118 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.*; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; @@ -79,7 +80,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Resource private BpmProcessInstanceCopyService processInstanceCopyService; @Resource - private BpmModelService bpmModelService; + private BpmModelService modelService; @Resource private BpmMessageService messageService; @@ -228,7 +229,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 1.1 校验当前任务 task 存在 Task task = validateTaskExist(id); // 1.2 根据流程定义获取流程模型信息 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); if (source == null) { throw exception(TASK_NOT_EXISTS); @@ -498,7 +499,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 3. 根据不同的 RejectHandler 处理策略 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); // 3.1 情况一:驳回到指定的任务节点 BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); @@ -562,7 +563,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { // 1.1 获取流程模型信息 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); // 1.3 获取当前任务节点元素 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); // 1.3 获取跳转的节点元素 @@ -690,7 +691,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { }); // 2. 终止流程 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); List activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能未空"); @@ -915,13 +916,29 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public void afterCommit() { if (StrUtil.isEmpty(task.getAssignee())) { + log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId()); return; } ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); - if (processInstance != null) { - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + if (processInstance == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); + return; } + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + + // 审批人与提交人为同一人时,根据策略进行处理 + if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() + .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); + return; + } + + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); } }); @@ -966,4 +983,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { })); } + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private BpmTaskServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + } From 40db4cf7b7f3eda04ba7665593d2c866e2ce2604 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 11:33:41 +0800 Subject: [PATCH 054/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=AE=A1=E6=89=B9=E8=8A=82=E7=82=B9=E7=9A=84?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=BA=BA=E4=B8=8E=E5=8F=91=E8=B5=B7=E4=BA=BA?= =?UTF-8?q?=E7=9B=B8=E5=90=8C=E6=97=B6=EF=BC=8C=E5=85=B7=E4=BD=93=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...serTaskAssignStartUserHandlerTypeEnum.java | 2 +- .../candidate/BpmTaskCandidateInvoker.java | 30 ++++++++++ .../flowable/core/util/BpmnModelUtils.java | 4 ++ .../bpm/service/task/BpmTaskServiceImpl.java | 60 +++++++++++++++---- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java index 27a923be0..a25a8911a 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java @@ -15,7 +15,7 @@ public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuabl START_USER_AUDIT(1), // 由发起人对自己审批 SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 - ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批 + ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 private final Integer type; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index c0c7ca0d9..e3acc6429 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.annotations.VisibleForTesting; @@ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; import java.util.HashMap; import java.util.List; @@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker { Set userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); // 1.2 移除被禁用的用户 removeDisableUsers(userIds); + // 1.3 移除发起人的用户 + removeStartUserIfSkip(execution, userIds); // 2. 校验是否有候选人 if (CollUtil.isEmpty(userIds)) { @@ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker { }); } + /** + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 + * + * 注意:如果只有一个候选人,则不处理,避免无法审批 + * + * @param execution 执行中的任务 + * @param assigneeUserIds 当前分配的候选人 + */ + @VisibleForTesting + void removeStartUserIfSkip(DelegateExecution execution, Set assigneeUserIds) { + if (CollUtil.size(assigneeUserIds) <= 1) { + return; + } + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); + if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { + return; + } + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) + .getProcessInstance(execution.getProcessInstanceId()); + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); + assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); + } + private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 56043923f..e612d49b8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -53,6 +53,10 @@ public class BpmnModelUtils { return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); } + public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { + return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); + } + public static String parseExtensionElement(FlowElement flowElement, String elementName) { if (flowElement == null) { return null; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index b269f7118..fd58567ec 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -1,15 +1,18 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; @@ -22,6 +25,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -47,7 +52,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; import java.util.*; import java.util.stream.Stream; @@ -86,6 +90,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Resource private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; // ========== Query 查询相关方法 ========== @@ -500,11 +506,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 3. 根据不同的 RejectHandler 处理策略 BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); - FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); // 3.1 情况一:驳回到指定的任务节点 - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { - String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); + String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); Assert.notNull(returnTaskId, "回退的节点不能为空"); returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); @@ -924,17 +930,47 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); return; } - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); - if (bpmnModel == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); - return; - } // 审批人与提交人为同一人时,根据策略进行处理 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() - .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); - return; + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); + + // 情况一:自动跳过 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() + .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); + return; + } + // 情况二:转交给部门负责人审批 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.ASSIGN_DEPT_LEADER.getType())) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); + // 找不到部门负责人的情况下,自动审批通过 + // noinspection DataFlowIssue + if (dept.getLeaderUserId() == null) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() + .setId(task.getId()).setReason("审批人与提交人为同一人时,找不到部门负责人,自动通过")); + return; + } + // 找得到部门负责人的情况下,修改负责人 + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) + .setReason("审批人与提交人为同一人时,转交给部门负责人审批")); + return; + } + // 如果部门负责人是自己,还是自己审批吧~ + } } AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); From 620a0d8c2ce29b0e17ca541d676281e076a3ff0f Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 17 Aug 2024 12:04:53 +0800 Subject: [PATCH 055/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=96=B0=E5=A2=9E=E8=BF=9E?= =?UTF-8?q?=E7=BB=AD=E5=A4=9A=E7=BA=A7=E9=83=A8=E9=97=A8=E8=B4=9F=E8=B4=A3?= =?UTF-8?q?=E4=BA=BA=E5=AE=A1=E6=89=B9=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...skCandidateAbstractDeptLeaderStrategy.java | 75 +++++++++++++++++++ ...CandidateMultiLevelDeptLeaderStrategy.java | 53 +++++++++++++ ...kCandidateStartUserDeptLeaderStrategy.java | 74 ++++++++++++++++++ ...StartUserMultiLevelDeptLeaderStrategy.java | 73 ++++++++++++++++++ .../enums/BpmTaskCandidateStrategyEnum.java | 3 + 5 files changed, 278 insertions(+) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java new file mode 100644 index 000000000..a6e0790c6 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类 + * + * @author jason + */ +public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy implements BpmTaskCandidateStrategy { + + protected DeptApi deptApi; + + public BpmTaskCandidateAbstractDeptLeaderStrategy(DeptApi deptApi) { + this.deptApi = deptApi; + } + + /** + * 获取上级部门的负责人 + * + * @param assignDept 指定部门 + * @param level 第几级 + * @return 部门负责人 Id + */ + protected Long getAssignLevelDeptLeaderId(DeptRespDTO assignDept, Integer level) { + Assert.isTrue(level > 0, "level 必须大于 0"); + if (assignDept == null) { + return null; + } + DeptRespDTO dept = assignDept; + for (int i = 1; i < level; i++) { + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); + if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人 + break; + } + dept = parentDept; + } + return dept.getLeaderUserId(); + } + + /** + * 获取连续上级部门的负责人, 包含指定部门的负责人 + * + * @param assignDept 指定部门 + * @param level 第几级 + * @return 连续部门负责人 Id + */ + protected Set getMultiLevelDeptLeaderIds(DeptRespDTO assignDept, Integer level){ + Assert.isTrue(level > 0, "level 必须大于 0"); + if (assignDept == null) { + return Collections.emptySet(); + } + Set deptLeaderIds = new LinkedHashSet<>(); // 保证有序 + DeptRespDTO dept = assignDept; + for (int i = 0; i < level; i++) { + if (dept.getLeaderUserId() != null) { + deptLeaderIds.add(dept.getLeaderUserId()); + } + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); + if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 + break; + } + dept = parentDept; + } + return deptLeaderIds; + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java new file mode 100644 index 000000000..c3eb064e1 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.util.string.StrUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +/** + * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author jason + */ +@Component +public class BpmTaskCandidateMultiLevelDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { + + public BpmTaskCandidateMultiLevelDeptLeaderStrategy(DeptApi deptApi) { + super(deptApi); + } + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.MULTI_LEVEL_DEPT_LEADER; + } + + @Override + public void validateParam(String param) { + // 参数格式: ,分割。 前面一个指定指定部门Id, 后面一个是部门的层级 + List params = StrUtils.splitToLong(param, ","); + Assert.isTrue(params.size() == 2, "参数格式不匹配"); + deptApi.validateDeptList(CollUtil.toList(params.get(0))); + Assert.isTrue(params.get(1) > 0, "部门层级必须大于 0"); + } + + @Override + public Set calculateUsers(DelegateExecution execution, String param) { + // 参数格式: ,分割。 前面一个指定指定部门Id, 后面一个是审批的层级 + List params = StrUtils.splitToLong(param, ","); + // TODO @芋艿 是否要支持多个部门。 是不是这种场景,一个部门就可以了 + Long deptId = params.get(0); + Long level = params.get(1); + DeptRespDTO dept = deptApi.getDept(deptId); + return getMultiLevelDeptLeaderIds(dept, level.intValue()); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java new file mode 100644 index 000000000..972d2909d --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; +import static java.util.Collections.emptySet; + +/** + * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author jason + */ +@Component +public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { + @Resource + @Lazy + private BpmProcessInstanceService processInstanceService; + @Resource + private AdminUserApi adminUserApi; + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; + } + + public BpmTaskCandidateStartUserDeptLeaderStrategy(DeptApi deptApi) { + super(deptApi); + } + + @Override + public void validateParam(String param) { + // 参数是部门的层级 + Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); + } + + @Override + public Set calculateUsers(DelegateExecution execution, String param) { + // 获得流程发起人 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + + DeptRespDTO dept = getStartUserDept(startUserId); + Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 + return deptLeaderId != null ? asSet(deptLeaderId) : emptySet(); + } + + /** + * 获取发起人的部门 + * + * @param startUserId 发起人 Id + */ + protected DeptRespDTO getStartUserDept(Long startUserId) { + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); + if (startUser.getDeptId() == null) { // 找不到部门 + return null; + } + return deptApi.getDept(startUser.getDeptId()); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java new file mode 100644 index 000000000..326ea8870 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author jason + */ +@Component +public class BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { + + @Resource + @Lazy + private BpmProcessInstanceService processInstanceService; + + @Resource + private AdminUserApi adminUserApi; + + public BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(deptApi); + } + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER_MULTI_LEVEL_DEPT_LEADER; + } + + @Override + public void validateParam(String param) { + // 参数是部门的层级 + Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); + } + + @Override + public Set calculateUsers(DelegateExecution execution, String param) { + // 获得流程发起人 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + + DeptRespDTO dept = getStartUserDept(startUserId); + return getMultiLevelDeptLeaderIds(dept, Integer.valueOf(param)); // 参数是部门的层级 + } + + /** + * 获取发起人的部门 + * + * @param startUserId 发起人 Id + */ + protected DeptRespDTO getStartUserDept(Long startUserId) { + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); + if (startUser.getDeptId() == null) { // 找不到部门 + return null; + } + return deptApi.getDept(startUser.getDeptId()); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index 78628d0da..6a37bc616 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -21,10 +21,13 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { ROLE(10, "角色"), DEPT_MEMBER(20, "部门的成员"), // 包括负责人 DEPT_LEADER(21, "部门的负责人"), + MULTI_LEVEL_DEPT_LEADER(23, "连续多级部门的负责人"), POST(22, "岗位"), USER(30, "用户"), START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 + START_USER_DEPT_LEADER(37, "发起人部门负责人"), // 可以是发起人上级部门负责人 + START_USER_MULTI_LEVEL_DEPT_LEADER(38, "发起人连续多级部门的负责人"), USER_GROUP(40, "用户组"), EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager ; From 58a2e3d5d42a8f89c9f3497b0a89ca1cc6eada8f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 12:09:29 +0800 Subject: [PATCH 056/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E3=80=91=E5=B0=86=E5=AE=A1=E6=89=B9=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E7=9A=84=20action=20=E7=BB=9F=E4=B8=80=E6=8D=A2=E6=88=90=20han?= =?UTF-8?q?dlerType=EF=BC=8C=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...serTaskAssignStartUserHandlerTypeEnum.java | 2 +- ...=> BpmUserTaskTimeoutHandlerTypeEnum.java} | 13 +++------- .../module/bpm/enums/task/BpmReasonEnum.java | 6 +++++ .../vo/model/simple/BpmSimpleModelNodeVO.java | 17 +++++++----- .../core/enums/BpmnModelConstants.java | 4 +-- .../core/listener/BpmTaskEventListener.java | 6 ++--- .../flowable/core/util/SimpleModelUtils.java | 10 +++---- .../bpm/service/task/BpmTaskService.java | 5 ++-- .../bpm/service/task/BpmTaskServiceImpl.java | 26 +++++++++---------- 9 files changed, 46 insertions(+), 43 deletions(-) rename yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/{BpmUserTaskTimeoutHandlerType.java => BpmUserTaskTimeoutHandlerTypeEnum.java} (57%) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java index a25a8911a..da175d039 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java @@ -15,7 +15,7 @@ public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuabl START_USER_AUDIT(1), // 由发起人对自己审批 SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 - ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 + TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 private final Integer type; diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java similarity index 57% rename from yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java index 328630575..0d56c9b37 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.enums.definition; -import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; @@ -14,24 +13,20 @@ import java.util.Arrays; */ @Getter @AllArgsConstructor -public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable { +public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable { REMINDER(1,"自动提醒"), APPROVE(2, "自动同意"), REJECT(3, "自动拒绝"); - // TODO @jason:type 是不是更合适哈; - private final Integer action; + private final Integer type; private final String name; - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerType::getAction).toArray(); - - public static BpmUserTaskTimeoutHandlerType typeOf(Integer type) { - return ArrayUtil.firstMatch(item -> item.getAction().equals(type), values()); - } + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray(); @Override public int[] array() { return ARRAYS; } + } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index c3c10629a..d32f3f147 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -22,6 +22,12 @@ public enum BpmReasonEnum { // ========== 流程任务的独有原因 ========== CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 + TIMEOUT_APPROVE("审批超时,系统自动通过"), + TIMEOUT_REJECT("审批超时,系统自动不通过"), + ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), + ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), + ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), + ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), ; private final String reason; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index a4072750e..4cedefcd0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -97,20 +98,22 @@ public class BpmSimpleModelNodeVO { private String returnNodeId; } - // TODO @芋艿:参数校验 @Data @Schema(description = "审批节点超时处理策略") + @Valid public static class TimeoutHandler { - @Schema(description = "是否开启超时处理", example = "false") + @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否开启超时处理不能为空") private Boolean enable; - // TODO @jason:type 是不是更合适哈; - @Schema(description = "任务超时未处理的行为", example = "1") - @InEnum(BpmUserTaskTimeoutHandlerType.class) - private Integer action; + @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "任务超时未处理的行为不能为空") + @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class) + private Integer type; - @Schema(description = "超时时间", example = "PT6H") + @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H") + @NotEmpty(message = "超时时间不能为空") private String timeDuration; @Schema(description = "最大提醒次数", example = "1") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 102880a42..1bebc56e4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -28,12 +28,10 @@ public interface BpmnModelConstants { */ String BOUNDARY_EVENT_TYPE = "boundaryEventType"; - // TODO @jason:这个命名,应该也要改哈 - // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler; /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 */ - String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; + String USER_TASK_TIMEOUT_HANDLER_TYPE = "timeoutHandlerType"; /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index f6019ca20..bece6740e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -106,10 +106,10 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { } // 2. 处理超时 - String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, - BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); + String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE); String taskKey = boundaryEvent.getAttachedToRefId(); - taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutAction)); + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType)); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 27c7ad383..73f18c9c0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -27,7 +27,7 @@ import java.util.Objects; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; @@ -341,7 +341,7 @@ public class SimpleModelUtils { // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { - BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); + BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler()); flowElements.add(boundaryEvent); } return flowElements; @@ -354,7 +354,7 @@ public class SimpleModelUtils { * @param timeoutHandler 超时处理器 * @return BoundaryEvent 超时事件 */ - private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { + private static BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { // 1.1 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); boundaryEvent.setId("Event-" + IdUtil.fastUUID()); @@ -363,7 +363,7 @@ public class SimpleModelUtils { // 1.2 定义超时时间、最大提醒次数 TimerEventDefinition eventDefinition = new TimerEventDefinition(); eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); - if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && + if (Objects.equals(REMINDER.getType(), timeoutHandler.getType()) && timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); @@ -373,7 +373,7 @@ public class SimpleModelUtils { // 2.1 添加定时器边界事件类型 addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString()); // 2.2 添加超时执行动作元素 - addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, StrUtil.toStringOrNull(timeoutHandler.getType())); return boundaryEvent; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index e5cb96bf1..b2128ed36 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; import jakarta.validation.Valid; import org.flowable.bpmn.model.UserTask; import org.flowable.task.api.Task; @@ -212,8 +213,8 @@ public interface BpmTaskService { * * @param processInstanceId 流程示例编号 * @param taskDefineKey 任务 Key - * @param taskAction 处理类型 + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} */ - void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction); + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index fd58567ec..4c35ca382 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -14,7 +14,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; @@ -944,13 +944,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 情况一:自动跳过 if (ObjectUtils.equalsAny(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() - .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); return; } // 情况二:转交给部门负责人审批 if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.ASSIGN_DEPT_LEADER.getType())) { + BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; @@ -958,15 +958,15 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 找不到部门负责人的情况下,自动审批通过 // noinspection DataFlowIssue if (dept.getLeaderUserId() == null) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() - .setId(task.getId()).setReason("审批人与提交人为同一人时,找不到部门负责人,自动通过")); + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); return; } // 找得到部门负责人的情况下,修改负责人 if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) - .setReason("审批人与提交人为同一人时,转交给部门负责人审批")); + .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); return; } // 如果部门负责人是自己,还是自己审批吧~ @@ -982,7 +982,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) - public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) { + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) { ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); if (processInstance == null) { log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); @@ -997,7 +997,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { // 情况一:自动提醒 - if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) { + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType())) { messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); @@ -1005,16 +1005,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 情况二:自动同意 - if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) { + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.APPROVE.getType())) { approveTask(Long.parseLong(task.getAssignee()), - new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意")); + new BpmTaskApproveReqVO().setId(task.getId()).setReason(BpmReasonEnum.TIMEOUT_APPROVE.getReason())); return; } // 情况三:自动拒绝 - if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) { + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REJECT.getType())) { rejectTask(Long.parseLong(task.getAssignee()), - new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝")); + new BpmTaskRejectReqVO().setId(task.getId()).setReason(BpmReasonEnum.REJECT_TASK.getReason())); } })); } From 0d738fa3976c61fdd840f695af5f51f3991a7832 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 16:24:25 +0800 Subject: [PATCH 057/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AE=A1=E6=89=B9=E4=BA=BA=E4=B8=BA=E7=A9=BA=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E6=A0=B9=E6=8D=AE=E9=85=8D=E7=BD=AE=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E9=80=9A=E8=BF=87=E3=80=81=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8B=92=E7=BB=9D=E3=80=81=E6=8C=87=E5=AE=9A=E4=BA=BA=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E7=9A=84=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...BpmUserTaskAssignEmptyHandlerTypeEnum.java | 29 ++++++++++ .../module/bpm/enums/task/BpmReasonEnum.java | 2 + .../vo/model/simple/BpmSimpleModelNodeVO.java | 20 +++++++ .../BpmParallelMultiInstanceBehavior.java | 6 +++ .../BpmSequentialMultiInstanceBehavior.java | 6 +++ .../behavior/BpmUserTaskActivityBehavior.java | 38 +++++++++++-- .../candidate/BpmTaskCandidateInvoker.java | 16 +++--- .../BpmTaskCandidateAssignEmptyStrategy.java | 54 +++++++++++++++++++ .../enums/BpmTaskCandidateStrategyEnum.java | 1 + .../core/enums/BpmnModelConstants.java | 9 ++++ .../flowable/core/util/BpmnModelUtils.java | 15 ++++-- .../flowable/core/util/SimpleModelUtils.java | 18 ++++--- .../bpm/service/task/BpmTaskServiceImpl.java | 5 +- 13 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java new file mode 100644 index 000000000..69fdc78b1 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * BPM 用户任务的审批人为空时,处理类型枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable { + + APPROVE(1), // 自动通过 + REJECT(2), // 自动拒绝 + ASSIGN_USER(3), // 指定人员审批 + ASSIGN_ADMIN(4), // 转交给流程管理员 + ; + + private final Integer type; + + @Override + public int[] array() { + return new int[0]; + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index d32f3f147..deafc0efa 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -28,6 +28,8 @@ public enum BpmReasonEnum { ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), + ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), + ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"), ; private final String reason; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 4cedefcd0..793e52fdc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -86,6 +86,11 @@ public class BpmSimpleModelNodeVO { @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) private Integer assignStartUserHandlerType; + /** + * 空处理策略 + */ + private AssignEmptyHandler assignEmptyHandler; + @Data @Schema(description = "审批节点拒绝处理策略") public static class RejectHandler { @@ -121,6 +126,21 @@ public class BpmSimpleModelNodeVO { } + @Data + @Schema(description = "空处理策略") + @Valid + public static class AssignEmptyHandler { + + @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "空处理类型不能为空") + @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class) + private Integer type; + + @Schema(description = "指定人员审批的用户编号数组", example = "1") + private List userIds; + + } + @Data @Schema(description = "操作按钮设置") public static class OperationButtonSetting { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index 64ebb1aac..d4720c380 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import lombok.Setter; @@ -49,6 +51,10 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav // 第二步,获取任务的所有处理人 Set assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); + if (CollUtil.isEmpty(assigneeUserIds)) { + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务,避免自动通过! + assigneeUserIds = SetUtils.asSet((Long) null); + } execution.setVariable(super.collectionVariable, assigneeUserIds); return assigneeUserIds.size(); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index a214e2625..641b847e2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import lombok.Setter; @@ -43,6 +45,10 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB // 第二步,获取任务的所有处理人 Set assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!! + if (CollUtil.isEmpty(assigneeUserIds)) { + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务,避免自动通过! + assigneeUserIds = SetUtils.asSet((Long) null); + } execution.setVariable(super.collectionVariable, assigneeUserIds); return assigneeUserIds.size(); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java index c49465273..f3a8cac18 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java @@ -1,9 +1,16 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.UserTask; @@ -14,6 +21,8 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.TaskHelper; import org.flowable.task.service.TaskService; import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.List; import java.util.Set; @@ -41,9 +50,29 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { // 第一步,获得任务的候选用户 Long assigneeUserId = calculateTaskCandidateUsers(execution); - Assert.notNull(assigneeUserId, "任务处理人不能为空"); // 第二步,设置作为负责人 - TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); + if (assigneeUserId != null) { + TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); + return; + } + + // 特殊:审批人为空,根据配置是否要自动通过、自动拒绝 + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTask); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); + } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); + } + + } + + }); } private Long calculateTaskCandidateUsers(DelegateExecution execution) { @@ -56,6 +85,9 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { // 情况二,如果非多实例的任务,则计算任务处理人 // 第一步,先计算可处理该任务的处理人们 Set candidateUserIds = taskCandidateInvoker.calculateUsers(execution); + if (CollUtil.isEmpty(candidateUserIds)) { + return null; + } // 第二步,后随机选择一个任务的处理人 // 疑问:为什么一定要选择一个任务处理人? // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index e3acc6429..855336b76 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -27,7 +27,6 @@ import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER; /** * {@link BpmTaskCandidateStrategy} 的调用者,用于调用对应的策略,实现任务的候选人的计算 @@ -89,17 +88,16 @@ public class BpmTaskCandidateInvoker { String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); // 1.1 计算任务的候选人 Set userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); - // 1.2 移除被禁用的用户 - removeDisableUsers(userIds); + // 1.2 候选人为空时,根据“审批人为空”的配置补充 + if (CollUtil.isEmpty(userIds)) { + userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) + .calculateUsers(execution, param); + } // 1.3 移除发起人的用户 removeStartUserIfSkip(execution, userIds); - // 2. 校验是否有候选人 - if (CollUtil.isEmpty(userIds)) { - log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(), - execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param); - throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER); - } + // 2. 移除被禁用的用户 + removeDisableUsers(userIds); return userIds; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java new file mode 100644 index 000000000..0f2fac678 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy { + + @Resource + private AdminUserApi adminUserApi; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY; + } + + @Override + public void validateParam(String param) { + } + + @Override + public Set calculateUsers(DelegateExecution execution, String param) { + // 情况一:指定人员审批 + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement()); + if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { + return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); + } + + // 情况二:流程管理员 + if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { + // TODO 芋艿:需要等待流程实例的管理员支持 + throw new UnsupportedOperationException("暂时实现!!!"); + } + + // 都不满足,还是返回空 + return new HashSet<>(); + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index 6a37bc616..ac024e158 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -30,6 +30,7 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { START_USER_MULTI_LEVEL_DEPT_LEADER(38, "发起人连续多级部门的负责人"), USER_GROUP(40, "用户组"), EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager + ASSIGN_EMPTY(1, "审批人为空"), ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray(); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 1bebc56e4..54d086785 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -38,6 +38,15 @@ public interface BpmnModelConstants { */ String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型 + */ + String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组 + */ + String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds"; + /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index e612d49b8..17545fb86 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; @@ -45,16 +46,24 @@ public class BpmnModelUtils { } public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { - Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); + Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); } public static String parseReturnTaskId(FlowElement flowElement) { - return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); + return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); } public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { - return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); + return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); + } + + public static Integer parseAssignEmptyHandlerType(FlowElement userTask) { + return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE)); + } + + public static List parseAssignEmptyHandlerUserIds(FlowElement userTask) { + return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); } public static String parseExtensionElement(FlowElement flowElement, String elementName) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 73f18c9c0..99b93eca7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -19,10 +19,7 @@ import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; @@ -456,7 +453,6 @@ public class SimpleModelUtils { userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); } - // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? // 添加候选人元素 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); // 添加表单字段权限属性元素 @@ -469,6 +465,8 @@ public class SimpleModelUtils { addTaskRejectElements(node.getRejectHandler(), userTask); // 添加用户任务的审批人与发起人相同时的处理元素 addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); + // 添加用户任务的空处理元素 + addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); return userTask; } @@ -487,6 +485,14 @@ public class SimpleModelUtils { addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); } + private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) { + if (emptyHandler == null) { + return; + } + addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType())); + addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds())); + } + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { @@ -496,7 +502,7 @@ public class SimpleModelUtils { addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod == null ? null : approveMethod.toString()); MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); - // 设置 collectionVariable。本系统用不到。会在 仅仅为了校验。 + // 设置 collectionVariable。本系统用不到。仅仅为了 Flowable 校验不报错。 multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY) { multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 4c35ca382..aaa08f60b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -191,7 +191,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private Task validateTask(Long userId, String taskId) { Task task = validateTaskExist(taskId); - if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { + // 为什么判断 assignee 非空的情况下? + // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 + if (StrUtil.isNotBlank(task.getAssignee()) + && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); } return task; From 17c7fa44c1223af8cba36cb28c7a0ac95eacc420 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 18:21:30 +0800 Subject: [PATCH 058/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E7=B1=BB=E5=9E=8B=EF=BC=8C=E5=8C=BA=E5=88=86=E4=BA=BA?= =?UTF-8?q?=E5=B7=A5=E5=AE=A1=E6=89=B9=E3=80=81=E8=87=AA=E5=8A=A8=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E3=80=81=E8=87=AA=E5=8A=A8=E6=8B=92=E7=BB=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...java => BpmUserTaskApproveMethodEnum.java} | 6 ++-- .../BpmUserTaskApproveTypeEnum.java | 31 +++++++++++++++++++ ...BpmUserTaskAssignEmptyHandlerTypeEnum.java | 6 +++- ...serTaskAssignStartUserHandlerTypeEnum.java | 6 +++- .../module/bpm/enums/task/BpmReasonEnum.java | 2 ++ .../vo/model/simple/BpmSimpleModelNodeVO.java | 8 +++-- .../BpmParallelMultiInstanceBehavior.java | 6 ++-- .../BpmSequentialMultiInstanceBehavior.java | 9 +++--- .../behavior/BpmUserTaskActivityBehavior.java | 28 ++++++++++++----- .../candidate/BpmTaskCandidateInvoker.java | 24 +++++++++++--- .../core/enums/BpmnModelConstants.java | 7 ++++- .../flowable/core/util/BpmnModelUtils.java | 4 +++ .../flowable/core/util/SimpleModelUtils.java | 31 ++++++++++--------- 13 files changed, 127 insertions(+), 41 deletions(-) rename yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/{BpmApproveMethodEnum.java => BpmUserTaskApproveMethodEnum.java} (82%) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java similarity index 82% rename from yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java index b3c1413bc..9d4dd63af 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java @@ -14,7 +14,7 @@ import java.util.Arrays; */ @Getter @AllArgsConstructor -public enum BpmApproveMethodEnum implements IntArrayValuable { +public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable { RANDOM(1, "随机挑选一人审批"), RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) @@ -31,9 +31,9 @@ public enum BpmApproveMethodEnum implements IntArrayValuable { */ private final String name; - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmApproveMethodEnum::getMethod).toArray(); + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray(); - public static BpmApproveMethodEnum valueOf(Integer method) { + public static BpmUserTaskApproveMethodEnum valueOf(Integer method) { return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java new file mode 100644 index 000000000..fa6dba665 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户任务的审批类型枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable { + + USER(1), // 人工审批 + AUTO_APPROVE(2), // 自动通过 + AUTO_REJECT(3); // 自动拒绝 + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray(); + + private final Integer type; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java index 69fdc78b1..7a7242a49 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.util.Arrays; + /** * BPM 用户任务的审批人为空时,处理类型枚举 * @@ -19,11 +21,13 @@ public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable { ASSIGN_ADMIN(4), // 转交给流程管理员 ; + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray(); + private final Integer type; @Override public int[] array() { - return new int[0]; + return ARRAYS; } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java index da175d039..501281502 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.util.Arrays; + /** * BPM 用户任务的审批人与发起人相同时,处理类型枚举 * @@ -17,11 +19,13 @@ public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuabl SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray(); + private final Integer type; @Override public int[] array() { - return new int[0]; + return ARRAYS; } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index deafc0efa..5ea8c4187 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -30,6 +30,8 @@ public enum BpmReasonEnum { ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"), + APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), + APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), ; private final String reason; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 793e52fdc..d68a5d581 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -59,8 +59,12 @@ public class BpmSimpleModelNodeVO { @Schema(description = "候选人参数") private String candidateParam; // 用于审批,抄送节点 + @Schema(description = "审批节点类型", example = "1") + @InEnum(BpmUserTaskApproveTypeEnum.class) + private Integer approveType; // 用于审批节点 + @Schema(description = "多人审批方式", example = "1") - @InEnum(BpmApproveMethodEnum.class) + @InEnum(BpmUserTaskApproveMethodEnum.class) private Integer approveMethod; // 用于审批节点 @Schema(description = "通过比例", example = "100") @@ -155,8 +159,6 @@ public class BpmSimpleModelNodeVO { private Boolean enable; } - // Map formPermissions; 表单权限;仅发起、审批、抄送节点会使用 - // TODO @芋艿:⑥ 没有人的策略? // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index d4720c380..495d3539c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; import org.flowable.bpmn.model.Activity; import org.flowable.engine.delegate.DelegateExecution; @@ -52,7 +52,9 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav // 第二步,获取任务的所有处理人 Set assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); if (CollUtil.isEmpty(assigneeUserIds)) { - // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务,避免自动通过! + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 assigneeUserIds = SetUtils.asSet((Long) null); } execution.setVariable(super.collectionVariable, assigneeUserIds); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index 641b847e2..463658c80 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java @@ -2,15 +2,14 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; import org.flowable.bpmn.model.Activity; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; -import java.util.LinkedHashSet; import java.util.Set; /** @@ -44,9 +43,11 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); // 第二步,获取任务的所有处理人 - Set assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!! + Set assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); if (CollUtil.isEmpty(assigneeUserIds)) { - // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务,避免自动通过! + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 assigneeUserIds = SetUtils.asSet((Long) null); } execution.setVariable(super.collectionVariable, assigneeUserIds); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java index f3a8cac18..c2ac89722 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java @@ -6,6 +6,7 @@ import cn.hutool.core.util.RandomUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; @@ -56,18 +57,31 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { return; } - // 特殊:审批人为空,根据配置是否要自动通过、自动拒绝 + // 特殊:处理需要自动通过、不通过的情况 + Integer approveType = BpmnModelUtils.parseApproveType(userTask); Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTask); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { - if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { - SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() - .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); - } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { - SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() - .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); + // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { + if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); + } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); + } + // 特殊情况二:【自动审核】审批类型为自动通过、不通过 + } else { + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); + } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); + } } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index 855336b76..21727f748 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -6,7 +6,9 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; @@ -20,10 +22,7 @@ import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.runtime.ProcessInstance; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; @@ -61,7 +60,14 @@ public class BpmTaskCandidateInvoker { List userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); // 遍历所有的 UserTask,校验审批人配置 userTaskList.forEach(userTask -> { - // 1. 非空校验 + // 1.1 非人工审批,无需校验审批人配置 + Integer approveType = BpmnModelUtils.parseApproveType(userTask); + if (ObjectUtils.equalsAny(approveType, + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + return; + } + // 1.2 非空校验 Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask); String param = BpmnModelUtils.parseCandidateParam(userTask); if (strategy == null) { @@ -84,6 +90,14 @@ public class BpmTaskCandidateInvoker { */ @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 public Set calculateUsers(DelegateExecution execution) { + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 + Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement()); + if (ObjectUtils.equalsAny(approveType, + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + return new HashSet<>(); + } + Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement()); String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); // 1.1 计算任务的候选人 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 54d086785..88f55ebfb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -56,12 +56,16 @@ public interface BpmnModelConstants { */ String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; + /** + * BPMN UserTask 的扩展属性,用于标记用户任务的审批类型 + */ + String USER_TASK_APPROVE_TYPE = "approveType"; + /** * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式 */ String USER_TASK_APPROVE_METHOD = "approveMethod"; - // TODO @jason:这个命名,可能有个 fieldsPermissions 更合适点。可能 formPermissions 会更更合适。 /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 */ @@ -76,6 +80,7 @@ public interface BpmnModelConstants { */ String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; + // TODO @jason:上面是 fieldsPermission,然后这里是 buttonsSettings;感觉有点不统一。然后 BpmSimpleModelNodeVO 里面是 fieldsPermission、buttonsSetting; /** * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 17545fb86..e0a3af545 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -45,6 +45,10 @@ public class BpmnModelUtils { return candidateParam; } + public static Integer parseApproveType(FlowElement userTask) { + return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE)); + } + public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 99b93eca7..8a124c2f3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -9,10 +9,7 @@ import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; import org.flowable.bpmn.BpmnAutoLayout; @@ -448,9 +445,11 @@ public class SimpleModelUtils { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); - // 设置审批任务的截止时间 - if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { - userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); + + // 如果不是审批人节点,则直接返回 + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType())); + if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { + return userTask; } // 添加候选人元素 @@ -467,6 +466,10 @@ public class SimpleModelUtils { addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); // 添加用户任务的空处理元素 addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); + // 设置审批任务的截止时间 + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { + userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); + } return userTask; } @@ -494,8 +497,8 @@ public class SimpleModelUtils { } private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { - BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); - if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { + BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); + if (approveMethodEnum == null || approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { return; } // 添加审批方式的扩展属性 @@ -504,19 +507,19 @@ public class SimpleModelUtils { MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); // 设置 collectionVariable。本系统用不到。仅仅为了 Flowable 校验不报错。 multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); - if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY) { + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) { multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(false); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.SEQUENTIAL) { + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) { multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.RATIO) { + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) { Assert.notNull(approveRatio, "通过比例不能为空"); - double approvePct = approveRatio / (double) 100; - multiInstanceCharacteristics.setCompletionCondition(String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approvePct))); + multiInstanceCharacteristics.setCompletionCondition( + String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100))); multiInstanceCharacteristics.setSequential(false); } userTask.setLoopCharacteristics(multiInstanceCharacteristics); From 20c97d14413380fb668a836502440803c9685a06 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 17 Aug 2024 18:45:23 +0800 Subject: [PATCH 059/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=BF=9E=E7=BB=AD=E5=A4=9A?= =?UTF-8?q?=E7=BA=A7=E9=83=A8=E9=97=A8=E8=B4=9F=E8=B4=A3=E4=BA=BA,=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=89=E5=A4=9A=E4=B8=AA=E9=83=A8=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...skCandidateAbstractDeptLeaderStrategy.java | 33 +++++++++++-------- ...CandidateMultiLevelDeptLeaderStrategy.java | 20 +++++------ ...StartUserMultiLevelDeptLeaderStrategy.java | 9 +++-- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java index a6e0790c6..314428023 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; /** @@ -26,7 +28,7 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy implements BpmT * 获取上级部门的负责人 * * @param assignDept 指定部门 - * @param level 第几级 + * @param level 第几级 * @return 部门负责人 Id */ protected Long getAssignLevelDeptLeaderId(DeptRespDTO assignDept, Integer level) { @@ -48,26 +50,29 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy implements BpmT /** * 获取连续上级部门的负责人, 包含指定部门的负责人 * - * @param assignDept 指定部门 - * @param level 第几级 + * @param assignDeptIds 指定部门 Ids + * @param level 第几级 * @return 连续部门负责人 Id */ - protected Set getMultiLevelDeptLeaderIds(DeptRespDTO assignDept, Integer level){ + protected Set getMultiLevelDeptLeaderIds(List assignDeptIds, Integer level) { Assert.isTrue(level > 0, "level 必须大于 0"); - if (assignDept == null) { + if (CollUtil.isEmpty(assignDeptIds)) { return Collections.emptySet(); } Set deptLeaderIds = new LinkedHashSet<>(); // 保证有序 - DeptRespDTO dept = assignDept; - for (int i = 0; i < level; i++) { - if (dept.getLeaderUserId() != null) { - deptLeaderIds.add(dept.getLeaderUserId()); + DeptRespDTO dept; + for (Long deptId : assignDeptIds) { + dept = deptApi.getDept(deptId); + for (int i = 0; i < level; i++) { + if (dept.getLeaderUserId() != null) { + deptLeaderIds.add(dept.getLeaderUserId()); + } + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); + if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 + break; + } + dept = parentDept; } - DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); - if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 - break; - } - dept = parentDept; } return deptLeaderIds; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java index c3eb064e1..60730ed1b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.dept.DeptApi; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; @@ -32,22 +31,21 @@ public class BpmTaskCandidateMultiLevelDeptLeaderStrategy extends BpmTaskCandida @Override public void validateParam(String param) { - // 参数格式: ,分割。 前面一个指定指定部门Id, 后面一个是部门的层级 + // 参数格式: ,分割。前面的部门Id. 可以为多个。 最后一个为部门层级 List params = StrUtils.splitToLong(param, ","); - Assert.isTrue(params.size() == 2, "参数格式不匹配"); - deptApi.validateDeptList(CollUtil.toList(params.get(0))); - Assert.isTrue(params.get(1) > 0, "部门层级必须大于 0"); + List> splitList = CollUtil.split(params, params.size() - 1); + Assert.isTrue(splitList.size() == 2, "参数格式不匹配"); + deptApi.validateDeptList(splitList.get(0)); + Assert.isTrue(splitList.get(1).get(0) > 0, "部门层级必须大于 0"); } @Override public Set calculateUsers(DelegateExecution execution, String param) { - // 参数格式: ,分割。 前面一个指定指定部门Id, 后面一个是审批的层级 + // 参数格式: ,分割。前面的部门Id. 可以为多个。 最后一个为部门层级 List params = StrUtils.splitToLong(param, ","); - // TODO @芋艿 是否要支持多个部门。 是不是这种场景,一个部门就可以了 - Long deptId = params.get(0); - Long level = params.get(1); - DeptRespDTO dept = deptApi.getDept(deptId); - return getMultiLevelDeptLeaderIds(dept, level.intValue()); + List> splitList = CollUtil.split(params, params.size() - 1); + Long level = splitList.get(1).get(0); + return getMultiLevelDeptLeaderIds(splitList.get(0), level.intValue()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java index 326ea8870..f8495e343 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java @@ -15,8 +15,11 @@ import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import java.util.Collections; import java.util.Set; +import static cn.hutool.core.collection.ListUtil.toList; + /** * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 * @@ -52,9 +55,11 @@ public class BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy extends BpmTa // 获得流程发起人 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); - DeptRespDTO dept = getStartUserDept(startUserId); - return getMultiLevelDeptLeaderIds(dept, Integer.valueOf(param)); // 参数是部门的层级 + if (dept == null) { + return Collections.emptySet(); + } + return getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 } /** From e7815dd7625c75241f21e9827da74d67fbb65db9 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 17 Aug 2024 19:54:57 +0800 Subject: [PATCH 060/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=BF=9E=E7=BB=AD=E5=A4=9A?= =?UTF-8?q?=E7=BA=A7=E9=83=A8=E9=97=A8=E8=B4=9F=E8=B4=A3=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java index f8495e343..26c047563 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java @@ -35,7 +35,7 @@ public class BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy extends BpmTa @Resource private AdminUserApi adminUserApi; - public BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + public BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy(DeptApi deptApi) { super(deptApi); } From 040a1bcfad61bb6083dd6dae2f869fed312f56b4 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 20:30:51 +0800 Subject: [PATCH 061/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E8=BF=9E?= =?UTF-8?q?=E7=BB=AD=E5=A4=9A=E7=BA=A7=E9=83=A8=E9=97=A8=E8=B4=9F=E8=B4=A3?= =?UTF-8?q?=E4=BA=BA=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...skCandidateAbstractDeptLeaderStrategy.java | 42 +++++++++---------- ...TaskCandidateDeptLeaderMultiStrategy.java} | 10 +++-- ...dateStartUserDeptLeaderMultiStrategy.java} | 11 ++--- ...kCandidateStartUserDeptLeaderStrategy.java | 14 +++++-- .../enums/BpmTaskCandidateStrategyEnum.java | 6 +-- 5 files changed, 44 insertions(+), 39 deletions(-) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/{BpmTaskCandidateMultiLevelDeptLeaderStrategy.java => BpmTaskCandidateDeptLeaderMultiStrategy.java} (74%) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/{BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java => BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java} (86%) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java index 314428023..7a6e7a9e1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java @@ -6,10 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类 @@ -25,44 +22,43 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy implements BpmT } /** - * 获取上级部门的负责人 + * 获得指定层级的部门负责人,只有第 level 的负责人 * - * @param assignDept 指定部门 - * @param level 第几级 - * @return 部门负责人 Id + * @param dept 指定部门 + * @param level 第几级 + * @return 部门负责人的编号 */ - protected Long getAssignLevelDeptLeaderId(DeptRespDTO assignDept, Integer level) { + protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) { Assert.isTrue(level > 0, "level 必须大于 0"); - if (assignDept == null) { + if (dept == null) { return null; } - DeptRespDTO dept = assignDept; + DeptRespDTO currentDept = dept; for (int i = 1; i < level; i++) { - DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); + DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()); if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人 break; } - dept = parentDept; + currentDept = parentDept; } - return dept.getLeaderUserId(); + return currentDept.getLeaderUserId(); } /** - * 获取连续上级部门的负责人, 包含指定部门的负责人 + * 获得连续层级的部门负责人,包含 [1, level] 的负责人 * - * @param assignDeptIds 指定部门 Ids - * @param level 第几级 + * @param deptIds 指定部门编号数组 + * @param level 最大层级 * @return 连续部门负责人 Id */ - protected Set getMultiLevelDeptLeaderIds(List assignDeptIds, Integer level) { + protected Set getMultiLevelDeptLeaderIds(List deptIds, Integer level) { Assert.isTrue(level > 0, "level 必须大于 0"); - if (CollUtil.isEmpty(assignDeptIds)) { - return Collections.emptySet(); + if (CollUtil.isEmpty(deptIds)) { + return new HashSet<>(); } Set deptLeaderIds = new LinkedHashSet<>(); // 保证有序 - DeptRespDTO dept; - for (Long deptId : assignDeptIds) { - dept = deptApi.getDept(deptId); + for (Long deptId : deptIds) { + DeptRespDTO dept = deptApi.getDept(deptId); for (int i = 0; i < level; i++) { if (dept.getLeaderUserId() != null) { deptLeaderIds.add(dept.getLeaderUserId()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java similarity index 74% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java index 60730ed1b..a7aa0c270 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateMultiLevelDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -18,20 +18,21 @@ import java.util.Set; * @author jason */ @Component -public class BpmTaskCandidateMultiLevelDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { +public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { - public BpmTaskCandidateMultiLevelDeptLeaderStrategy(DeptApi deptApi) { + public BpmTaskCandidateDeptLeaderMultiStrategy(DeptApi deptApi) { super(deptApi); } @Override public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.MULTI_LEVEL_DEPT_LEADER; + return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI; } @Override public void validateParam(String param) { - // 参数格式: ,分割。前面的部门Id. 可以为多个。 最后一个为部门层级 + // TODO @jason:是不是可以 | 分隔 deptId 数组,和 level;这样后续可以加更多的参数。 + // 参数格式: , 分割。前面的部门编号,可以为多个。最后一个为部门层级 List params = StrUtils.splitToLong(param, ","); List> splitList = CollUtil.split(params, params.size() - 1); Assert.isTrue(splitList.size() == 2, "参数格式不匹配"); @@ -41,6 +42,7 @@ public class BpmTaskCandidateMultiLevelDeptLeaderStrategy extends BpmTaskCandida @Override public Set calculateUsers(DelegateExecution execution, String param) { + // TODO @jason:是不是可以 | 分隔 deptId 数组,和 level;这样后续可以加更多的参数。 // 参数格式: ,分割。前面的部门Id. 可以为多个。 最后一个为部门层级 List params = StrUtils.splitToLong(param, ","); List> splitList = CollUtil.split(params, params.size() - 1); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java similarity index 86% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java index 26c047563..d901b34fc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java @@ -15,7 +15,7 @@ import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import java.util.Collections; +import java.util.HashSet; import java.util.Set; import static cn.hutool.core.collection.ListUtil.toList; @@ -26,7 +26,7 @@ import static cn.hutool.core.collection.ListUtil.toList; * @author jason */ @Component -public class BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { @Resource @Lazy @@ -35,13 +35,13 @@ public class BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy extends BpmTa @Resource private AdminUserApi adminUserApi; - public BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy(DeptApi deptApi) { + public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(DeptApi deptApi) { super(deptApi); } @Override public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.START_USER_MULTI_LEVEL_DEPT_LEADER; + return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI; } @Override @@ -55,9 +55,10 @@ public class BpmTaskCandidateStartUserMultiLevelDeptLeaderStrategy extends BpmTa // 获得流程发起人 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + // 获取发起人的 multi 部门负责人 DeptRespDTO dept = getStartUserDept(startUserId); if (dept == null) { - return Collections.emptySet(); + return new HashSet<>(); } return getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java index 972d2909d..1d8a6feff 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java @@ -15,10 +15,10 @@ import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import java.util.HashSet; import java.util.Set; import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static java.util.Collections.emptySet; /** * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类 @@ -27,11 +27,14 @@ import static java.util.Collections.emptySet; */ @Component public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { + @Resource - @Lazy + @Lazy // 避免循环依赖 private BpmProcessInstanceService processInstanceService; + @Resource private AdminUserApi adminUserApi; + @Override public BpmTaskCandidateStrategyEnum getStrategy() { return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; @@ -52,10 +55,13 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat // 获得流程发起人 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); - + // 获取发起人的部门负责人 DeptRespDTO dept = getStartUserDept(startUserId); + if (dept == null) { + return new HashSet<>(); + } Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 - return deptLeaderId != null ? asSet(deptLeaderId) : emptySet(); + return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); } /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index ac024e158..240aa18dc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -21,13 +21,13 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { ROLE(10, "角色"), DEPT_MEMBER(20, "部门的成员"), // 包括负责人 DEPT_LEADER(21, "部门的负责人"), - MULTI_LEVEL_DEPT_LEADER(23, "连续多级部门的负责人"), + MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), POST(22, "岗位"), USER(30, "用户"), START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 - START_USER_DEPT_LEADER(37, "发起人部门负责人"), // 可以是发起人上级部门负责人 - START_USER_MULTI_LEVEL_DEPT_LEADER(38, "发起人连续多级部门的负责人"), + START_USER_DEPT_LEADER(37, "发起人部门负责人"), + START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), USER_GROUP(40, "用户组"), EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager ASSIGN_EMPTY(1, "审批人为空"), From b98421dfc620046a01949c6836db8914012fee01 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 21:20:10 +0800 Subject: [PATCH 062/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=8A=84?= =?UTF-8?q?=E9=80=81=E7=9A=84=E5=AE=9E=E7=8E=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/convert/task/BpmTaskConvert.java | 1 + ...stants.java => BpmnVariableConstants.java} | 4 ++-- .../core/enums/SimpleModelConstants.java | 7 +----- .../BpmCopyTaskDelegate.java | 12 +++++++--- .../flowable/core/util/FlowableUtils.java | 16 ++++++------- .../flowable/core/util/SimpleModelUtils.java | 24 ++++++++----------- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskServiceImpl.java | 14 +++++------ 8 files changed, 39 insertions(+), 41 deletions(-) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/{BpmConstants.java => BpmnVariableConstants.java} (95%) rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/{custom/delegate => listener}/BpmCopyTaskDelegate.java (76%) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 23c922541..17c4e329a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -113,6 +113,7 @@ public interface BpmTaskConvert { } if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置 + // TODO @jason:貌似这么返回,主要解决当前审批 task 的表单权限,但是不同抄送人的表单权限,可能不太对。例如说,对 A 抄送人是隐藏某个字段。 taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); // 操作按钮设置 taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey())); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java similarity index 95% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java index d5cd53885..56f211982 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; import org.flowable.engine.runtime.ProcessInstance; /** - * BPM 通用常量 + * BPM Variable 通用常量 * * @author 芋道源码 */ -public class BpmConstants { +public class BpmnVariableConstants { /** * 流程实例的变量 - 状态 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java index 33e2c016e..2275f0c12 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; +// TODO @jason:要不合并到 BpmnModelConstants 那 /** * 仿钉钉快搭 JSON 常量信息 * @@ -7,12 +8,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; */ public interface SimpleModelConstants { - // TODO @芋艿:审批方式的名字,可能要看下; - /** - * 审批方式属性 - */ - String APPROVE_METHOD_ATTRIBUTE = "approveMethod"; - // TODO @芋艿:条件表达式的字段名 /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java similarity index 76% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java index 96937b559..9c2341294 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate; +package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; @@ -11,13 +11,19 @@ import org.springframework.stereotype.Component; import java.util.Set; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate.BEAN_NAME; + /** * 处理抄送用户的 {@link JavaDelegate} 的实现类 * + * 目前只有快搭模式的【抄送节点】使用 + * * @author jason */ -@Component -public class BpmCopyTaskDelegate implements JavaDelegate { +@Component(BEAN_NAME) +public class BpmCopyTaskDelegate implements JavaDelegate { + + public static final String BEAN_NAME = "bpmCopyTaskDelegate"; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java index d2810fa85..6456c943a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.common.engine.impl.el.ExpressionManager; @@ -91,7 +91,7 @@ public class FlowableUtils { * @return 状态 */ private static Integer getProcessInstanceStatus(Map processVariables) { - return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); } /** @@ -115,7 +115,7 @@ public class FlowableUtils { * @return 过滤后的表单 */ public static Map filterProcessInstanceFormVariable(Map processVariables) { - processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); return processVariables; } @@ -128,7 +128,7 @@ public class FlowableUtils { @SuppressWarnings("unchecked") public static Map> getStartUserSelectAssignees(ProcessInstance processInstance) { return (Map>) processInstance.getProcessVariables().get( - BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); } // ========== Task 相关的工具方法 ========== @@ -140,7 +140,7 @@ public class FlowableUtils { * @return 状态 */ public static Integer getTaskStatus(TaskInfo task) { - return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); } /** @@ -150,7 +150,7 @@ public class FlowableUtils { * @return 审批原因 */ public static String getTaskReason(TaskInfo task) { - return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON); + return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON); } /** @@ -174,8 +174,8 @@ public class FlowableUtils { * @return 过滤后的表单 */ public static Map filterTaskFormVariable(Map taskLocalVariables) { - taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS); - taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON); + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS); + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON); return taskLocalVariables; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 8a124c2f3..4f09b7bbc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.B import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; @@ -277,20 +278,22 @@ public class SimpleModelUtils { private static List buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) { List list = new ArrayList<>(); switch (nodeType) { - case START_NODE: { - // @芋艿 改成 convert 是不是好理解一点 + case START_NODE: { // 开始节点 StartEvent startEvent = convertStartNode(node); list.add(startEvent); break; } - case APPROVE_NODE: { - // TODO @芋艿 改成 convertXXXNode, , 方面里面使用 buildBpmnXXXNode. 是否更好理解 - // 转换审批节点 + case END_NODE: { // 结束节点 + EndEvent endEvent = convertEndNode(node); + list.add(endEvent); + break; + } + case APPROVE_NODE: { // 审批节点 List flowElements = convertApproveNode(node); list.addAll(flowElements); break; } - case COPY_NODE: { + case COPY_NODE: { // 抄送节点 ServiceTask serviceTask = convertCopyNode(node); list.add(serviceTask); break; @@ -316,11 +319,6 @@ public class SimpleModelUtils { list.add(inclusiveGateway); break; } - case END_NODE: { - EndEvent endEvent = convertEndNode(node); - list.add(endEvent); - break; - } default: { // TODO 其它节点类型的实现 } @@ -389,13 +387,11 @@ public class SimpleModelUtils { serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - serviceTask.setImplementation("${bpmCopyTaskDelegate}"); + serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}"); // 添加抄送候选人元素 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); - // 添加表单字段权限属性元素 - // TODO @芋艿:这块关注下哈; addFormFieldsPermission(node.getFieldsPermission(), serviceTask); return serviceTask; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 284739cb8..3cba26090 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index aaa08f60b..428dc3d50 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -19,7 +19,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; @@ -446,7 +446,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 - Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) { return; } @@ -531,7 +531,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { * @param status 状态 */ private void updateTaskStatus(String id, Integer status) { - taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status); + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_STATUS, status); } /** @@ -543,7 +543,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private void updateTaskStatusAndReason(String id, Integer status, String reason) { updateTaskStatus(id, status); - taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason); + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_REASON, reason); } @Override @@ -692,7 +692,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 taskList.forEach(task -> { - Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { return; } @@ -884,7 +884,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public void processTaskCreated(Task task) { - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); if (status != null) { log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); return; @@ -908,7 +908,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 2. 更新 task 状态 + 原因 - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); if (BpmTaskStatusEnum.isEndStatus(status)) { log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); return; From 3a433e8226ca72643118efa47a00593f495a2125 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 17 Aug 2024 22:46:10 +0800 Subject: [PATCH 063/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=BF=9E=E7=BB=AD=E5=A4=9A?= =?UTF-8?q?=E7=BA=A7=E9=83=A8=E9=97=A8=E8=B4=9F=E8=B4=A3=E4=BA=BAreview=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...mTaskCandidateDeptLeaderMultiStrategy.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java index a7aa0c270..c0bbef98a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -31,23 +31,19 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs @Override public void validateParam(String param) { - // TODO @jason:是不是可以 | 分隔 deptId 数组,和 level;这样后续可以加更多的参数。 - // 参数格式: , 分割。前面的部门编号,可以为多个。最后一个为部门层级 - List params = StrUtils.splitToLong(param, ","); - List> splitList = CollUtil.split(params, params.size() - 1); - Assert.isTrue(splitList.size() == 2, "参数格式不匹配"); - deptApi.validateDeptList(splitList.get(0)); - Assert.isTrue(splitList.get(1).get(0) > 0, "部门层级必须大于 0"); + // 参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + String[] params = param.split("\\|"); + Assert.isTrue(params.length == 2, "参数格式不匹配"); + deptApi.validateDeptList(StrUtils.splitToLong(params[0], ",")); + Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0"); } @Override public Set calculateUsers(DelegateExecution execution, String param) { - // TODO @jason:是不是可以 | 分隔 deptId 数组,和 level;这样后续可以加更多的参数。 + // 参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 // 参数格式: ,分割。前面的部门Id. 可以为多个。 最后一个为部门层级 - List params = StrUtils.splitToLong(param, ","); - List> splitList = CollUtil.split(params, params.size() - 1); - Long level = splitList.get(1).get(0); - return getMultiLevelDeptLeaderIds(splitList.get(0), level.intValue()); + String[] params = param.split("\\|"); + return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1])); } } From 14c8b591d342f0ce764204c71c8a8d36a43924d9 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Aug 2024 22:57:44 +0800 Subject: [PATCH 064/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=8A=84?= =?UTF-8?q?=E9=80=81=E7=9A=84=E5=AE=9E=E7=8E=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/simple/BpmSimpleModelNodeVO.java | 17 +++++++++-------- ...BpmTaskCandidateDeptLeaderMultiStrategy.java | 6 +----- .../flowable/core/util/SimpleModelUtils.java | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index d68a5d581..e444085b4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -31,8 +31,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "模型节点名称", example = "领导审批") private String name; - // TODO @jason:要不改成 placeholder 和一般 Element-Plus 组件一致。占位符,用于展示。@芋艿。这个不是 placeholder 占位符的含义。节点配置后。节点展示的内容,不知道取什么名字好??? - // TODO @jason:【回复】占位文本(showText)是指当一个文本框没有被 focus 的时候显示的是提示文字,当他被点击之后就显示空白。。。虽然不是完全精准,但是 placeholder 相对正式点~ + // TODO @jason:和 gpt 大模型对了下这个字段的命名,貌似叫 displayText 合适点。可以等最后我们全局替换下。(优先级:低) @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") private String showText; @@ -43,7 +42,7 @@ public class BpmSimpleModelNodeVO { private List conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用 @Schema(description = "节点的属性") - private Map attributes; // TODO @jason:建议是字段分拆下;类似说: + private Map attributes; // TODO @jason:这个字段,目前只有条件表达式使用;是不是搞的更巨像。TODO @芋艿 // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到 /** @@ -95,8 +94,8 @@ public class BpmSimpleModelNodeVO { */ private AssignEmptyHandler assignEmptyHandler; - @Data @Schema(description = "审批节点拒绝处理策略") + @Data public static class RejectHandler { @Schema(description = "拒绝处理类型", example = "1") @@ -107,9 +106,9 @@ public class BpmSimpleModelNodeVO { private String returnNodeId; } - @Data @Schema(description = "审批节点超时处理策略") @Valid + @Data public static class TimeoutHandler { @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") @@ -130,8 +129,8 @@ public class BpmSimpleModelNodeVO { } - @Data @Schema(description = "空处理策略") + @Data @Valid public static class AssignEmptyHandler { @@ -145,12 +144,14 @@ public class BpmSimpleModelNodeVO { } - @Data @Schema(description = "操作按钮设置") + @Data + @Valid public static class OperationButtonSetting { + // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。 @Schema(description = "按钮 Id", example = "1") - private Integer id; + private Integer id; @Schema(description = "显示名称", example = "审批") private String displayName; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java index c0bbef98a..ce4ec5225 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; @@ -9,7 +8,6 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Set; /** @@ -31,7 +29,7 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs @Override public void validateParam(String param) { - // 参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级 String[] params = param.split("\\|"); Assert.isTrue(params.length == 2, "参数格式不匹配"); deptApi.validateDeptList(StrUtils.splitToLong(params[0], ",")); @@ -40,8 +38,6 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs @Override public Set calculateUsers(DelegateExecution execution, String param) { - // 参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 - // 参数格式: ,分割。前面的部门Id. 可以为多个。 最后一个为部门层级 String[] params = param.split("\\|"); return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1])); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 4f09b7bbc..5beb0071d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -454,7 +454,7 @@ public class SimpleModelUtils { addFormFieldsPermission(node.getFieldsPermission(), userTask); // 添加操作按钮配置属性元素 addButtonsSetting(node.getButtonsSetting(), userTask); - // 处理多实例 + // 处理多实例(审批方式) processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); // 添加任务被拒绝的处理元素 addTaskRejectElements(node.getRejectHandler(), userTask); From 5679b4ef95b8a62ceeeef3256353239c04ef9e78 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 21 Aug 2024 20:55:54 +0800 Subject: [PATCH 065/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=96=B0=E5=A2=9E=E5=8F=91?= =?UTF-8?q?=E8=B5=B7=E4=BA=BA=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 22 +++--- ...mTaskCandidateDeptLeaderMultiStrategy.java | 2 - .../core/enums/BpmnModelConstants.java | 10 +++ .../core/enums/BpmnVariableConstants.java | 6 ++ .../flowable/core/util/SimpleModelUtils.java | 74 +++++++++++++----- .../bpm/service/task/BpmTaskServiceImpl.java | 78 ++++++++++--------- 6 files changed, 127 insertions(+), 65 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 85067269c..34910d848 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -19,18 +19,20 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; // @芋艿 感觉还是用 START_NODE . END_NODE 比较好. + // 0 1 开始和结束 START_NODE(0, "开始节点"), - END_NODE(-2, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; + END_NODE(1, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; - APPROVE_NODE(1, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 - COPY_NODE(2, "抄送人节点"), + // 10 ~ 49 各种节点 + START_USER_NODE(10, "发起人节点"), // 发起人节点。前端的开始节点,Id 固定 + APPROVE_NODE(11, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 + COPY_NODE(12, "抄送人节点"), - CONDITION_NODE(3, "条件节点"), // 用于构建流转条件的表达式 - CONDITION_BRANCH_NODE(4, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? - PARALLEL_BRANCH_NODE(5, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 -// PARALLEL_BRANCH_JOIN_NODE(6, "并行分支聚合节点"), - INCLUSIVE_BRANCH_FORK_NODE(7, "包容网关分叉节点"), - INCLUSIVE_BRANCH_JOIN_NODE(8, "包容网关聚合节点"), + // 50 ~ 条件分支 + CONDITION_NODE(50, "条件节点"), // 用于构建流转条件的表达式 + CONDITION_BRANCH_NODE(51, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? + PARALLEL_BRANCH_NODE(52, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 + INCLUSIVE_BRANCH_NODE(53, "包容分叉节点"), // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~ // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。 ; @@ -48,7 +50,7 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { public static boolean isBranchNode(Integer type) { return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) - || Objects.equals(INCLUSIVE_BRANCH_FORK_NODE.getType(), type) ; + || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type) ; } public static BpmSimpleModelNodeType valueOf(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java index c0bbef98a..e1d7028da 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; @@ -9,7 +8,6 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Set; /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 88f55ebfb..b6e3d776a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -99,4 +99,14 @@ public interface BpmnModelConstants { */ String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; + /** + * BPMN Start Event Node Id + */ + String START_EVENT_NODE_ID = "StartEvent"; + + /** + * BPMN Start Event Node Name + */ + String START_EVENT_NODE_NAME = "开始"; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java index 56f211982..f1d4e8fff 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -30,6 +30,12 @@ public class BpmnVariableConstants { */ public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; + /** + * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; /** * 任务的变量 - 状态 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 4f09b7bbc..52060b927 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -9,7 +9,10 @@ import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; -import cn.iocoder.yudao.module.bpm.enums.definition.*; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; @@ -17,12 +20,18 @@ import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; @@ -76,19 +85,29 @@ public class SimpleModelUtils { process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么? bpmnModel.addProcess(process); - // 前端模型数据结构 - // 从 SimpleModel 构建 FlowNode 并添加到 Main Process - traverseNodeToBuildFlowNode(simpleModelNode, process); + // 目前前端的第一个节点是 发起人节点。这里构建一个StartNode. 用于创建 Bpmn 的 StartEvent 节点 + BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode(); + startNode.setChildNode(simpleModelNode); + // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process + traverseNodeToBuildFlowNode(startNode, process); // 找到 end event EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent); // 构建并添加节点之间的连线 Sequence Flow - traverseNodeToBuildSequenceFlow(process, simpleModelNode, endEvent.getId()); + traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); return bpmnModel; } + private static BpmSimpleModelNodeVO buildStartSimpleModelNode() { + BpmSimpleModelNodeVO startNode = new BpmSimpleModelNodeVO(); + startNode.setId(START_EVENT_NODE_ID); + startNode.setName(START_EVENT_NODE_NAME); + startNode.setType(START_NODE.getType()); + return startNode; + } + // TODO @芋艿:在优化下这个注释 private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { // 1.1 无效节点返回 @@ -288,6 +307,11 @@ public class SimpleModelUtils { list.add(endEvent); break; } + case START_USER_NODE: { // 发起人节点 + UserTask userTask = convertStartUserNode(node); + list.add(userTask); + break; + } case APPROVE_NODE: { // 审批节点 List flowElements = convertApproveNode(node); list.addAll(flowElements); @@ -309,14 +333,8 @@ public class SimpleModelUtils { break; } - case INCLUSIVE_BRANCH_FORK_NODE: { - InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.TRUE); - list.add(inclusiveGateway); - break; - } - case INCLUSIVE_BRANCH_JOIN_NODE: { - InclusiveGateway inclusiveGateway = convertInclusiveBranchNode(node, Boolean.FALSE); - list.add(inclusiveGateway); + case INCLUSIVE_BRANCH_NODE: { + // TODO jason 待实现 break; } default: { @@ -326,6 +344,11 @@ public class SimpleModelUtils { return list; } + private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) { + return buildBpmnStartUserTask(node); + } + + private static List convertApproveNode(BpmSimpleModelNodeVO node) { List flowElements = new ArrayList<>(); UserTask userTask = buildBpmnUserTask(node); @@ -444,7 +467,7 @@ public class SimpleModelUtils { // 如果不是审批人节点,则直接返回 addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType())); - if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { + if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) { return userTask; } @@ -576,17 +599,32 @@ public class SimpleModelUtils { // ========== 各种 build 节点的方法 ========== - private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) { + private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); - // TODO 芋艿 + jason:要不要在开启节点后面,加一个“发起人”任务节点,然后自动审批通过 // @芋艿 这个是不是由前端来实现。 默认开始节点后面跟一个 “发起人”的审批节点(审批人是发起人自己)。 - // 我看有些平台这个审批节点允许删除,有些不允许。由用户决定 return startEvent; } + private static UserTask buildBpmnStartUserTask(BpmSimpleModelNodeVO node) { + UserTask userTask = new UserTask(); + userTask.setId(node.getId()); + userTask.setName(node.getName()); + // 人工审批 + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString()); + // 候选人策略为发起人自己 + addCandidateElements(START_USER.getStrategy(),null, userTask); + // 添加表单字段权限属性元素 + addFormFieldsPermission(node.getFieldsPermission(), userTask); + // 添加操作按钮配置属性元素. + addButtonsSetting(node.getButtonsSetting(), userTask); + // 使用自动通过策略。TODO @芋艿 复用了SKIP, 是否需要新加一个策略 + addAssignStartUserHandlerType(SKIP.getType(), userTask); + + return userTask; + } private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 428dc3d50..7a6cea8c8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -59,6 +59,7 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; /** * 流程任务实例 Service 实现类 @@ -194,7 +195,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 为什么判断 assignee 非空的情况下? // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 if (StrUtil.isNotBlank(task.getAssignee()) - && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { + && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); } return task; @@ -618,6 +619,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); }); + // 设置流程变量节点驳回标记。用于驳回到节点。不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略 而自动通过 + runtimeService.setVariable(currentTask.getProcessInstanceId(), + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); // 3. 执行驳回 runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) @@ -894,7 +898,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 重要补充说明:该方法目前主要有两个情况会调用到: - * + *

* 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 */ @@ -933,46 +937,50 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); return; } - // 审批人与提交人为同一人时,根据策略进行处理 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); - if (bpmnModel == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); - return; - } - FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); + // 判断是否为回退或者驳回 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); + if (!BooleanUtil.isTrue(returnTaskFlag)) { // 如果是回退或者驳回不走这个策略 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); - // 情况一:自动跳过 - if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); - return; - } - // 情况二:转交给部门负责人审批 - if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); - Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); - DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; - Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); - // 找不到部门负责人的情况下,自动审批通过 - // noinspection DataFlowIssue - if (dept.getLeaderUserId() == null) { + // 情况一:自动跳过 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); return; } - // 找得到部门负责人的情况下,修改负责人 - if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { - getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() - .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); - return; + // 情况二:转交给部门负责人审批 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); + // 找不到部门负责人的情况下,自动审批通过 + // noinspection DataFlowIssue + if (dept.getLeaderUserId() == null) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + return; + } + // 找得到部门负责人的情况下,修改负责人 + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); + return; + } + // 如果部门负责人是自己,还是自己审批吧~ } - // 如果部门负责人是自己,还是自己审批吧~ } } From 764a242d07e855ef3238323f1ddd310e264b407e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 21 Aug 2024 22:16:19 +0800 Subject: [PATCH 066/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91BPM=EF=BC=9A=E5=A2=9E=E5=8A=A0=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E9=BB=98=E8=AE=A4=E7=9A=84=E5=8F=91=E8=B5=B7=E4=BA=BA?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 8 +++---- .../core/enums/BpmnModelConstants.java | 1 - .../core/enums/BpmnVariableConstants.java | 4 +++- .../flowable/core/util/SimpleModelUtils.java | 24 +++++++------------ 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 34910d848..d89789751 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -17,15 +17,13 @@ import java.util.Objects; @AllArgsConstructor public enum BpmSimpleModelNodeType implements IntArrayValuable { - // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; - // @芋艿 感觉还是用 START_NODE . END_NODE 比较好. - // 0 1 开始和结束 + // 0 ~ 1 开始和结束 START_NODE(0, "开始节点"), - END_NODE(1, "结束节点"), // TODO @jaosn:挪到 START_EVENT_NODE 后; + END_NODE(1, "结束节点"), // 10 ~ 49 各种节点 START_USER_NODE(10, "发起人节点"), // 发起人节点。前端的开始节点,Id 固定 - APPROVE_NODE(11, "审批人节点"), // TODO @jaosn:是不是这里从 10 开始好点;相当于说,0-9 给开始和结束;10-19 给各种节点;20-29 给各种条件; TODO 后面改改 + APPROVE_NODE(11, "审批人节点"), COPY_NODE(12, "抄送人节点"), // 50 ~ 条件分支 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index b6e3d776a..ee9fcbfbc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -103,7 +103,6 @@ public interface BpmnModelConstants { * BPMN Start Event Node Id */ String START_EVENT_NODE_ID = "StartEvent"; - /** * BPMN Start Event Node Name */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java index f1d4e8fff..87a323cc1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -30,12 +30,14 @@ public class BpmnVariableConstants { */ public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; + // TODO @芋艿:用于处理,驳回到发起人时,如果被自动通过的逻辑 /** * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} * * @see ProcessInstance#getProcessVariables() */ - public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; + public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; + /** * 任务的变量 - 状态 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index eb3562a80..950324f48 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -9,10 +9,7 @@ import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; @@ -28,7 +25,6 @@ import java.util.Objects; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; @@ -85,7 +81,8 @@ public class SimpleModelUtils { process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么? bpmnModel.addProcess(process); - // 目前前端的第一个节点是 发起人节点。这里构建一个StartNode. 用于创建 Bpmn 的 StartEvent 节点 + // TODO 芋艿:这里可能纠结下,到底前端传递,还是后端创建出来。 + // 目前前端的第一个节点是“发起人节点”。这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode(); startNode.setChildNode(simpleModelNode); // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process @@ -348,7 +345,6 @@ public class SimpleModelUtils { return buildBpmnStartUserTask(node); } - private static List convertApproveNode(BpmSimpleModelNodeVO node) { List flowElements = new ArrayList<>(); UserTask userTask = buildBpmnUserTask(node); @@ -467,7 +463,7 @@ public class SimpleModelUtils { // 如果不是审批人节点,则直接返回 addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType())); - if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) { + if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { return userTask; } @@ -599,12 +595,10 @@ public class SimpleModelUtils { // ========== 各种 build 节点的方法 ========== - private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) { + private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); - // TODO 芋艿 + jason:要不要在开启节点后面,加一个“发起人”任务节点,然后自动审批通过 - // @芋艿 这个是不是由前端来实现。 默认开始节点后面跟一个 “发起人”的审批节点(审批人是发起人自己)。 return startEvent; } @@ -613,18 +607,18 @@ public class SimpleModelUtils { userTask.setId(node.getId()); userTask.setName(node.getName()); // 人工审批 - addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString()); + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType().toString()); // 候选人策略为发起人自己 addCandidateElements(START_USER.getStrategy(),null, userTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node.getFieldsPermission(), userTask); - // 添加操作按钮配置属性元素. + // 添加操作按钮配置属性元素 addButtonsSetting(node.getButtonsSetting(), userTask); - // 使用自动通过策略。TODO @芋艿 复用了SKIP, 是否需要新加一个策略 + // 使用自动通过策略 TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过; addAssignStartUserHandlerType(SKIP.getType(), userTask); - return userTask; } + private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); From 70386263e4775ef21d6b11a0f9e661202f630502 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 24 Aug 2024 14:42:09 +0800 Subject: [PATCH 067/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=AE=A1=E6=89=B9=E9=80=9A=E8=BF=87=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E4=BD=BF=E7=94=A8=20TransactionSynchronizati?= =?UTF-8?q?onManager=20=E7=9A=84=20afterCommit=20=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../behavior/BpmUserTaskActivityBehavior.java | 45 +----------------- .../bpm/service/task/BpmTaskService.java | 7 ++- .../bpm/service/task/BpmTaskServiceImpl.java | 47 +++++++++++++++++-- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java index c2ac89722..592e02bfb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java @@ -1,17 +1,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; -import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.UserTask; @@ -22,8 +13,7 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.TaskHelper; import org.flowable.task.service.TaskService; import org.flowable.task.service.impl.persistence.entity.TaskEntity; -import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Set; @@ -46,6 +36,7 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { } @Override + @Transactional(rollbackFor = Exception.class) protected void handleAssignments(TaskService taskService, String assignee, String owner, List candidateUsers, List candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { @@ -54,39 +45,7 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { // 第二步,设置作为负责人 if (assigneeUserId != null) { TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); - return; } - - // 特殊:处理需要自动通过、不通过的情况 - Integer approveType = BpmnModelUtils.parseApproveType(userTask); - Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTask); - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - - @Override - public void afterCommit() { - // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 - if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { - if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { - SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() - .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); - } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { - SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() - .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); - } - // 特殊情况二:【自动审核】审批类型为自动通过、不通过 - } else { - if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { - SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() - .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); - } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { - SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() - .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); - } - } - - } - - }); } private Long calculateTaskCandidateUsers(DelegateExecution execution) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index b2128ed36..450af5f83 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -188,7 +188,12 @@ public interface BpmTaskService { // ========== Event 事件相关方法 ========== /** - * 处理 Task 创建事件,目前是更新它的状态为审批中 + * 处理 Task 创建事件,目前是 + * + * 1. 更新它的状态为审批中 + * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 + * + * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 * * @param task 任务实体 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 7a6cea8c8..6b8aeaf46 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -12,9 +12,7 @@ import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; @@ -888,12 +886,55 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public void processTaskCreated(Task task) { + // 1. 设置为待办中 Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); if (status != null) { log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); return; } updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); + + // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance == null) { + log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); + return; + } + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement); + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCompletion(int transactionStatus) { + // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以 + // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时 + if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) { + return; + } + // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { + if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); + } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); + } + // 特殊情况二:【自动审核】审批类型为自动通过、不通过 + } else { + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); + } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); + } + } + } + + }); } /** From 56664ca0a0e334d04c0c529fa7c81c1ac192be86 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 24 Aug 2024 15:26:16 +0800 Subject: [PATCH 068/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E9=A9=B3?= =?UTF-8?q?=E5=9B=9E=E5=88=B0=E5=8F=91=E8=B5=B7=E4=BA=BA=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E8=87=AA=E5=8A=A8=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/core/enums/BpmnVariableConstants.java | 3 ++- .../module/bpm/service/task/BpmTaskServiceImpl.java | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java index 87a323cc1..5ccaea306 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -30,10 +30,11 @@ public class BpmnVariableConstants { */ public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; - // TODO @芋艿:用于处理,驳回到发起人时,如果被自动通过的逻辑 /** * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} * + * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 + * * @see ProcessInstance#getProcessVariables() */ public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 6b8aeaf46..0f8a0d3a1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -617,10 +617,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); }); - // 设置流程变量节点驳回标记。用于驳回到节点。不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略 而自动通过 + // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 runtimeService.setVariable(currentTask.getProcessInstanceId(), String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); - // 3. 执行驳回 + + // 4. 执行驳回 runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多) @@ -978,12 +979,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); return; } - // 审批人与提交人为同一人时,根据策略进行处理 + // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { - // 判断是否为回退或者驳回 + // 判断是否为回退或者驳回:如果是回退或者驳回不走这个策略 + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); - if (!BooleanUtil.isTrue(returnTaskFlag)) { // 如果是回退或者驳回不走这个策略 + if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); if (bpmnModel == null) { log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); From da690a2b1c697267041a3456b33fea39ecf7a438 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 24 Aug 2024 22:57:23 +0800 Subject: [PATCH 069/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=96=B0=E5=A2=9E=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=B5=81=E7=A8=8B=E8=A1=A8=E5=8D=95=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A5=E5=8F=A3,=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E5=88=86=E7=A6=BB=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 2 +- .../task/BpmProcessInstanceController.java | 13 +++++++---- ...cessInstanceFormFieldsPermissionReqVO.java | 23 +++++++++++++++++++ .../admin/task/vo/task/BpmTaskRespVO.java | 3 +-- .../bpm/convert/task/BpmTaskConvert.java | 1 + .../core/enums/BpmnModelConstants.java | 5 ++-- .../flowable/core/util/BpmnModelUtils.java | 3 +++ .../task/BpmProcessInstanceService.java | 19 +++++++++++---- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 8 +++++++ .../bpm/service/task/BpmTaskServiceImpl.java | 9 ++++---- 11 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index d89789751..e754a9da1 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -30,7 +30,7 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { CONDITION_NODE(50, "条件节点"), // 用于构建流转条件的表达式 CONDITION_BRANCH_NODE(51, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? PARALLEL_BRANCH_NODE(52, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 - INCLUSIVE_BRANCH_NODE(53, "包容分叉节点"), + INCLUSIVE_BRANCH_NODE(53, "包容分支节点"), // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~ // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。 ; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 29e042409..8b338573e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -4,10 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; @@ -160,4 +157,12 @@ public class BpmProcessInstanceController { return success(true); } + @GetMapping("/form-fields-permission") + @Operation(summary = "获得流程实例表单字段权限", description = "在【我的流程】菜单中,进行调用") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult> getProcessInstanceFormFieldsPermission( + @Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO){ + return success(processInstanceService.getProcessInstanceFormFieldsPermission(reqVO)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java new file mode 100644 index 000000000..069e6ecae --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** + * @author jason + */ +@Schema(description = "管理后台 - 流程实例表单字段权限 Request VO") +@Data +public class BpmProcessInstanceFormFieldsPermissionReqVO { + + @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程实例的编号不能为空") + private String id; + + @Schema(description = "流程活动编号", example = "StartUserNode") + private String activityId; // 对应 BPMN XML 节点 Id + + @Schema(description = "流程任务的编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") + private String taskId; // UserTask 对应的Id +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java index 6d1dff28e..ac64fcccd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -67,10 +67,9 @@ public class BpmTaskRespVO { private List formFields; @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) private Map formVariables; - // TODO @jason:fieldsPermissions + // @芋艿 都改成了 fieldsPermission。 buttonsSetting。和 BpmSimpleModelNodeVO 统一 @Schema(description = "表单字段权限值") private Map fieldsPermission; - // TODO @jason:buttonsSettings @Schema(description = "操作按钮设置值") private Map buttonsSetting; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index 17c4e329a..4131dc502 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -114,6 +114,7 @@ public interface BpmTaskConvert { if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置 // TODO @jason:貌似这么返回,主要解决当前审批 task 的表单权限,但是不同抄送人的表单权限,可能不太对。例如说,对 A 抄送人是隐藏某个字段。 + // @芋艿 表单权限需要分离开。单独的接口来获取了 BpmProcessInstanceService.getProcessInstanceFormFieldsPermission taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); // 操作按钮设置 taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey())); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index ee9fcbfbc..f9198e43b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -80,15 +80,16 @@ public interface BpmnModelConstants { */ String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; - // TODO @jason:上面是 fieldsPermission,然后这里是 buttonsSettings;感觉有点不统一。然后 BpmSimpleModelNodeVO 里面是 fieldsPermission、buttonsSetting; /** * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 */ - String BUTTON_SETTING_ELEMENT = "buttonsSettings"; + String BUTTON_SETTING_ELEMENT = "buttonsSetting"; + /** * BPMN ExtensionElement Attribute, 用于标记按钮编号 */ String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id"; + /** * BPMN ExtensionElement Attribute, 用于标记按钮显示名称 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index e0a3af545..abf379474 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -79,6 +79,9 @@ public class BpmnModelUtils { } public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { + if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { + return null; + } FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); if (flowElement == null) { return null; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 0c7266f8f..0c2bf41cd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceFormFieldsPermissionReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import jakarta.validation.Valid; import org.flowable.engine.history.HistoricProcessInstance; @@ -76,8 +77,6 @@ public interface BpmProcessInstanceService { return convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId); } - // ========== Update 写入相关方法 ========== - /** * 获得流程实例的分页 * @@ -88,6 +87,16 @@ public interface BpmProcessInstanceService { PageResult getProcessInstancePage(Long userId, @Valid BpmProcessInstancePageReqVO pageReqVO); + /** + * 获得流程实例表单字段权限 + * + * @param reqVO 请求消息 + * @return 表单字段权限 + */ + Map getProcessInstanceFormFieldsPermission(@Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO); + + // ========== Update 写入相关方法 ========== + /** * 创建流程实例(提供给前端) * @@ -117,7 +126,7 @@ public interface BpmProcessInstanceService { /** * 管理员取消流程实例 * - * @param userId 用户编号 + * @param userId 用户编号 * @param cancelReqVO 取消信息 */ void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); @@ -125,8 +134,8 @@ public interface BpmProcessInstanceService { /** * 更新 ProcessInstance 为不通过 * - * @param processInstance 流程实例 - * @param reason 理由。例如说,审批不通过时,需要传递该值 + * @param processInstance 流程实例 + * @param reason 理由。例如说,审批不通过时,需要传递该值 */ void updateProcessInstanceReject(ProcessInstance processInstance, String reason); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 3cba26090..b07596d66 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceFormFieldsPermissionReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1. 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 2. 通过流程活动 Id. 查询配置的表单字段权限 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId)) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id if (StrUtil.isNotEmpty(reqVO.getTaskId())) { activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } } if (StrUtil.isEmpty(activityId)) { return null; } // 3. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index b2128ed36..b5acfa6c4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -85,6 +85,14 @@ public interface BpmTaskService { */ Task getTask(String id); + /** + * 获取历史任务 + * + * @param id 任务编号 + * @return 历史任务 + */ + HistoricTaskInstance getHistoricTask(String id); + /** * 根据条件查询正在进行中的任务 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 7a6cea8c8..773b445da 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -214,6 +214,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); } + @Override + public HistoricTaskInstance getHistoricTask(String id) { + return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); + } + @Override public List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) { Assert.notNull(processInstanceId, "processInstanceId 不能为空"); @@ -230,10 +235,6 @@ public class BpmTaskServiceImpl implements BpmTaskService { return taskQuery.list(); } - private HistoricTaskInstance getHistoricTask(String id) { - return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); - } - @Override public List getUserTaskListByReturn(String id) { // 1.1 校验当前任务 task 存在 From dd0bba4752a1c5caad1732db931d79b5663e1180 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 25 Aug 2024 19:19:01 +0800 Subject: [PATCH 070/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/controller/admin/task/BpmProcessInstanceController.java | 1 + .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 8b338573e..56d9dd366 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -157,6 +157,7 @@ public class BpmProcessInstanceController { return success(true); } + // TODO @jason:有个 get-form-fields-permission @GetMapping("/form-fields-permission") @Operation(summary = "获得流程实例表单字段权限", description = "在【我的流程】菜单中,进行调用") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index b07596d66..2d727c3ae 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceFormFieldsPermissionReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1. 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 2. 通过流程活动 Id. 查询配置的表单字段权限 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId)) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id if (StrUtil.isNotEmpty(reqVO.getTaskId())) { activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } } if (StrUtil.isEmpty(activityId)) { return null; } // 3. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceFormFieldsPermissionReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 6b0520d7795796d61054ec31c3c53edacd50218f Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 25 Aug 2024 23:06:08 +0800 Subject: [PATCH 071/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=B5=81=E7=A8=8B=E6=8A=84?= =?UTF-8?q?=E9=80=81=E5=A2=9E=E5=8A=A0=E6=B5=81=E7=A8=8B=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=20Id=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/bpm_update.sql | 6 ++++++ .../admin/task/BpmProcessInstanceController.java | 3 +-- .../dal/dataobject/task/BpmProcessInstanceCopyDO.java | 10 ++++++++-- .../flowable/core/listener/BpmCopyTaskDelegate.java | 2 +- .../service/task/BpmProcessInstanceCopyService.java | 4 +++- .../task/BpmProcessInstanceCopyServiceImpl.java | 7 ++++--- 6 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 sql/mysql/bpm_update.sql diff --git a/sql/mysql/bpm_update.sql b/sql/mysql/bpm_update.sql new file mode 100644 index 000000000..7327b6cdd --- /dev/null +++ b/sql/mysql/bpm_update.sql @@ -0,0 +1,6 @@ +-- ---------------------------- +-- 流程抄送表新加流程活动编号 +-- ---------------------------- +ALTER TABLE `pro-test`.`bpm_process_instance_copy` + ADD COLUMN `activity_id` varchar(64) NULL COMMENT '流程活动编号' AFTER `category`, + MODIFY COLUMN `task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '任务编号' AFTER `category`; \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 56d9dd366..5ec1e4b13 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -157,8 +157,7 @@ public class BpmProcessInstanceController { return success(true); } - // TODO @jason:有个 get-form-fields-permission - @GetMapping("/form-fields-permission") + @GetMapping("/get-form-fields-permission") @Operation(summary = "获得流程实例表单字段权限", description = "在【我的流程】菜单中,进行调用") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") public CommonResult> getProcessInstanceFormFieldsPermission( diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java index 57e729605..4ed4bd2f2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java @@ -48,10 +48,16 @@ public class BpmProcessInstanceCopyDO extends BaseDO { * 冗余 ProcessInstance 的 category 字段 */ private String category; - + /** + * 流程活动编号 + *

+ * 对应 BPMN XML 节点 Id, 用于查询抄送节点的表单字段权限 + * 这里冗余的原因。如果是钉钉易搭的抄送节点 (ServiceTask) 。 使用 taskId 可能查不到对应的 activityId + */ + private String activityId; /** * 任务主键 - * + * // @芋艿 这个 taskId 是不是可以去掉了 * 关联 Task 的 id 属性 */ private String taskId; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java index 9c2341294..3b9b34e59 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java @@ -41,7 +41,7 @@ public class BpmCopyTaskDelegate implements JavaDelegate { // 2. 执行抄送 FlowElement currentFlowElement = execution.getCurrentFlowElement(); processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), - currentFlowElement.getId(), currentFlowElement.getName()); + currentFlowElement.getId(), null, currentFlowElement.getName()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index bd9e531ce..b89f39b31 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -26,10 +26,12 @@ public interface BpmProcessInstanceCopyService { * * @param userIds 抄送的用户编号 * @param processInstanceId 流程编号 + * @param activityId 流程活动编号 id (对应 BPMN XML 节点 Id) + * // TODO 芋艿这个 taskId 是不是可以不要了 * @param taskId 任务编号 * @param taskName 任务名称 */ - void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName); + void createProcessInstanceCopy(Collection userIds, String processInstanceId, String activityId, String taskId, String taskName); /** * 获得抄送的流程的分页 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index 2b110d11d..a0d61035e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -53,11 +53,11 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); } String processInstanceId = task.getProcessInstanceId(); - createProcessInstanceCopy(userIds, processInstanceId, task.getId(), task.getName()); + createProcessInstanceCopy(userIds, processInstanceId, task.getTaskDefinitionKey(), task.getId(), task.getName()); } @Override - public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { + public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String activityId, String taskId, String taskName) { // 1.1 校验流程实例存在 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); if (processInstance == null) { @@ -74,7 +74,8 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy List copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) - .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(taskName)); + .setCategory(processDefinition.getCategory()).setActivityId(activityId) + .setTaskId(taskId).setTaskName(taskName)); processInstanceCopyMapper.insertBatch(copyList); } From 77d518b9c853b824ea9f814ea7bd1148f18b9dcb Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 26 Aug 2024 12:52:11 +0800 Subject: [PATCH 072/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java index 4ed4bd2f2..c7d10396f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java @@ -51,13 +51,13 @@ public class BpmProcessInstanceCopyDO extends BaseDO { /** * 流程活动编号 *

- * 对应 BPMN XML 节点 Id, 用于查询抄送节点的表单字段权限 - * 这里冗余的原因。如果是钉钉易搭的抄送节点 (ServiceTask) 。 使用 taskId 可能查不到对应的 activityId + * 对应 BPMN XML 节点编号,用于查询抄送节点的表单字段权限 + * 这里冗余的原因:如果是钉钉易搭的抄送节点 (ServiceTask),使用 taskId 可能查不到对应的 activityId */ private String activityId; /** * 任务主键 - * // @芋艿 这个 taskId 是不是可以去掉了 + * // @芋艿 这个 taskId 是不是可以去掉了;TODO 可能要留着,因为得知道是来自哪个 task 的抄送 * 关联 Task 的 id 属性 */ private String taskId; From c26862f3e4ec8e30f27b69fa9fafe772f09afa06 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 26 Aug 2024 13:07:42 +0800 Subject: [PATCH 073/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E2=80=9C=E5=AF=BC=E5=85=A5=E2=80=9D=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8E=9F=E7=94=9F=20bpmn?= =?UTF-8?q?=20=E8=AE=BE=E8=AE=A1=E5=99=A8=EF=BC=8C=E5=B7=B2=E7=BB=8F?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/definition/BpmModelController.java | 15 +-------------- .../bpm/service/definition/BpmModelService.java | 3 +-- .../service/definition/BpmModelServiceImpl.java | 4 +--- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index 4f7e3ccf3..610d3615b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -4,9 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.io.IoUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; @@ -30,7 +28,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -100,7 +97,7 @@ public class BpmModelController { @Operation(summary = "新建模型") @PreAuthorize("@ss.hasPermission('bpm:model:create')") public CommonResult createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) { - return success(modelService.createModel(createRetVO, null)); + return success(modelService.createModel(createRetVO)); } @PutMapping("/update") @@ -111,16 +108,6 @@ public class BpmModelController { return success(true); } - @PostMapping("/import") - @Operation(summary = "导入模型") - @PreAuthorize("@ss.hasPermission('bpm:model:import')") - public CommonResult importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException { - BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class); - // 读取文件 - String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false); - return success(modelService.createModel(createReqVO, bpmnXml)); - } - @PostMapping("/deploy") @Operation(summary = "部署模型") @Parameter(name = "id", description = "编号", required = true, example = "1024") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index 2e625f54c..9a20bd475 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -29,10 +29,9 @@ public interface BpmModelService { * 创建流程模型 * * @param modelVO 创建信息 - * @param bpmnXml BPMN XML * @return 创建的流程模型的编号 */ - String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml); + String createModel(@Valid BpmModelCreateReqVO modelVO); /** * 获得流程模块 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 4c8f0f8fd..86a85ffbf 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -90,7 +90,7 @@ public class BpmModelServiceImpl implements BpmModelService { @Override @Transactional(rollbackFor = Exception.class) - public String createModel(@Valid BpmModelCreateReqVO createReqVO, String bpmnXml) { + public String createModel(@Valid BpmModelCreateReqVO createReqVO) { if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { throw exception(MODEL_KEY_VALID); } @@ -106,8 +106,6 @@ public class BpmModelServiceImpl implements BpmModelService { model.setTenantId(FlowableUtils.getTenantId()); // 保存流程定义 repositoryService.saveModel(model); - // 保存 BPMN XML - saveModelBpmnXml(model.getId(), bpmnXml); return model.getId(); } From ff6bee964b5622e2e0184726f1aa59efc58b6b73 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 26 Aug 2024 18:38:32 +0800 Subject: [PATCH 074/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9ABPM=20Mod?= =?UTF-8?q?el=20=E5=A2=9E=E5=8A=A0=20type=20=E6=A0=87=E8=AE=B0=E6=98=AF=20?= =?UTF-8?q?BPMN=20=E8=AE=BE=E8=AE=A1=E5=99=A8=EF=BC=8C=E8=BF=98=E6=98=AF?= =?UTF-8?q?=20SIMPLE=20=E9=92=89=E9=92=89=E8=AE=BE=E8=AE=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/util/object/BeanUtils.java | 7 ++ .../enums/definition/BpmModelTypeEnum.java | 31 ++++++++ .../admin/definition/BpmModelController.java | 15 +++- .../BpmProcessDefinitionController.java | 10 ++- .../vo/model/BpmModeImportReqVO.java | 19 ----- .../vo/model/BpmModeUpdateBpmnReqVO.java | 19 +++++ .../vo/model/BpmModelMetaInfoVO.java | 53 +++++++++++++ .../definition/vo/model/BpmModelRespVO.java | 15 +--- ...reateReqVO.java => BpmModelSaveReqVO.java} | 16 ++-- .../vo/model/BpmModelUpdateReqVO.java | 46 ----------- .../convert/definition/BpmModelConvert.java | 69 ++++------------- .../BpmProcessDefinitionInfoDO.java | 33 ++++++-- .../service/definition/BpmModelService.java | 15 +++- .../definition/BpmModelServiceImpl.java | 77 ++++++++----------- .../BpmProcessDefinitionService.java | 4 +- .../BpmProcessDefinitionServiceImpl.java | 4 +- .../dto/BpmModelMetaInfoRespDTO.java | 46 ----------- 17 files changed, 230 insertions(+), 249 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelTypeEnum.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/{BpmModelCreateReqVO.java => BpmModelSaveReqVO.java} (67%) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java index 720b56510..00ef7db20 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java @@ -59,4 +59,11 @@ public class BeanUtils { return new PageResult<>(list, source.getTotal()); } + public static void copyProperties(Object source, Object target) { + if (source == null || target == null) { + return; + } + BeanUtil.copyProperties(source, target, false); + } + } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelTypeEnum.java new file mode 100644 index 000000000..9863a44e8 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelTypeEnum.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 模型的类型的枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum BpmModelTypeEnum implements IntArrayValuable { + + BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/ + SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器 + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray(); + + private final Integer type; + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index 610d3615b..c5e9d6f10 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -63,7 +62,7 @@ public class BpmModelController { // 拼接数据 // 获得 Form 表单 Set formIds = convertSet(pageResult.getList(), model -> { - BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); + BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); return metaInfo != null ? metaInfo.getFormId() : null; }); Map formMap = formService.getFormMap(formIds); @@ -96,14 +95,14 @@ public class BpmModelController { @PostMapping("/create") @Operation(summary = "新建模型") @PreAuthorize("@ss.hasPermission('bpm:model:create')") - public CommonResult createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) { + public CommonResult createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) { return success(modelService.createModel(createRetVO)); } @PutMapping("/update") @Operation(summary = "修改模型") @PreAuthorize("@ss.hasPermission('bpm:model:update')") - public CommonResult updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) { + public CommonResult updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) { modelService.updateModel(modelVO); return success(true); } @@ -125,6 +124,14 @@ public class BpmModelController { return success(true); } + @PutMapping("/update-bpmn") + @Operation(summary = "修改模型的 BPMN") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) { + modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml()); + return success(true); + } + @DeleteMapping("/delete") @Operation(summary = "删除模型") @Parameter(name = "id", description = "编号", required = true, example = "1024") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java index 52ccd6274..4e7a6243b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java @@ -79,14 +79,20 @@ public class BpmProcessDefinitionController { @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举 public CommonResult> getProcessDefinitionList( @RequestParam("suspensionState") Integer suspensionState) { + // 1.1 获得开启的流程定义 List list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState); if (CollUtil.isEmpty(list)) { return success(Collections.emptyList()); } - - // 获得 BpmProcessDefinitionInfoDO Map + // 1.2 移除不可见的流程定义 Map processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap( convertSet(list, ProcessDefinition::getId)); + list.removeIf(processDefinition -> { + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId()); + return processDefinitionInfo != null && Boolean.FALSE.equals(processDefinitionInfo.getVisible()); + }); + + // 2. 拼接 VO 返回 return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList( list, null, processDefinitionMap, null, null)); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java deleted file mode 100644 index a78a96fcb..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.web.multipart.MultipartFile; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 流程模型的导入 Request VO 相比流程模型的新建来说,只是多了一个 bpmnFile 文件") -@Data -public class BpmModeImportReqVO extends BpmModelCreateReqVO { - - @Schema(description = "BPMN 文件", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "BPMN 文件不能为空") - private MultipartFile bpmnFile; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java new file mode 100644 index 000000000..55d7533d6 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO") +@Data +public class BpmModeUpdateBpmnReqVO { + + @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程编号不能为空") + private String id; + + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "BPMN XML 不能为空") + private String bpmnXml; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java new file mode 100644 index 000000000..870febe61 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * BPM 流程 MetaInfo Response DTO + * 主要用于 { Model#setMetaInfo(String)} 的存储 + * + * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 + * + * @author 芋道源码 + */ +@Data +public class BpmModelMetaInfoVO { + + @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") + @NotEmpty(message = "流程图标不能为空") + @URL(message = "流程图标格式不正确") + private String icon; + + @Schema(description = "流程描述", example = "我是描述") + private String description; + + @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @InEnum(BpmModelTypeEnum.class) + @NotNull(message = "流程类型不能为空") + private Integer type; + + @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @InEnum(BpmModelFormTypeEnum.class) + @NotNull(message = "表单类型不能为空") + private Integer formType; + @Schema(description = "表单编号", example = "1024") + private Long formId; // formType 为 NORMAL 使用,必须非空 + @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", + example = "/bpm/oa/leave/create") + private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 + @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", + example = "/bpm/oa/leave/view") + private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 + + @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否可见不能为空") + private Boolean visible; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java index aad2015c7..40f56033b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java @@ -8,7 +8,7 @@ import java.time.LocalDateTime; @Schema(description = "管理后台 - 流程模型 Response VO") @Data -public class BpmModelRespVO { +public class BpmModelRespVO extends BpmModelMetaInfoVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; @@ -22,27 +22,14 @@ public class BpmModelRespVO { @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") private String icon; - @Schema(description = "流程描述", example = "我是描述") - private String description; - @Schema(description = "流程分类编码", example = "1") private String category; @Schema(description = "流程分类名字", example = "请假") private String categoryName; - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") - private Integer formType; - - @Schema(description = "表单编号", example = "1024") - private Long formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 @Schema(description = "表单名字", example = "请假表单") private String formName; - @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create") - private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 - @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view") - private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java similarity index 67% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java index cd4a2c166..dd15e67ae 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java @@ -1,15 +1,15 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - import jakarta.validation.constraints.NotEmpty; +import lombok.Data; -@Schema(description = "管理后台 - 流程模型的创建 Request VO") +@Schema(description = "管理后台 - 流程模型的保存 Request VO") @Data -public class BpmModelCreateReqVO { +public class BpmModelSaveReqVO extends BpmModelMetaInfoVO { + + @Schema(description = "编号", example = "1024") + private String id; @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") @NotEmpty(message = "流程标识不能为空") @@ -19,7 +19,7 @@ public class BpmModelCreateReqVO { @NotEmpty(message = "流程名称不能为空") private String name; - @Schema(description = "流程描述", example = "我是描述") - private String description; + @Schema(description = "流程分类", example = "1") + private String category; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java deleted file mode 100644 index 94585af3d..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; -import org.hibernate.validator.constraints.URL; - -@Schema(description = "管理后台 - 流程模型的更新 Request VO") -@Data -public class BpmModelUpdateReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotEmpty(message = "编号不能为空") - private String id; - - @Schema(description = "流程名称", example = "芋道") - private String name; - - @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") - @URL(message = "流程图标格式不正确") - private String icon; - - @Schema(description = "流程描述", example = "我是描述") - private String description; - - @Schema(description = "流程分类", example = "1") - private String category; - - @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) - private String bpmnXml; - - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") - @InEnum(BpmModelFormTypeEnum.class) - private Integer formType; - @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024") - private Long formId; - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", - example = "/bpm/oa/leave/create") - private String formCustomCreatePath; - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", - example = "/bpm/oa/leave/view") - private String formCustomViewPath; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index 3fe5cc068..9a355a92f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -1,19 +1,17 @@ package cn.iocoder.yudao.module.bpm.convert.definition; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Model; @@ -23,7 +21,6 @@ import org.mapstruct.factory.Mappers; import java.util.List; import java.util.Map; -import java.util.Objects; /** * 流程模型 Convert @@ -40,7 +37,7 @@ public interface BpmModelConvert { Map categoryMap, Map deploymentMap, Map processDefinitionMap) { List list = CollectionUtils.convertList(pageResult.getList(), model -> { - BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); + BpmModelMetaInfoVO metaInfo = buildMetaInfo(model); BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; BpmCategoryDO category = categoryMap.get(model.getCategory()); Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; @@ -52,7 +49,7 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) { - BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); + BpmModelMetaInfoVO metaInfo = buildMetaInfo(model); BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null); if (ArrayUtil.isNotEmpty(bpmnBytes)) { modelVO.setBpmnXml(new String(bpmnBytes)); @@ -61,20 +58,15 @@ public interface BpmModelConvert { } default BpmModelRespVO buildModel0(Model model, - BpmModelMetaInfoRespDTO metaInfo, BpmFormDO form, BpmCategoryDO category, - Deployment deployment, ProcessDefinition processDefinition) { + BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, + Deployment deployment, ProcessDefinition processDefinition) { BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) .setKey(model.getKey()).setCategory(model.getCategory()) .setCreateTime(DateUtils.of(model.getCreateTime())); // Form - if (metaInfo != null) { - modelRespVO.setFormType(metaInfo.getFormType()).setFormId(metaInfo.getFormId()) - .setFormCustomCreatePath(metaInfo.getFormCustomCreatePath()) - .setFormCustomViewPath(metaInfo.getFormCustomViewPath()); - modelRespVO.setIcon(metaInfo.getIcon()).setDescription(metaInfo.getDescription()); - } + BeanUtils.copyProperties(metaInfo, modelRespVO); if (form != null) { - modelRespVO.setFormId(form.getId()).setFormName(form.getName()); + modelRespVO.setFormName(form.getName()); } // Category if (category != null) { @@ -92,46 +84,15 @@ public interface BpmModelConvert { return modelRespVO; } - default void copyToCreateModel(Model model, BpmModelCreateReqVO bean) { - model.setName(bean.getName()); - model.setKey(bean.getKey()); - model.setMetaInfo(buildMetaInfoStr(null, - null, bean.getDescription(), - null, null, null, null)); + default void copyToModel(Model model, BpmModelSaveReqVO reqVO) { + model.setName(reqVO.getName()); + model.setKey(reqVO.getKey()); + model.setCategory(reqVO.getCategory()); + model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); } - default void copyToUpdateModel(Model model, BpmModelUpdateReqVO bean) { - model.setName(bean.getName()); - model.setCategory(bean.getCategory()); - model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model), - bean.getIcon(), bean.getDescription(), - bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath())); - } - - default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, - String icon, String description, - Integer formType, Long formId, String formCustomCreatePath, String formCustomViewPath) { - if (metaInfo == null) { - metaInfo = new BpmModelMetaInfoRespDTO(); - } - // 只有非空,才进行设置,避免更新时的覆盖 - if (StrUtil.isNotEmpty(icon)) { - metaInfo.setIcon(icon); - } - if (StrUtil.isNotEmpty(description)) { - metaInfo.setDescription(description); - } - if (Objects.nonNull(formType)) { - metaInfo.setFormType(formType); - metaInfo.setFormId(formId); - metaInfo.setFormCustomCreatePath(formCustomCreatePath); - metaInfo.setFormCustomViewPath(formCustomViewPath); - } - return JsonUtils.toJsonString(metaInfo); - } - - default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) { - return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); + default BpmModelMetaInfoVO buildMetaInfo(Model model) { + return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 9ac9252d5..06d81cb5b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -6,7 +6,12 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; import java.util.List; @@ -31,15 +36,21 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { /** * 流程定义的编号 * - * 关联 ProcessDefinition 的 id 属性 + * 关联 {@link ProcessDefinition#getId()} 属性 */ private String processDefinitionId; /** * 流程模型的编号 * - * 关联 Model 的 id 属性 + * 关联 {@link Model#getId()} 属性 */ private String modelId; + /** + * 流程模型的类型 + * + * 枚举 {@link BpmModelFormTypeEnum} + */ + private Integer modelType; /** * 图标 @@ -53,11 +64,12 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { /** * 表单类型 * - * 关联 {@link BpmModelFormTypeEnum} + * 枚举 {@link BpmModelFormTypeEnum} */ private Integer formType; /** * 动态表单编号 + * * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 * * 关联 {@link BpmFormDO#getId()} @@ -65,6 +77,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { private Long formId; /** * 表单的配置 + * * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 * * 冗余 {@link BpmFormDO#getConf()} @@ -72,21 +85,31 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { private String formConf; /** * 表单项的数组 + * * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 * - * 冗余 {@link BpmFormDO#getFields()} ()} + * 冗余 {@link BpmFormDO#getFields()} */ @TableField(typeHandler = JacksonTypeHandler.class) private List formFields; /** * 自定义表单的提交路径,使用 Vue 的路由地址 + * * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 */ private String formCustomCreatePath; /** * 自定义表单的查看路径,使用 Vue 的路由地址 + * * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 */ private String formCustomViewPath; + /** + * 是否可见 + * + * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 + */ + private Boolean visible; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index 9a20bd475..84f7a440f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.bpm.service.definition; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import jakarta.validation.Valid; @@ -31,7 +30,7 @@ public interface BpmModelService { * @param modelVO 创建信息 * @return 创建的流程模型的编号 */ - String createModel(@Valid BpmModelCreateReqVO modelVO); + String createModel(@Valid BpmModelSaveReqVO modelVO); /** * 获得流程模块 @@ -49,12 +48,20 @@ public interface BpmModelService { */ byte[] getModelBpmnXML(String id); + /** + * 修改流程模型的 BPMN XML + * + * @param id 编号 + * @param bpmnXml BPMN XML + */ + void updateModelBpmnXml(String id, String bpmnXml); + /** * 修改流程模型 * * @param updateReqVO 更新信息 */ - void updateModel(@Valid BpmModelUpdateReqVO updateReqVO); + void updateModel(@Valid BpmModelSaveReqVO updateReqVO); /** * 将流程模型,部署成一个流程定义 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 86a85ffbf..6e816c056 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -6,9 +6,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert; @@ -18,7 +17,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; @@ -32,7 +31,6 @@ import org.flowable.engine.repository.ModelQuery; import org.flowable.engine.repository.ProcessDefinition; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import java.util.List; @@ -90,55 +88,58 @@ public class BpmModelServiceImpl implements BpmModelService { @Override @Transactional(rollbackFor = Exception.class) - public String createModel(@Valid BpmModelCreateReqVO createReqVO) { + public String createModel(@Valid BpmModelSaveReqVO createReqVO) { if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { throw exception(MODEL_KEY_VALID); } - // 校验流程标识已经存在 + // 1. 校验流程标识已经存在 Model keyModel = getModelByKey(createReqVO.getKey()); if (keyModel != null) { throw exception(MODEL_KEY_EXISTS, createReqVO.getKey()); } - // 创建流程定义 + // 2.1 创建流程定义 Model model = repositoryService.newModel(); - BpmModelConvert.INSTANCE.copyToCreateModel(model, createReqVO); + BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); model.setTenantId(FlowableUtils.getTenantId()); - // 保存流程定义 + // 2.2 保存流程定义 repositoryService.saveModel(model); return model.getId(); } @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 - public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) { - // 校验流程模型存在 - Model model = getModel(updateReqVO.getId()); + public void updateModel(@Valid BpmModelSaveReqVO updateReqVO) { + // 1. 校验流程模型存在 + Model model = validateModelExists(updateReqVO.getId()); + + // 修改流程定义 + BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); + // 更新模型 + repositoryService.saveModel(model); + } + + private Model validateModelExists(String id) { + Model model = repositoryService.getModel(id); if (model == null) { throw exception(MODEL_NOT_EXISTS); } - - // 修改流程定义 - BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateReqVO); - // 更新模型 - repositoryService.saveModel(model); - // 更新 BPMN XML - saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml()); + return model; } +// // 更新 BPMN XML +// saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml()); + @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 public void deployModel(String id) { // 1.1 校验流程模型存在 - Model model = getModel(id); - if (ObjectUtils.isEmpty(model)) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelExists(id); // 1.2 校验流程图 byte[] bpmnBytes = getModelBpmnXML(model.getId()); validateBpmnXml(bpmnBytes); // 1.3 校验表单已配 - BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); + BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); BpmFormDO form = validateFormConfig(metaInfo); // 1.4 校验任务分配规则已配置 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); @@ -178,10 +179,8 @@ public class BpmModelServiceImpl implements BpmModelService { @Transactional(rollbackFor = Exception.class) public void deleteModel(String id) { // 校验流程模型存在 - Model model = getModel(id); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelExists(id); + // 执行删除 repositoryService.deleteModel(id); // 禁用流程定义 @@ -191,10 +190,7 @@ public class BpmModelServiceImpl implements BpmModelService { @Override public void updateModelState(String id, Integer state) { // 1.1 校验流程模型存在 - Model model = getModel(id); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelExists(id); // 1.2 校验流程定义存在 ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId()); if (definition == null) { @@ -212,10 +208,7 @@ public class BpmModelServiceImpl implements BpmModelService { @Override public BpmSimpleModelNodeVO getSimpleModel(String modelId) { - Model model = getModel(modelId); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelExists(modelId); // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 byte[] jsonBytes = getModelSimpleJson(model.getId()); return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); @@ -224,15 +217,12 @@ public class BpmModelServiceImpl implements BpmModelService { @Override public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) { // 1. 校验流程模型存在 - Model model = getModel(reqVO.getId()); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelExists(reqVO.getId()); // 2.1 JSON 转换成 bpmnModel BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); // 2.2 保存 Bpmn XML - saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); + updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); // 2.3 保存 JSON 数据 saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel())); } @@ -243,7 +233,7 @@ public class BpmModelServiceImpl implements BpmModelService { * @param metaInfo 流程模型元数据 * @return 表单配置 */ - private BpmFormDO validateFormConfig(BpmModelMetaInfoRespDTO metaInfo) { + private BpmFormDO validateFormConfig(BpmModelMetaInfoVO metaInfo) { if (metaInfo == null || metaInfo.getFormType() == null) { throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); } @@ -265,7 +255,8 @@ public class BpmModelServiceImpl implements BpmModelService { } } - private void saveModelBpmnXml(String id, String bpmnXml) { + @Override + public void updateModelBpmnXml(String id, String bpmnXml) { if (StrUtil.isEmpty(bpmnXml)) { return; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java index 5e2e2f805..b1af0b120 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Model; @@ -51,7 +51,7 @@ public interface BpmProcessDefinitionService { * @param form 表单 * @return 流程编号 */ - String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form); + String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form); /** * 更新流程定义状态 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index 99470f6a5..3219af302 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitio import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; @@ -105,7 +105,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ } @Override - public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, + public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form) { // 创建 Deployment 部署 Deployment deploy = repositoryService.createDeployment() diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java deleted file mode 100644 index 8ec9dc64b..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.bpm.service.definition.dto; - -import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; -import lombok.Data; - -/** - * BPM 流程 MetaInfo Response DTO - * 主要用于 { Model#setMetaInfo(String)} 的存储 - * - * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 - * - * @author 芋道源码 - */ -@Data -public class BpmModelMetaInfoRespDTO { - - /** - * 流程图标 - */ - private String icon; - /** - * 流程描述 - */ - private String description; - - /** - * 表单类型 - */ - private Integer formType; - /** - * 表单编号 - * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 - */ - private Long formId; - /** - * 自定义表单的提交路径,使用 Vue 的路由地址 - * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 - */ - private String formCustomCreatePath; - /** - * 自定义表单的查看路径,使用 Vue 的路由地址 - * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 - */ - private String formCustomViewPath; - -} From f03e26bc86f6f8d6068f58066de55ece51ff09ba Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 1 Sep 2024 23:12:04 +0800 Subject: [PATCH 075/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=A2=9E=E5=8A=A0=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=BF=9B=E5=BA=A6=E6=8E=A5=E5=8F=A3=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E7=89=88=20(=E7=94=A8=E4=BA=8E=E6=9F=A5=E8=AF=A2=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=AE=B0=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/bpm_update.sql | 7 +- .../BpmProcessNodeProgressEnum.java | 67 +++++++ .../task/BpmProcessInstanceStatusEnum.java | 6 + .../admin/task/BpmActivityController.java | 3 +- .../task/BpmProcessInstanceController.java | 13 +- .../BpmProcessInstanceProgressRespVO.java | 60 ++++++ .../BpmProcessDefinitionInfoDO.java | 9 +- .../task/BpmProcessInstanceCopyMapper.java | 5 + .../candidate/BpmTaskCandidateInvoker.java | 2 +- .../candidate/BpmTaskCandidateStrategy.java | 13 ++ .../BpmTaskCandidateDeptMemberStrategy.java | 10 + .../BpmTaskCandidateRoleStrategy.java | 9 + .../BpmTaskCandidateStartUserStrategy.java | 13 +- .../BpmTaskCandidateUserStrategy.java | 5 + .../flowable/core/util/SimpleModelUtils.java | 136 +++++++++++++- .../definition/BpmModelServiceImpl.java | 8 +- .../BpmProcessDefinitionService.java | 3 +- .../BpmProcessDefinitionServiceImpl.java | 9 +- .../bpm/service/task/BpmActivityService.java | 31 +++- .../service/task/BpmActivityServiceImpl.java | 174 +++++++++++++++++- .../task/BpmProcessInstanceCopyService.java | 9 + .../BpmProcessInstanceCopyServiceImpl.java | 8 + .../task/BpmProcessInstanceService.java | 14 +- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 8 + .../bpm/service/task/BpmTaskServiceImpl.java | 5 + 26 files changed, 590 insertions(+), 39 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java diff --git a/sql/mysql/bpm_update.sql b/sql/mysql/bpm_update.sql index 7327b6cdd..40a5bc973 100644 --- a/sql/mysql/bpm_update.sql +++ b/sql/mysql/bpm_update.sql @@ -3,4 +3,9 @@ -- ---------------------------- ALTER TABLE `pro-test`.`bpm_process_instance_copy` ADD COLUMN `activity_id` varchar(64) NULL COMMENT '流程活动编号' AFTER `category`, - MODIFY COLUMN `task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '任务编号' AFTER `category`; \ No newline at end of file + MODIFY COLUMN `task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '任务编号' AFTER `category`; + +ALTER TABLE `pro-test`.`bpm_process_definition_info` + ADD COLUMN `model_type` tinyint NOT NULL DEFAULT 10 COMMENT '流程模型的类型' AFTER `model_id`, + ADD COLUMN `simple_model` json NULL COMMENT 'SIMPLE 设计器模型数据' AFTER `form_custom_view_path`, + ADD COLUMN `visible` bit(1) NOT NULL DEFAULT 1 COMMENT '是否可见' AFTER `simple_model`; \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java new file mode 100644 index 000000000..33fa001f1 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程节点进度的枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmProcessNodeProgressEnum { + // 0 未开始 + NOT_START(0,"未开始"), + // 1 ~ 20 进行中 + RUNNING(1, "进行中"), // 节点的进行 + // 特殊的进行中状态 + USER_TASK_DELEGATE(10, "委派中"), // 审批节点 + USER_TASK_APPROVING(11, "向后加签审批通过中"), //向后加签 审批通过中. + USER_TASK_WAIT(12, "待审批"), // 一般用于先前加签 + + // 30 ~ 50 已经结束 + // 30 ~ 40 审批节点的结束状态 + USER_TASK_APPROVE(30, "审批通过"), // 审批节点 + USER_TASK_REJECT(31, "审批不通过"), // 审批节点 + USER_TASK_RETURN(32, "已退回"), // 审批节点 + USER_TASK_CANCEL(34, "已取消"), // 审批节点 + // 40 ~ 50 一般节点的接榫状态 + FINISHED(41, "已结束"), // 一般节点的节点的结束状态 + SKIP(42, "跳过"); // 未执行,跳过的节点 + + private final Integer status; + private final String name; + + public static Integer convertBpmnTaskStatus(Integer taskStatus) { + Integer convertStatus = null; + if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)) { + convertStatus = RUNNING.getStatus(); + } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(taskStatus)) { + convertStatus = USER_TASK_REJECT.getStatus(); + } else if( BpmTaskStatusEnum.APPROVE.getStatus().equals(taskStatus) ) { + convertStatus = USER_TASK_APPROVE.getStatus(); + } else if (BpmTaskStatusEnum.DELEGATE.getStatus().equals(taskStatus)) { + convertStatus = USER_TASK_DELEGATE.getStatus(); + } else if (BpmTaskStatusEnum.APPROVING.getStatus().equals(taskStatus)) { + convertStatus = USER_TASK_APPROVE.getStatus(); + } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(taskStatus)) { + convertStatus = USER_TASK_CANCEL.getStatus(); + } else if (BpmTaskStatusEnum.WAIT.getStatus().equals(taskStatus)) { + convertStatus = USER_TASK_WAIT.getStatus(); + } + return convertStatus; + } + + /** + * 判断用户节点是不是未通过 + * + * @param status 状态 + */ + public static boolean isUserTaskNotApproved(Integer status) { + return ObjectUtils.equalsAny(status, + USER_TASK_REJECT.getStatus(), USER_TASK_RETURN.getStatus(), USER_TASK_CANCEL.getStatus()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java index 720d4f13e..12cf9b6dc 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.bpm.enums.task; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; @@ -36,4 +37,9 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { return ARRAYS; } + public static boolean isProcessEndStatus(Integer status) { + return ObjectUtils.equalsAny(status, + APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java index 8e7e76a3e..6e07859f8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; +import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Parameter; @@ -34,6 +35,6 @@ public class BpmActivityController { @PreAuthorize("@ss.hasPermission('bpm:task:query')") public CommonResult> getActivityList( @RequestParam("processInstanceId") String processInstanceId) { - return success(activityService.getActivityListByProcessInstanceId(processInstanceId)); + return success(BpmActivityConvert.INSTANCE.convertList(activityService.getActivityListByProcessInstanceId(processInstanceId))); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 5ec1e4b13..195eeabf1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -47,6 +47,7 @@ public class BpmProcessInstanceController { private BpmProcessInstanceService processInstanceService; @Resource private BpmTaskService taskService; + @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @@ -158,11 +159,19 @@ public class BpmProcessInstanceController { } @GetMapping("/get-form-fields-permission") - @Operation(summary = "获得流程实例表单字段权限", description = "在【我的流程】菜单中,进行调用") + @Operation(summary = "获得流程实例表单字段权限") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") public CommonResult> getProcessInstanceFormFieldsPermission( - @Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO){ + @Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { return success(processInstanceService.getProcessInstanceFormFieldsPermission(reqVO)); } + @GetMapping("/get-progress") + @Operation(summary = "获得流程实例的进度") + @Parameter(name = "id", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult getProcessInstanceProgress(@RequestParam("id") String id) { + return success(processInstanceService.getProcessInstanceProgress(id)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java new file mode 100644 index 000000000..fc1b1672e --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + + +@Schema(description = "管理后台 - 流程实例的进度 Response VO") +@Data +public class BpmProcessInstanceProgressRespVO { + + @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 + + private List nodeProgressList; + + @Schema(description = "节点进度信息") + @Data + public static class ProcessNodeProgress { + + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") + private String id; // Bpmn XML 节点 Id + @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") + private String name; + private String displayText; + @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 + @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 BpmProcessNodeProgressEnum 枚举 + @Schema(description = "节点的开始时间") + private LocalDateTime startTime; + @Schema(description = "节点的结束时间") + private LocalDateTime endTime; + @Schema(description = "用户列表") + private List userList; + @Schema(description = "分支节点") + private List branchNodes; // 有且仅有条件、并行、包容节点才会有分支节点 + + // TODO 用户意见,评论 + + } + + @Schema(description = "用户信息") + @Data + public static class User { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String nickname; + @Schema(description = "用户头像", example = "芋艿") + private String avatar; + @Schema(description = "是否已处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean processed; + @Schema(description = "用户任务的处理状态", example = "1") + private Integer userTaskStatus; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 06d81cb5b..e2382eb1f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -48,7 +49,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { /** * 流程模型的类型 * - * 枚举 {@link BpmModelFormTypeEnum} + * 枚举 {@link BpmModelTypeEnum} */ private Integer modelType; @@ -105,6 +106,12 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { */ private String formCustomViewPath; + /** + * SIMPLE 设计器模型数据 json 格式 + * + * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。 + */ + private String simpleModel; /** * 是否可见 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java index c5ec50f65..3605c6400 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper public interface BpmProcessInstanceCopyMapper extends BaseMapperX { @@ -18,4 +20,7 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX selectListByProcInstIdAndActId(String processInstanceId, String activityId) { + return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId, BpmProcessInstanceCopyDO::getActivityId, activityId); + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index 21727f748..8b9f98ea5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -150,7 +150,7 @@ public class BpmTaskCandidateInvoker { assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); } - private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { + public BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java index 1534d39c2..64e4328f4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import org.flowable.engine.delegate.DelegateExecution; +import java.util.Collections; import java.util.Set; /** @@ -36,6 +37,18 @@ public interface BpmTaskCandidateStrategy { */ Set calculateUsers(DelegateExecution execution, String param); + + /** + * 基于流程实例,获得任务的候选用户们。 用于获取未执行节点的候选用户们 + * + * @param processInstanceId 流程实例 + * @param param 节点的参数 + * @return 用户编号集合 + */ + default Set calculateUsers(String processInstanceId, String param) { + return Collections.emptySet(); + } + /** * 是否一定要输入参数 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java index f60b1cc8b..1f18c249d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java @@ -41,6 +41,16 @@ public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrat @Override public Set calculateUsers(DelegateExecution execution, String param) { + return calculateUsersByParam(param); + } + + @Override + public Set calculateUsers(String processInstanceId, String param) { + return calculateUsersByParam(param); + } + + private Set calculateUsersByParam(String param) { + Set deptIds = StrUtils.splitToLongSet(param); List users = adminUserApi.getUserListByDeptIds(deptIds); return convertSet(users, AdminUserRespDTO::getId); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java index 0dd178626..a05610938 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java @@ -37,6 +37,15 @@ public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { @Override public Set calculateUsers(DelegateExecution execution, String param) { + return calculateUsersByParam(param); + } + + @Override + public Set calculateUsers(String processInstanceId, String param) { + return calculateUsersByParam(param); + } + + private Set calculateUsersByParam(String param) { Set roleIds = StrUtils.splitToLongSet(param); return permissionApi.getUserRoleIdListByRoleIds(roleIds); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java index 266e229d5..33b1b2696 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -35,8 +35,7 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate @Override public Set calculateUsers(DelegateExecution execution, String param) { - String startUserId = processInstanceService.getProcessInstance(execution.getProcessInstanceId()).getStartUserId(); - return SetUtils.asSet(Long.valueOf(startUserId)); + return getStartUserOfProcessInstance(execution.getProcessInstanceId()); } @Override @@ -44,4 +43,14 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate return false; } + @Override + public Set calculateUsers(String processInstanceId, String param) { + return getStartUserOfProcessInstance(processInstanceId); + } + + private Set getStartUserOfProcessInstance(String processInstanceId) { + String startUserId = processInstanceService.getProcessInstance(processInstanceId).getStartUserId(); + return SetUtils.asSet(Long.valueOf(startUserId)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java index 390e4903a..7a665d7c6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java @@ -36,4 +36,9 @@ public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { return StrUtils.splitToLongSet(param); } + @Override + public Set calculateUsers(String processInstanceId, String param) { + return StrUtils.splitToLongSet(param); + } + } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 950324f48..2c794c520 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -7,24 +7,30 @@ import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ProcessNodeProgress; import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; +import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.RANDOM; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.RATIO; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; @@ -361,7 +367,7 @@ public class SimpleModelUtils { /** * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 * - * @param userTask 审批任务 + * @param userTask 审批任务 * @param timeoutHandler 超时处理器 * @return BoundaryEvent 超时事件 */ @@ -463,7 +469,7 @@ public class SimpleModelUtils { // 如果不是审批人节点,则直接返回 addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType())); - if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { + if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) { return userTask; } @@ -513,7 +519,7 @@ public class SimpleModelUtils { private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); - if (approveMethodEnum == null || approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { + if (approveMethodEnum == null || approveMethodEnum == RANDOM) { return; } // 添加审批方式的扩展属性 @@ -531,7 +537,7 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) { + } else if (approveMethodEnum == RATIO) { Assert.notNull(approveRatio, "通过比例不能为空"); multiInstanceCharacteristics.setCompletionCondition( String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100))); @@ -607,9 +613,9 @@ public class SimpleModelUtils { userTask.setId(node.getId()); userTask.setName(node.getName()); // 人工审批 - addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType().toString()); + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString()); // 候选人策略为发起人自己 - addCandidateElements(START_USER.getStrategy(),null, userTask); + addCandidateElements(START_USER.getStrategy(), null, userTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node.getFieldsPermission(), userTask); // 添加操作按钮配置属性元素 @@ -628,4 +634,114 @@ public class SimpleModelUtils { return endEvent; } + /** + * 遍历简单模型, 构建节点的进度。 TODO 回退节点暂未处理 + * + * @param processInstance 流程实例 + * @param simpleModel 简单模型 + * @param historicActivityList 流程实例的活力列表 + * @param activityInstanceMap 流程实例的活力 Map。 key: activityId + * @param nodeProgresses 节点的进度列表 + * @param returnNodePosition 回退节点的位置。 TODO 处理回退节点,还未处理。还没想好 + */ + public static void traverseNodeToBuildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO simpleModel + , List historicActivityList, Map activityInstanceMap + , List nodeProgresses, List returnNodePosition) { + // 判断是否有效节点 + if (!isValidNode(simpleModel)) { + return; + } + buildNodeProgress(processInstance, simpleModel, nodeProgresses, historicActivityList, activityInstanceMap, returnNodePosition); + // 如果有“子”节点,则递归处理子节点 + traverseNodeToBuildNodeProgress(processInstance, simpleModel.getChildNode(), historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition); + } + + + private static void buildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO node, List nodeProgresses, + List historicActivityList, Map activityInstanceMap, List returnNodePosition) { + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + + ProcessNodeProgress nodeProgress = new ProcessNodeProgress(); + nodeProgress.setNodeType(nodeType.getType()); + nodeProgress.setName(node.getName()); + nodeProgress.setDisplayText(node.getShowText()); + BpmActivityService activityService = SpringUtils.getBean(BpmActivityService.class); + if (!activityInstanceMap.containsKey(node.getId())) { // 说明这些节点没有执行过 + // 1. 得到流程状态 + Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); + // 2. 设置节点状态 + nodeProgress.setStatus(activityService.getNotRunActivityProgressStatus(processInstanceStatus)); + // 3. 抄送节点, 审批节点设置用户列表 + if (COPY_NODE.getType().equals(node.getType()) || + (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()))) { + nodeProgress.setUserList(activityService.getNotRunActivityUserList(processInstance.getId() + , processInstanceStatus, node.getCandidateStrategy(), node.getCandidateParam())); + } + } else { + nodeProgress.setStatus(BpmProcessNodeProgressEnum.FINISHED.getStatus()); // 默认设置成结束状态 + HistoricActivityInstance historicActivity = activityInstanceMap.get(node.getId()); + nodeProgress.setStartTime(DateUtils.of(historicActivity.getStartTime())); + nodeProgress.setEndTime(DateUtils.of(historicActivity.getEndTime())); + nodeProgress.setId(historicActivity.getId()); + switch (nodeType) { + case START_USER_NODE: { // 发起人节点 + nodeProgress.setDisplayText(""); // 发起人节点不需要显示 displayText + // 1. 设置节点的状态 + nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); + // 2. 设置用户信息 + nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, false, historicActivityList)); + break; + } + case APPROVE_NODE: { // 审批节点 + if (USER.getType().equals(node.getApproveType())) { // 人工审批 + // 1. 判断是否多人审批 + boolean isMultiInstance = !RANDOM.getMethod().equals(node.getApproveMethod()); + // 2. 设置节点的状态 + nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, isMultiInstance, historicActivityList)); + // 3. 设置用户信息 + nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, isMultiInstance, historicActivityList)); + } else { + nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); + } + break; + } + case COPY_NODE: { // 抄送节点 + // 1. 设置节点的状态 + nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); + // 2. 设置用户信息 + nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, false, historicActivityList)); + break; + } + + default: { + // TODO 其它节点类型的实现 + } + } + } + // 如果是“分支”节点, + if (BpmSimpleModelNodeType.isBranchNode(node.getType()) + && ArrayUtil.isNotEmpty(node.getConditionNodes())) { + // 网关是否执行了, 执行了。只包含运行的分支。 未执行包含所有的分支 + final boolean executed = activityInstanceMap.containsKey(node.getId()); + LinkedList branchNodeList = new LinkedList<>(); + node.getConditionNodes().forEach(item -> { + // 如果条件节点执行了。 ACT_HI_ACTINST 表会记录 + if (executed) { + if (activityInstanceMap.containsKey(item.getId())) { + List branchReturnNodePosition = new ArrayList<>(); + traverseNodeToBuildNodeProgress(processInstance, item, historicActivityList, activityInstanceMap, branchNodeList, branchReturnNodePosition); + // TODO 处理回退节点 + } + } else { + List branchReturnNodePosition = new ArrayList<>(); + traverseNodeToBuildNodeProgress(processInstance, item, historicActivityList, activityInstanceMap, branchNodeList, branchReturnNodePosition); + // TODO 处理回退节点 + } + }); + nodeProgress.setBranchNodes(branchNodeList); + } + nodeProgresses.add(nodeProgress); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 6e816c056..649537fe0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; @@ -17,7 +18,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; @@ -143,9 +143,11 @@ public class BpmModelServiceImpl implements BpmModelService { BpmFormDO form = validateFormConfig(metaInfo); // 1.4 校验任务分配规则已配置 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); + // 1.5 获取仿钉钉流程设计器模型数据 + byte[] simpleBytes = getModelSimpleJson(model.getId()); // 2.1 创建流程定义 - String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form); + String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleBytes, form); // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 updateProcessDefinitionSuspended(model.getDeploymentId()); @@ -276,7 +278,7 @@ public class BpmModelServiceImpl implements BpmModelService { /** * 挂起 deploymentId 对应的流程定义 - * + *

* 注意:这里一个 deploymentId 只关联一个流程定义 * * @param deploymentId 流程发布Id diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java index b1af0b120..9994cd084 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java @@ -48,10 +48,11 @@ public interface BpmProcessDefinitionService { * @param model 流程模型 * @param modelMetaInfo 流程模型元信息 * @param bpmnBytes BPMN XML 字节数组 + * @param simpleBytes simple model json 字节数组 * @param form 表单 * @return 流程编号 */ - String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form); + String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form); /** * 更新流程定义状态 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index 3219af302..30623c333 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -5,13 +5,13 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; @@ -24,6 +24,7 @@ import org.flowable.engine.repository.ProcessDefinitionQuery; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.nio.charset.StandardCharsets; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -106,7 +107,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @Override public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, - byte[] bpmnBytes, BpmFormDO form) { + byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form) { // 创建 Deployment 部署 Deployment deploy = repositoryService.createDeployment() .key(model.getKey()).name(model.getName()).category(model.getCategory()) @@ -131,7 +132,9 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ // 插入拓展表 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()); + .setModelId(model.getId()).setProcessDefinitionId(definition.getId()).setModelType(modelMetaInfo.getType()) + .setSimpleModel(StrUtil.str(simpleBytes, StandardCharsets.UTF_8)); + if (form != null) { definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java index 4bed16413..3d6b1866b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.bpm.service.task; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO; import org.flowable.engine.history.HistoricActivityInstance; import java.util.List; @@ -18,7 +18,7 @@ public interface BpmActivityService { * @param processInstanceId 流程实例的编号 * @return 活动实例列表 */ - List getActivityListByProcessInstanceId(String processInstanceId); + List getActivityListByProcessInstanceId(String processInstanceId); /** * 获得执行编号对应的活动实例 @@ -28,4 +28,31 @@ public interface BpmActivityService { */ List getHistoricActivityListByExecutionId(String executionId); + /** + * 获取活动的用户列表。 例如:抄送人列表。 审批人列表 + * + * @param historicActivity 活动 + * @param isMultiInstance 是否多实例 (会签,或签 ) + * @param historicActivityList 某个流程实例的所有活动列表 + * @return 用户列表 + */ + List getHistoricActivityUserList(HistoricActivityInstance historicActivity, + Boolean isMultiInstance, List historicActivityList); + + /** + * 获取活动的进度状态。 + * + * @param historicActivity 活动 + * @param isMultiInstance 是否多实例 (会签,或签 ) + * @param historicActivityList 某个流程实例的所有活动列表 + * @return 活动的进度状态 + */ + Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity, + Boolean isMultiInstance, List historicActivityList); + + Integer getNotRunActivityProgressStatus(Integer processInstanceStatus); + + List getNotRunActivityUserList(String processInstanceId, Integer processInstanceStatus + , Integer candidateStrategy, String candidateParam); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java index 1ae9b1df0..d73d5872b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java @@ -1,15 +1,33 @@ package cn.iocoder.yudao.module.bpm.service.task; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.NumberUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgressEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgressEnum.*; /** @@ -22,14 +40,31 @@ import java.util.List; @Validated public class BpmActivityServiceImpl implements BpmActivityService { + /** + * 抄送节点活动类型 + */ + private static final String COPY_NODE_ACTIVITY_TYPE = "serviceTask"; + /** + * 审批节点活动类型 + */ + private static final String APPROVE_NODE_ACTIVITY_TYPE = "userTask"; + @Resource private HistoryService historyService; + @Resource + @Lazy + private BpmTaskService bpmTaskService; + @Resource + private BpmProcessInstanceCopyService bpmProcessInstanceCopyService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Override - public List getActivityListByProcessInstanceId(String processInstanceId) { - List activityList = historyService.createHistoricActivityInstanceQuery() - .processInstanceId(processInstanceId).list(); - return BpmActivityConvert.INSTANCE.convertList(activityList); + public List getActivityListByProcessInstanceId(String processInstanceId) { + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) + .orderByHistoricActivityInstanceStartTime().asc().list(); } @Override @@ -37,4 +72,129 @@ public class BpmActivityServiceImpl implements BpmActivityService { return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); } + @Override + public List getHistoricActivityUserList(HistoricActivityInstance historicActivity + , Boolean isMultiInstance, List historicActivityList) { + Assert.notNull(historicActivity, "historicActivity 不能为 null "); + List returnUserList = Collections.emptyList(); + if (COPY_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) { + Set copyUserIds = bpmProcessInstanceCopyService.getCopyUserIds(historicActivity.getProcessInstanceId(), + historicActivity.getActivityId()); + List userList = adminUserApi.getUserList(copyUserIds); + returnUserList = CollectionUtils.convertList(userList, item -> { + User user = BeanUtils.toBean(item, User.class); + user.setProcessed(Boolean.TRUE); + return user; + }); + } else if (APPROVE_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) { + if (isMultiInstance) { // 多人 (会签 、 或签) // TODO 依次审批可能要特殊处理一下 + // 多个任务列表 + List taskList = CollectionUtils.filterList(historicActivityList, + item -> historicActivity.getActivityId().equals(item.getActivityId())); + List userIds = CollectionUtils.convertList(taskList, item -> NumberUtil.parseLong(item.getAssignee(), null)); + List taskIds = CollectionUtils.convertList(taskList, HistoricActivityInstance::getTaskId); + Map adminUserMap = CollectionUtils.convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); + Map historicTaskInstanceMap = CollectionUtils.convertMap(bpmTaskService.getHistoricTasks(taskIds), HistoricTaskInstance::getId); + returnUserList = CollectionUtils.convertList(taskList, item -> { + AdminUserRespDTO adminUser = adminUserMap.get(NumberUtil.parseLong(item.getAssignee(), null)); + User user = BeanUtils.toBean(adminUser, User.class); + if (user != null) { + HistoricTaskInstance taskInstance = historicTaskInstanceMap.get(item.getTaskId()); + if (taskInstance != null) { + user.setProcessed(taskInstance.getEndTime() != null); + user.setUserTaskStatus(FlowableUtils.getTaskStatus(taskInstance)); + } + } + return user; + }); + } else { + AdminUserRespDTO adminUserResp = adminUserApi.getUser(Long.valueOf(historicActivity.getAssignee())); + if (adminUserResp != null) { + User user = BeanUtils.toBean(adminUserResp, User.class); + // TODO 需要处理加签 + // 查询任务状态 + HistoricTaskInstance historicTask = bpmTaskService.getHistoricTask(historicActivity.getTaskId()); + if (historicTask != null) { + Integer taskStatus = FlowableUtils.getTaskStatus(historicTask); + user.setProcessed(historicTask.getEndTime() != null); + user.setUserTaskStatus(taskStatus); + } + returnUserList = ListUtil.of(user); + } + } + } + return returnUserList; + } + + @Override + public Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity + , Boolean isMultiInstance, List historicActivityList) { + Assert.notNull(historicActivity, "historicActivity 不能为 null "); + Integer progressStatus = null; + if (APPROVE_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) { + if (isMultiInstance) { // 多人 (会签 、 或签) + // 多个任务列表 + List taskList = CollectionUtils.filterList(historicActivityList, + item -> historicActivity.getActivityId().equals(item.getActivityId())); + List taskIds = CollectionUtils.convertList(taskList, HistoricActivityInstance::getTaskId); + Map historicTaskMap = CollectionUtils.convertMap(bpmTaskService.getHistoricTasks(taskIds), HistoricTaskInstance::getId); + for (HistoricActivityInstance activity : taskList) { + if (activity.getEndTime() == null) { + progressStatus = RUNNING.getStatus(); + } else { + HistoricTaskInstance task = historicTaskMap.get(activity.getTaskId()); + if (task != null) { + Integer taskStatus = FlowableUtils.getTaskStatus(task); + progressStatus = BpmProcessNodeProgressEnum.convertBpmnTaskStatus(taskStatus); + } + } + // 运行中或者未通过状态。退出循环 (会签可能需要多人通过) + if (RUNNING.getStatus().equals(progressStatus) || isUserTaskNotApproved(progressStatus)) { + break; + } + } + } else { + HistoricTaskInstance historicTask = bpmTaskService.getHistoricTask(historicActivity.getTaskId()); + if (historicTask != null) { + Integer taskStatus = FlowableUtils.getTaskStatus(historicTask); + progressStatus = BpmProcessNodeProgressEnum.convertBpmnTaskStatus(taskStatus); + } + } + } else { + if (historicActivity.getEndTime() == null) { + progressStatus = RUNNING.getStatus(); + }else { + progressStatus = BpmProcessNodeProgressEnum.FINISHED.getStatus(); + } + + } + return progressStatus; + } + + @Override + public Integer getNotRunActivityProgressStatus(Integer processInstanceStatus) { + if(BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)){ + return SKIP.getStatus(); + }else { + return NOT_START.getStatus(); + } + } + + @Override + public List getNotRunActivityUserList(String processInstanceId, Integer processInstanceStatus, Integer candidateStrategy, String candidateParam) { + if(BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)){ + // 跳过节点。返回空 + return Collections.emptyList(); + }else { + BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); + Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); + List userList = adminUserApi.getUserList(userIds); + return CollectionUtils.convertList(userList, item -> { + User user = BeanUtils.toBean(item, User.class); + user.setProcessed(Boolean.FALSE); + return user; + }); + } + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index b89f39b31..78c8f8fef 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import java.util.Collection; +import java.util.Set; /** * 流程抄送 Service 接口 @@ -42,5 +43,13 @@ public interface BpmProcessInstanceCopyService { */ PageResult getProcessInstanceCopyPage(Long userId, BpmProcessInstanceCopyPageReqVO pageReqVO); + /** + * 通过流程实例和流程活动编号获取抄送人的 Id + * + * @param processInstanceId 流程实例 Id + * @param activityId 流程活动编号 Id + * @return 抄送人 Ids + */ + Set getCopyUserIds(String processInstanceId, String activityId); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index a0d61035e..b30b6c1e3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper; @@ -18,6 +19,7 @@ import org.springframework.validation.annotation.Validated; import java.util.Collection; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -85,4 +87,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy return processInstanceCopyMapper.selectPage(userId, pageReqVO); } + @Override + public Set getCopyUserIds(String processInstanceId, String activityId) { + return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcInstIdAndActId(processInstanceId, activityId), + BpmProcessInstanceCopyDO::getUserId); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 0c2bf41cd..e57260312 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceFormFieldsPermissionReqVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import jakarta.validation.Valid; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; @@ -95,6 +92,14 @@ public interface BpmProcessInstanceService { */ Map getProcessInstanceFormFieldsPermission(@Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO); + /** + * 获取流程实例的进度 + * + * @param id 流程 Id + * @return 流程实例的进度 + */ + BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id); + // ========== Update 写入相关方法 ========== /** @@ -148,4 +153,5 @@ public interface BpmProcessInstanceService { */ void processProcessInstanceCompleted(ProcessInstance instance); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 2d727c3ae..be01e0018 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceFormFieldsPermissionReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ProcessNodeProgress; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); List historicActivityList = activityService.getActivityListByProcessInstanceId( processInstance.getId()); // if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { // 仿钉钉流程设计器 BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List nodeProgresses = new LinkedList<>(); Map activityInstanceMap = CollectionUtils.convertMap(historicActivityList, HistoricActivityInstance::getActivityId); // TODO 回退节点需要处理。 List returnNodePosition = new ArrayList<>(); SimpleModelUtils.traverseNodeToBuildNodeProgress(processInstance, simpleModel, historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition); respVO.setNodeProgressList(nodeProgresses); } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO 待实现 } return respVO; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index fbcc9888e..4a71b63fa 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -93,6 +93,14 @@ public interface BpmTaskService { */ HistoricTaskInstance getHistoricTask(String id); + /** + * 获取历史任务列表 + * + * @param taskIds 任务编号集合 + * @return 历史任务列表 + */ + List getHistoricTasks(Collection taskIds); + /** * 根据条件查询正在进行中的任务 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 12fe8f269..4445abecb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -217,6 +217,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); } + @Override + public List getHistoricTasks(Collection taskIds) { + return historyService.createHistoricTaskInstanceQuery().taskIds(taskIds).includeTaskLocalVariables().list(); + } + @Override public List getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) { Assert.notNull(processInstanceId, "processInstanceId 不能为空"); From 4d15396e36abd22d99792f8155d0aa35d03769e5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 5 Sep 2024 09:15:34 +0800 Subject: [PATCH 076/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E8=8E=B7?= =?UTF-8?q?=E5=BE=97=E5=AE=A1=E6=89=B9=E7=9A=84=E8=BF=9B=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmProcessNodeProgressEnum.java | 1 + .../task/BpmProcessInstanceController.java | 1 - .../BpmProcessInstanceProgressRespVO.java | 13 ++++++ .../task/BpmProcessInstanceCopyMapper.java | 6 ++- .../candidate/BpmTaskCandidateStrategy.java | 43 ++++++++++--------- .../BpmTaskCandidateDeptMemberStrategy.java | 7 +-- .../BpmTaskCandidateRoleStrategy.java | 2 +- .../BpmTaskCandidateStartUserStrategy.java | 8 ++-- .../flowable/core/util/SimpleModelUtils.java | 3 +- .../BpmProcessDefinitionService.java | 5 ++- .../bpm/service/task/BpmActivityService.java | 19 +++++--- .../service/task/BpmActivityServiceImpl.java | 1 + .../task/BpmProcessInstanceCopyService.java | 1 + .../BpmProcessInstanceCopyServiceImpl.java | 2 +- .../task/BpmProcessInstanceService.java | 1 + 15 files changed, 69 insertions(+), 44 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java index 33fa001f1..4a6f32a7e 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java @@ -13,6 +13,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum BpmProcessNodeProgressEnum { + // 0 未开始 NOT_START(0,"未开始"), // 1 ~ 20 进行中 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 195eeabf1..f91bf1f91 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -47,7 +47,6 @@ public class BpmProcessInstanceController { private BpmProcessInstanceService processInstanceService; @Resource private BpmTaskService taskService; - @Resource private BpmProcessDefinitionService processDefinitionService; @Resource diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java index fc1b1672e..3760234cc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java @@ -22,17 +22,24 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") private String id; // Bpmn XML 节点 Id + @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") private String name; + + @Schema(description = "节点展示内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "指定成员: 芋道源码") private String displayText; + @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 + @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") private Integer status; // 参见 BpmProcessNodeProgressEnum 枚举 + @Schema(description = "节点的开始时间") private LocalDateTime startTime; @Schema(description = "节点的结束时间") private LocalDateTime endTime; + @Schema(description = "用户列表") private List userList; @Schema(description = "分支节点") @@ -48,13 +55,19 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Long id; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") private String nickname; + @Schema(description = "用户头像", example = "芋艿") private String avatar; + @Schema(description = "是否已处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean processed; + @Schema(description = "用户任务的处理状态", example = "1") private Integer userTaskStatus; + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java index 3605c6400..daf93747a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java @@ -20,7 +20,9 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX selectListByProcInstIdAndActId(String processInstanceId, String activityId) { - return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId, BpmProcessInstanceCopyDO::getActivityId, activityId); + default List selectListByProcessIstanceIdAndActivityId(String processInstanceId, String activityId) { + return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId, + BpmProcessInstanceCopyDO::getActivityId, activityId); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java index 64e4328f4..937a1a3c5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -29,26 +29,6 @@ public interface BpmTaskCandidateStrategy { */ void validateParam(String param); - /** - * 基于执行任务,获得任务的候选用户们 - * - * @param execution 执行任务 - * @return 用户编号集合 - */ - Set calculateUsers(DelegateExecution execution, String param); - - - /** - * 基于流程实例,获得任务的候选用户们。 用于获取未执行节点的候选用户们 - * - * @param processInstanceId 流程实例 - * @param param 节点的参数 - * @return 用户编号集合 - */ - default Set calculateUsers(String processInstanceId, String param) { - return Collections.emptySet(); - } - /** * 是否一定要输入参数 * @@ -58,4 +38,27 @@ public interface BpmTaskCandidateStrategy { return true; } + /** + * 基于执行任务,获得任务的候选用户们 + * + * @param execution 执行任务 + * @return 用户编号集合 + */ + Set calculateUsers(DelegateExecution execution, String param); + + /** + * 基于流程实例,获得任务的候选用户们 + * + * 目的:用于获取未执行节点的候选用户们 + * + * @param processInstanceId 流程实例编号 + * @param param 节点的参数 + * @return 用户编号集合 + */ + default Set calculateUsers(String processInstanceId, String param) { + return Collections.emptySet(); + } + + // TODO @芋艿:后续可以抽象一个 calculateUsers(String param),默认 calculateUsers 和 calculateUsers 调用它 + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java index 1f18c249d..21788771b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java @@ -41,16 +41,11 @@ public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrat @Override public Set calculateUsers(DelegateExecution execution, String param) { - return calculateUsersByParam(param); + return calculateUsers((String) null, param); } @Override public Set calculateUsers(String processInstanceId, String param) { - return calculateUsersByParam(param); - } - - private Set calculateUsersByParam(String param) { - Set deptIds = StrUtils.splitToLongSet(param); List users = adminUserApi.getUserListByDeptIds(deptIds); return convertSet(users, AdminUserRespDTO::getId); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java index a05610938..dcc1d5c0b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java @@ -45,7 +45,7 @@ public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { return calculateUsersByParam(param); } - private Set calculateUsersByParam(String param) { + private Set calculateUsersByParam(String param) { Set roleIds = StrUtils.splitToLongSet(param); return permissionApi.getUserRoleIdListByRoleIds(roleIds); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java index 33b1b2696..690885586 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -34,13 +34,13 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate public void validateParam(String param) {} @Override - public Set calculateUsers(DelegateExecution execution, String param) { - return getStartUserOfProcessInstance(execution.getProcessInstanceId()); + public boolean isParamRequired() { + return false; } @Override - public boolean isParamRequired() { - return false; + public Set calculateUsers(DelegateExecution execution, String param) { + return getStartUserOfProcessInstance(execution.getProcessInstanceId()); } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 2c794c520..03085166a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -519,6 +519,7 @@ public class SimpleModelUtils { private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); + // TODO @jason:这种枚举,最终不要去掉哈 BpmUserTaskApproveMethodEnum。因为容易不经意重叠 if (approveMethodEnum == null || approveMethodEnum == RANDOM) { return; } @@ -656,7 +657,7 @@ public class SimpleModelUtils { traverseNodeToBuildNodeProgress(processInstance, simpleModel.getChildNode(), historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition); } - + // TODO @芋艿:重点在 review 下 private static void buildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO node, List nodeProgresses, List historicActivityList, Map activityInstanceMap, List returnNodePosition) { BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java index 9994cd084..c949e0a70 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java @@ -48,11 +48,12 @@ public interface BpmProcessDefinitionService { * @param model 流程模型 * @param modelMetaInfo 流程模型元信息 * @param bpmnBytes BPMN XML 字节数组 - * @param simpleBytes simple model json 字节数组 + * @param simpleBytes SIMPLE Model JSON 字节数组 * @param form 表单 * @return 流程编号 */ - String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form); + String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, + byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form); /** * 更新流程定义状态 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java index 3d6b1866b..0934b60d6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java @@ -29,7 +29,9 @@ public interface BpmActivityService { List getHistoricActivityListByExecutionId(String executionId); /** - * 获取活动的用户列表。 例如:抄送人列表。 审批人列表 + * 获取活动的用户列表。 + * + * 例如:抄送人列表、审批人列表 * * @param historicActivity 活动 * @param isMultiInstance 是否多实例 (会签,或签 ) @@ -37,10 +39,11 @@ public interface BpmActivityService { * @return 用户列表 */ List getHistoricActivityUserList(HistoricActivityInstance historicActivity, - Boolean isMultiInstance, List historicActivityList); + Boolean isMultiInstance, + List historicActivityList); /** - * 获取活动的进度状态。 + * 获取活动的进度状态 * * @param historicActivity 活动 * @param isMultiInstance 是否多实例 (会签,或签 ) @@ -48,11 +51,15 @@ public interface BpmActivityService { * @return 活动的进度状态 */ Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity, - Boolean isMultiInstance, List historicActivityList); + Boolean isMultiInstance, + List historicActivityList); + // TODO @jason:可以写下这 2 个方法的注释 Integer getNotRunActivityProgressStatus(Integer processInstanceStatus); - List getNotRunActivityUserList(String processInstanceId, Integer processInstanceStatus - , Integer candidateStrategy, String candidateParam); + List getNotRunActivityUserList(String processInstanceId, + Integer processInstanceStatus, + Integer candidateStrategy, + String candidateParam); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java index d73d5872b..b39d3e4d6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java @@ -72,6 +72,7 @@ public class BpmActivityServiceImpl implements BpmActivityService { return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); } + // TODO @芋艿:重点在 review 下~ @Override public List getHistoricActivityUserList(HistoricActivityInstance historicActivity , Boolean isMultiInstance, List historicActivityList) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index 78c8f8fef..7fd5ff361 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -43,6 +43,7 @@ public interface BpmProcessInstanceCopyService { */ PageResult getProcessInstanceCopyPage(Long userId, BpmProcessInstanceCopyPageReqVO pageReqVO); + // TODO @芋艿:重点在 review 下 /** * 通过流程实例和流程活动编号获取抄送人的 Id * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index b30b6c1e3..211f508a5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -89,7 +89,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @Override public Set getCopyUserIds(String processInstanceId, String activityId) { - return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcInstIdAndActId(processInstanceId, activityId), + return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcessIstanceIdAndActivityId(processInstanceId, activityId), BpmProcessInstanceCopyDO::getUserId); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index e57260312..26fde8888 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -92,6 +92,7 @@ public interface BpmProcessInstanceService { */ Map getProcessInstanceFormFieldsPermission(@Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO); + // TODO @芋艿:重点在 review 下 /** * 获取流程实例的进度 * From d02e1e1d6be194345e61c65a84437f151f808169 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 Sep 2024 11:29:28 +0800 Subject: [PATCH 077/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9A=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BB=BB=E5=8A=A1=E7=9A=84=E8=AE=B0=E5=BD=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/enums/definition/BpmProcessNodeProgressEnum.java | 2 +- .../task/vo/instance/BpmProcessInstanceProgressRespVO.java | 5 +++++ .../bpm/framework/flowable/core/util/SimpleModelUtils.java | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java index 4a6f32a7e..a9483074f 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java @@ -29,7 +29,7 @@ public enum BpmProcessNodeProgressEnum { USER_TASK_REJECT(31, "审批不通过"), // 审批节点 USER_TASK_RETURN(32, "已退回"), // 审批节点 USER_TASK_CANCEL(34, "已取消"), // 审批节点 - // 40 ~ 50 一般节点的接榫状态 + // 40 ~ 50 一般节点的接榫状态 // TODO @jason:接榫 是啥呀? FINISHED(41, "已结束"), // 一般节点的节点的结束状态 SKIP(42, "跳过"); // 未执行,跳过的节点 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java index 3760234cc..90af0213c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java @@ -32,6 +32,7 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 + // TODO @jason:可以复用 BpmTaskStatusEnum 么?非必要不加太多状态枚举哈 @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") private Integer status; // 参见 BpmProcessNodeProgressEnum 枚举 @@ -42,6 +43,8 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "用户列表") private List userList; + + // TODO @jason:如果条件信息,怎么展示哈? @Schema(description = "分支节点") private List branchNodes; // 有且仅有条件、并行、包容节点才会有分支节点 @@ -62,6 +65,8 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "用户头像", example = "芋艿") private String avatar; + // TODO @jason:是不是把 processed 和 userTaskStatus 合并? + @Schema(description = "是否已处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean processed; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 03085166a..158dfd3ee 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -654,10 +654,11 @@ public class SimpleModelUtils { } buildNodeProgress(processInstance, simpleModel, nodeProgresses, historicActivityList, activityInstanceMap, returnNodePosition); // 如果有“子”节点,则递归处理子节点 + // TODO @jason:需要根据条件,是否继续递归。例如说,一共有 3 个 node;第 2 个 node 审批不通过了。那么第 3 个 node 就不用了。(微信讨论下) traverseNodeToBuildNodeProgress(processInstance, simpleModel.getChildNode(), historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition); } - // TODO @芋艿:重点在 review 下 + // TODO @芋艿,@jason:可重构优化的点,SimpleModelUtils 负责提供一个遍历的方法,有个 Function 进行每个节点的处理。目的是,把逻辑拿回到 Service 里面;或者说,减少 Utils 去调用 Service private static void buildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO node, List nodeProgresses, List historicActivityList, Map activityInstanceMap, List returnNodePosition) { BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); @@ -674,6 +675,7 @@ public class SimpleModelUtils { // 2. 设置节点状态 nodeProgress.setStatus(activityService.getNotRunActivityProgressStatus(processInstanceStatus)); // 3. 抄送节点, 审批节点设置用户列表 + // TODO @芋艿:抄送节点,要不要跳过(不展示) if (COPY_NODE.getType().equals(node.getType()) || (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()))) { nodeProgress.setUserList(activityService.getNotRunActivityUserList(processInstance.getId() @@ -707,6 +709,7 @@ public class SimpleModelUtils { } break; } + // TODO @芋艿:抄送节点,要不要跳过(不展示) case COPY_NODE: { // 抄送节点 // 1. 设置节点的状态 nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); From b28d917d566c0882e8b4e0d7183575cd1e25f7dc Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 8 Sep 2024 17:20:28 +0800 Subject: [PATCH 078/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmProcessNodeProgressEnum.java | 2 +- .../bpm/service/task/BpmActivityService.java | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java index a9483074f..e8095e4a5 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java @@ -29,7 +29,7 @@ public enum BpmProcessNodeProgressEnum { USER_TASK_REJECT(31, "审批不通过"), // 审批节点 USER_TASK_RETURN(32, "已退回"), // 审批节点 USER_TASK_CANCEL(34, "已取消"), // 审批节点 - // 40 ~ 50 一般节点的接榫状态 // TODO @jason:接榫 是啥呀? + // 40 ~ 50 节点的通用结束状态 FINISHED(41, "已结束"), // 一般节点的节点的结束状态 SKIP(42, "跳过"); // 未执行,跳过的节点 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java index 0934b60d6..a3c1a228e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import org.flowable.engine.history.HistoricActivityInstance; import java.util.List; @@ -54,9 +55,23 @@ public interface BpmActivityService { Boolean isMultiInstance, List historicActivityList); - // TODO @jason:可以写下这 2 个方法的注释 + /** + * 获取未执行活动的进度状态 + * + * @param processInstanceStatus 流程实例的状态 {@link BpmProcessInstanceStatusEnum} + * @return 活动的进度状态 + */ Integer getNotRunActivityProgressStatus(Integer processInstanceStatus); + /** + * 获取未执行活动的用户列表 + * + * @param processInstanceId 流程实例的编号 + * @param processInstanceStatus 流程实例的状态 {@link BpmProcessInstanceStatusEnum} + * @param candidateStrategy 活动的候选人策略 + * @param candidateParam 活动的候选人参数 + * @return 用户列表 + */ List getNotRunActivityUserList(String processInstanceId, Integer processInstanceStatus, Integer candidateStrategy, From da398dfefc60f0af42b3d0fa853cfbad2339ab38 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 15 Sep 2024 19:40:29 +0800 Subject: [PATCH 079/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=AE=B0=E5=BD=95=E7=AC=AC=E4=BA=8C=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmProcessNodeProgressEnum.java | 68 -------- .../definition/BpmSimpleModelNodeType.java | 33 ++-- .../bpm/enums/task/BpmTaskStatusEnum.java | 6 +- .../BpmProcessInstanceProgressRespVO.java | 47 ++--- .../core/enums/BpmnModelConstants.java | 5 + .../flowable/core/util/SimpleModelUtils.java | 133 ++------------ .../bpm/service/task/BpmActivityService.java | 50 ------ .../service/task/BpmActivityServiceImpl.java | 164 ------------------ .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../task/bo/AlreadyRunApproveNodeRespBO.java | 20 +++ 10 files changed, 89 insertions(+), 439 deletions(-) delete mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java deleted file mode 100644 index e8095e4a5..000000000 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmProcessNodeProgressEnum.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.iocoder.yudao.module.bpm.enums.definition; - -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 流程节点进度的枚举 - * - * @author jason - */ -@Getter -@AllArgsConstructor -public enum BpmProcessNodeProgressEnum { - - // 0 未开始 - NOT_START(0,"未开始"), - // 1 ~ 20 进行中 - RUNNING(1, "进行中"), // 节点的进行 - // 特殊的进行中状态 - USER_TASK_DELEGATE(10, "委派中"), // 审批节点 - USER_TASK_APPROVING(11, "向后加签审批通过中"), //向后加签 审批通过中. - USER_TASK_WAIT(12, "待审批"), // 一般用于先前加签 - - // 30 ~ 50 已经结束 - // 30 ~ 40 审批节点的结束状态 - USER_TASK_APPROVE(30, "审批通过"), // 审批节点 - USER_TASK_REJECT(31, "审批不通过"), // 审批节点 - USER_TASK_RETURN(32, "已退回"), // 审批节点 - USER_TASK_CANCEL(34, "已取消"), // 审批节点 - // 40 ~ 50 节点的通用结束状态 - FINISHED(41, "已结束"), // 一般节点的节点的结束状态 - SKIP(42, "跳过"); // 未执行,跳过的节点 - - private final Integer status; - private final String name; - - public static Integer convertBpmnTaskStatus(Integer taskStatus) { - Integer convertStatus = null; - if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)) { - convertStatus = RUNNING.getStatus(); - } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(taskStatus)) { - convertStatus = USER_TASK_REJECT.getStatus(); - } else if( BpmTaskStatusEnum.APPROVE.getStatus().equals(taskStatus) ) { - convertStatus = USER_TASK_APPROVE.getStatus(); - } else if (BpmTaskStatusEnum.DELEGATE.getStatus().equals(taskStatus)) { - convertStatus = USER_TASK_DELEGATE.getStatus(); - } else if (BpmTaskStatusEnum.APPROVING.getStatus().equals(taskStatus)) { - convertStatus = USER_TASK_APPROVE.getStatus(); - } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(taskStatus)) { - convertStatus = USER_TASK_CANCEL.getStatus(); - } else if (BpmTaskStatusEnum.WAIT.getStatus().equals(taskStatus)) { - convertStatus = USER_TASK_WAIT.getStatus(); - } - return convertStatus; - } - - /** - * 判断用户节点是不是未通过 - * - * @param status 状态 - */ - public static boolean isUserTaskNotApproved(Integer status) { - return ObjectUtils.equalsAny(status, - USER_TASK_REJECT.getStatus(), USER_TASK_RETURN.getStatus(), USER_TASK_CANCEL.getStatus()); - } -} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index e754a9da1..621a7a5da 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -18,26 +18,27 @@ import java.util.Objects; public enum BpmSimpleModelNodeType implements IntArrayValuable { // 0 ~ 1 开始和结束 - START_NODE(0, "开始节点"), - END_NODE(1, "结束节点"), + START_NODE(0, "startEvent", "开始节点"), + END_NODE(1, "endEvent", "结束节点"), // 10 ~ 49 各种节点 - START_USER_NODE(10, "发起人节点"), // 发起人节点。前端的开始节点,Id 固定 - APPROVE_NODE(11, "审批人节点"), - COPY_NODE(12, "抄送人节点"), + START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点。前端的开始节点,Id 固定 + APPROVE_NODE(11, "userTask", "审批人节点"), + COPY_NODE(12, "serviceTask", "抄送人节点"), // 50 ~ 条件分支 - CONDITION_NODE(50, "条件节点"), // 用于构建流转条件的表达式 - CONDITION_BRANCH_NODE(51, "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? - PARALLEL_BRANCH_NODE(52, "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 - INCLUSIVE_BRANCH_NODE(53, "包容分支节点"), + CONDITION_NODE(50, "sequenceFlow", "条件节点"), // 用于构建流转条件的表达式 + CONDITION_BRANCH_NODE(51, " “parallelGateway”", "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? + PARALLEL_BRANCH_NODE(52, "exclusiveGateway", "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 + INCLUSIVE_BRANCH_NODE(53, "inclusiveGateway", "包容分支节点"), // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~ // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。 ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); - + public static final String BPMN_USER_TASK_TYPE ="userTask"; private final Integer type; + private final String bpmnType; private final String name; /** @@ -48,7 +49,17 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { public static boolean isBranchNode(Integer type) { return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) - || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type) ; + || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type); + } + + /** + * 判断是否需要记录的节点 + * + * @param bpmnType bpmn节点类型 + */ + public static boolean isRecordNode(String bpmnType) { + return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType) + || Objects.equals(END_NODE.getBpmnType(), bpmnType); } public static BpmSimpleModelNodeType valueOf(Integer type) { diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index 40a385a58..cddb11bf2 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -12,7 +12,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum BpmTaskStatusEnum { - + NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), REJECT(3, "审批不通过"), @@ -57,5 +57,9 @@ public enum BpmTaskStatusEnum { APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(), RETURN.getStatus(), APPROVING.getStatus()); } + public static boolean isEndStatusButNotApproved(Integer status) { + return ObjectUtils.equalsAny(status, + REJECT.getStatus(), CANCEL.getStatus(), RETURN.getStatus()); + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java index 90af0213c..e98361551 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java @@ -14,41 +14,35 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 - private List nodeProgressList; + @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List approveNodeList; - @Schema(description = "节点进度信息") + @Schema(description = "审批节点信息") @Data - public static class ProcessNodeProgress { + public static class ApproveNodeInfo { @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") - private String id; // Bpmn XML 节点 Id + private String id; @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") private String name; - @Schema(description = "节点展示内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "指定成员: 芋道源码") - private String displayText; - @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 - // TODO @jason:可以复用 BpmTaskStatusEnum 么?非必要不加太多状态枚举哈 @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer status; // 参见 BpmProcessNodeProgressEnum 枚举 + private Integer status; // 参见 BpmTaskStatusEnum 枚举 @Schema(description = "节点的开始时间") private LocalDateTime startTime; @Schema(description = "节点的结束时间") private LocalDateTime endTime; - @Schema(description = "用户列表") - private List userList; + @Schema(description = "审批节点的任务信息") + private List tasks; - // TODO @jason:如果条件信息,怎么展示哈? - @Schema(description = "分支节点") - private List branchNodes; // 有且仅有条件、并行、包容节点才会有分支节点 - - // TODO 用户意见,评论 + @Schema(description = "候选人用户列表") + private List candidateUserList; // 用于未运行任务节点 } @@ -65,14 +59,25 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "用户头像", example = "芋艿") private String avatar; - // TODO @jason:是不是把 processed 和 userTaskStatus 合并? + } + @Schema(description = "审批任务信息") + @Data + public static class ApproveTaskInfo { - @Schema(description = "是否已处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean processed; + @Schema(description = "任务编号", example = "1") + private String id; - @Schema(description = "用户任务的处理状态", example = "1") - private Integer userTaskStatus; + @Schema(description = "任务所属人") + private User ownerUser; + @Schema(description = "任务分配人") + private User assigneeUser; + + @Schema(description = "任务状态", example = "1") + private Integer status; // 参见 BpmTaskStatusEnum 枚举 + + @Schema(description = "审批意见", example = "同意") + private String reason; } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index f9198e43b..53e75c5d9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -109,4 +109,9 @@ public interface BpmnModelConstants { */ String START_EVENT_NODE_NAME = "开始"; + /** + * 发起人节点 Id + */ + String START_USER_NODE_ID = "StartUserNode"; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 158dfd3ee..cffaa61b2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -7,23 +7,23 @@ import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.date.DateUtils; -import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ProcessNodeProgress; -import cn.iocoder.yudao.module.bpm.enums.definition.*; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; -import org.flowable.engine.history.HistoricActivityInstance; -import org.flowable.engine.history.HistoricProcessInstance; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; @@ -218,7 +218,7 @@ public class SimpleModelUtils { * * @param conditionNode 条件节点 */ - private static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) { + public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) { Integer conditionType = MapUtil.getInt(conditionNode.getAttributes(), CONDITION_TYPE_ATTRIBUTE); BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionType); String conditionExpression = null; @@ -293,7 +293,7 @@ public class SimpleModelUtils { traverseNodeToBuildFlowNode(node.getChildNode(), process); } - private static boolean isValidNode(BpmSimpleModelNodeVO node) { + public static boolean isValidNode(BpmSimpleModelNodeVO node) { return node != null && node.getId() != null; } @@ -635,117 +635,4 @@ public class SimpleModelUtils { return endEvent; } - /** - * 遍历简单模型, 构建节点的进度。 TODO 回退节点暂未处理 - * - * @param processInstance 流程实例 - * @param simpleModel 简单模型 - * @param historicActivityList 流程实例的活力列表 - * @param activityInstanceMap 流程实例的活力 Map。 key: activityId - * @param nodeProgresses 节点的进度列表 - * @param returnNodePosition 回退节点的位置。 TODO 处理回退节点,还未处理。还没想好 - */ - public static void traverseNodeToBuildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO simpleModel - , List historicActivityList, Map activityInstanceMap - , List nodeProgresses, List returnNodePosition) { - // 判断是否有效节点 - if (!isValidNode(simpleModel)) { - return; - } - buildNodeProgress(processInstance, simpleModel, nodeProgresses, historicActivityList, activityInstanceMap, returnNodePosition); - // 如果有“子”节点,则递归处理子节点 - // TODO @jason:需要根据条件,是否继续递归。例如说,一共有 3 个 node;第 2 个 node 审批不通过了。那么第 3 个 node 就不用了。(微信讨论下) - traverseNodeToBuildNodeProgress(processInstance, simpleModel.getChildNode(), historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition); - } - - // TODO @芋艿,@jason:可重构优化的点,SimpleModelUtils 负责提供一个遍历的方法,有个 Function 进行每个节点的处理。目的是,把逻辑拿回到 Service 里面;或者说,减少 Utils 去调用 Service - private static void buildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO node, List nodeProgresses, - List historicActivityList, Map activityInstanceMap, List returnNodePosition) { - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); - Assert.notNull(nodeType, "模型节点类型不支持"); - - ProcessNodeProgress nodeProgress = new ProcessNodeProgress(); - nodeProgress.setNodeType(nodeType.getType()); - nodeProgress.setName(node.getName()); - nodeProgress.setDisplayText(node.getShowText()); - BpmActivityService activityService = SpringUtils.getBean(BpmActivityService.class); - if (!activityInstanceMap.containsKey(node.getId())) { // 说明这些节点没有执行过 - // 1. 得到流程状态 - Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); - // 2. 设置节点状态 - nodeProgress.setStatus(activityService.getNotRunActivityProgressStatus(processInstanceStatus)); - // 3. 抄送节点, 审批节点设置用户列表 - // TODO @芋艿:抄送节点,要不要跳过(不展示) - if (COPY_NODE.getType().equals(node.getType()) || - (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()))) { - nodeProgress.setUserList(activityService.getNotRunActivityUserList(processInstance.getId() - , processInstanceStatus, node.getCandidateStrategy(), node.getCandidateParam())); - } - } else { - nodeProgress.setStatus(BpmProcessNodeProgressEnum.FINISHED.getStatus()); // 默认设置成结束状态 - HistoricActivityInstance historicActivity = activityInstanceMap.get(node.getId()); - nodeProgress.setStartTime(DateUtils.of(historicActivity.getStartTime())); - nodeProgress.setEndTime(DateUtils.of(historicActivity.getEndTime())); - nodeProgress.setId(historicActivity.getId()); - switch (nodeType) { - case START_USER_NODE: { // 发起人节点 - nodeProgress.setDisplayText(""); // 发起人节点不需要显示 displayText - // 1. 设置节点的状态 - nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); - // 2. 设置用户信息 - nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, false, historicActivityList)); - break; - } - case APPROVE_NODE: { // 审批节点 - if (USER.getType().equals(node.getApproveType())) { // 人工审批 - // 1. 判断是否多人审批 - boolean isMultiInstance = !RANDOM.getMethod().equals(node.getApproveMethod()); - // 2. 设置节点的状态 - nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, isMultiInstance, historicActivityList)); - // 3. 设置用户信息 - nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, isMultiInstance, historicActivityList)); - } else { - nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); - } - break; - } - // TODO @芋艿:抄送节点,要不要跳过(不展示) - case COPY_NODE: { // 抄送节点 - // 1. 设置节点的状态 - nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList)); - // 2. 设置用户信息 - nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, false, historicActivityList)); - break; - } - - default: { - // TODO 其它节点类型的实现 - } - } - } - // 如果是“分支”节点, - if (BpmSimpleModelNodeType.isBranchNode(node.getType()) - && ArrayUtil.isNotEmpty(node.getConditionNodes())) { - // 网关是否执行了, 执行了。只包含运行的分支。 未执行包含所有的分支 - final boolean executed = activityInstanceMap.containsKey(node.getId()); - LinkedList branchNodeList = new LinkedList<>(); - node.getConditionNodes().forEach(item -> { - // 如果条件节点执行了。 ACT_HI_ACTINST 表会记录 - if (executed) { - if (activityInstanceMap.containsKey(item.getId())) { - List branchReturnNodePosition = new ArrayList<>(); - traverseNodeToBuildNodeProgress(processInstance, item, historicActivityList, activityInstanceMap, branchNodeList, branchReturnNodePosition); - // TODO 处理回退节点 - } - } else { - List branchReturnNodePosition = new ArrayList<>(); - traverseNodeToBuildNodeProgress(processInstance, item, historicActivityList, activityInstanceMap, branchNodeList, branchReturnNodePosition); - // TODO 处理回退节点 - } - }); - nodeProgress.setBranchNodes(branchNodeList); - } - nodeProgresses.add(nodeProgress); - } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java index a3c1a228e..be76c7013 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.bpm.service.task; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO; -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import org.flowable.engine.history.HistoricActivityInstance; import java.util.List; @@ -29,52 +27,4 @@ public interface BpmActivityService { */ List getHistoricActivityListByExecutionId(String executionId); - /** - * 获取活动的用户列表。 - * - * 例如:抄送人列表、审批人列表 - * - * @param historicActivity 活动 - * @param isMultiInstance 是否多实例 (会签,或签 ) - * @param historicActivityList 某个流程实例的所有活动列表 - * @return 用户列表 - */ - List getHistoricActivityUserList(HistoricActivityInstance historicActivity, - Boolean isMultiInstance, - List historicActivityList); - - /** - * 获取活动的进度状态 - * - * @param historicActivity 活动 - * @param isMultiInstance 是否多实例 (会签,或签 ) - * @param historicActivityList 某个流程实例的所有活动列表 - * @return 活动的进度状态 - */ - Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity, - Boolean isMultiInstance, - List historicActivityList); - - /** - * 获取未执行活动的进度状态 - * - * @param processInstanceStatus 流程实例的状态 {@link BpmProcessInstanceStatusEnum} - * @return 活动的进度状态 - */ - Integer getNotRunActivityProgressStatus(Integer processInstanceStatus); - - /** - * 获取未执行活动的用户列表 - * - * @param processInstanceId 流程实例的编号 - * @param processInstanceStatus 流程实例的状态 {@link BpmProcessInstanceStatusEnum} - * @param candidateStrategy 活动的候选人策略 - * @param candidateParam 活动的候选人参数 - * @return 用户列表 - */ - List getNotRunActivityUserList(String processInstanceId, - Integer processInstanceStatus, - Integer candidateStrategy, - String candidateParam); - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java index b39d3e4d6..26da5ad0e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java @@ -1,33 +1,13 @@ package cn.iocoder.yudao.module.bpm.service.task; -import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.NumberUtil; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgressEnum; -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.history.HistoricActivityInstance; -import org.flowable.task.api.history.HistoricTaskInstance; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; - -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgressEnum.*; /** @@ -40,26 +20,8 @@ import static cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgres @Validated public class BpmActivityServiceImpl implements BpmActivityService { - /** - * 抄送节点活动类型 - */ - private static final String COPY_NODE_ACTIVITY_TYPE = "serviceTask"; - /** - * 审批节点活动类型 - */ - private static final String APPROVE_NODE_ACTIVITY_TYPE = "userTask"; - @Resource private HistoryService historyService; - @Resource - @Lazy - private BpmTaskService bpmTaskService; - @Resource - private BpmProcessInstanceCopyService bpmProcessInstanceCopyService; - @Resource - private AdminUserApi adminUserApi; - @Resource - private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Override public List getActivityListByProcessInstanceId(String processInstanceId) { @@ -72,130 +34,4 @@ public class BpmActivityServiceImpl implements BpmActivityService { return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); } - // TODO @芋艿:重点在 review 下~ - @Override - public List getHistoricActivityUserList(HistoricActivityInstance historicActivity - , Boolean isMultiInstance, List historicActivityList) { - Assert.notNull(historicActivity, "historicActivity 不能为 null "); - List returnUserList = Collections.emptyList(); - if (COPY_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) { - Set copyUserIds = bpmProcessInstanceCopyService.getCopyUserIds(historicActivity.getProcessInstanceId(), - historicActivity.getActivityId()); - List userList = adminUserApi.getUserList(copyUserIds); - returnUserList = CollectionUtils.convertList(userList, item -> { - User user = BeanUtils.toBean(item, User.class); - user.setProcessed(Boolean.TRUE); - return user; - }); - } else if (APPROVE_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) { - if (isMultiInstance) { // 多人 (会签 、 或签) // TODO 依次审批可能要特殊处理一下 - // 多个任务列表 - List taskList = CollectionUtils.filterList(historicActivityList, - item -> historicActivity.getActivityId().equals(item.getActivityId())); - List userIds = CollectionUtils.convertList(taskList, item -> NumberUtil.parseLong(item.getAssignee(), null)); - List taskIds = CollectionUtils.convertList(taskList, HistoricActivityInstance::getTaskId); - Map adminUserMap = CollectionUtils.convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); - Map historicTaskInstanceMap = CollectionUtils.convertMap(bpmTaskService.getHistoricTasks(taskIds), HistoricTaskInstance::getId); - returnUserList = CollectionUtils.convertList(taskList, item -> { - AdminUserRespDTO adminUser = adminUserMap.get(NumberUtil.parseLong(item.getAssignee(), null)); - User user = BeanUtils.toBean(adminUser, User.class); - if (user != null) { - HistoricTaskInstance taskInstance = historicTaskInstanceMap.get(item.getTaskId()); - if (taskInstance != null) { - user.setProcessed(taskInstance.getEndTime() != null); - user.setUserTaskStatus(FlowableUtils.getTaskStatus(taskInstance)); - } - } - return user; - }); - } else { - AdminUserRespDTO adminUserResp = adminUserApi.getUser(Long.valueOf(historicActivity.getAssignee())); - if (adminUserResp != null) { - User user = BeanUtils.toBean(adminUserResp, User.class); - // TODO 需要处理加签 - // 查询任务状态 - HistoricTaskInstance historicTask = bpmTaskService.getHistoricTask(historicActivity.getTaskId()); - if (historicTask != null) { - Integer taskStatus = FlowableUtils.getTaskStatus(historicTask); - user.setProcessed(historicTask.getEndTime() != null); - user.setUserTaskStatus(taskStatus); - } - returnUserList = ListUtil.of(user); - } - } - } - return returnUserList; - } - - @Override - public Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity - , Boolean isMultiInstance, List historicActivityList) { - Assert.notNull(historicActivity, "historicActivity 不能为 null "); - Integer progressStatus = null; - if (APPROVE_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) { - if (isMultiInstance) { // 多人 (会签 、 或签) - // 多个任务列表 - List taskList = CollectionUtils.filterList(historicActivityList, - item -> historicActivity.getActivityId().equals(item.getActivityId())); - List taskIds = CollectionUtils.convertList(taskList, HistoricActivityInstance::getTaskId); - Map historicTaskMap = CollectionUtils.convertMap(bpmTaskService.getHistoricTasks(taskIds), HistoricTaskInstance::getId); - for (HistoricActivityInstance activity : taskList) { - if (activity.getEndTime() == null) { - progressStatus = RUNNING.getStatus(); - } else { - HistoricTaskInstance task = historicTaskMap.get(activity.getTaskId()); - if (task != null) { - Integer taskStatus = FlowableUtils.getTaskStatus(task); - progressStatus = BpmProcessNodeProgressEnum.convertBpmnTaskStatus(taskStatus); - } - } - // 运行中或者未通过状态。退出循环 (会签可能需要多人通过) - if (RUNNING.getStatus().equals(progressStatus) || isUserTaskNotApproved(progressStatus)) { - break; - } - } - } else { - HistoricTaskInstance historicTask = bpmTaskService.getHistoricTask(historicActivity.getTaskId()); - if (historicTask != null) { - Integer taskStatus = FlowableUtils.getTaskStatus(historicTask); - progressStatus = BpmProcessNodeProgressEnum.convertBpmnTaskStatus(taskStatus); - } - } - } else { - if (historicActivity.getEndTime() == null) { - progressStatus = RUNNING.getStatus(); - }else { - progressStatus = BpmProcessNodeProgressEnum.FINISHED.getStatus(); - } - - } - return progressStatus; - } - - @Override - public Integer getNotRunActivityProgressStatus(Integer processInstanceStatus) { - if(BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)){ - return SKIP.getStatus(); - }else { - return NOT_START.getStatus(); - } - } - - @Override - public List getNotRunActivityUserList(String processInstanceId, Integer processInstanceStatus, Integer candidateStrategy, String candidateParam) { - if(BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)){ - // 跳过节点。返回空 - return Collections.emptyList(); - }else { - BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); - Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); - List userList = adminUserApi.getUserList(userIds); - return CollectionUtils.convertList(userList, item -> { - User user = BeanUtils.toBean(item, User.class); - user.setProcessed(Boolean.FALSE); - return user; - }); - } - } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index be01e0018..ada5c2d87 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ProcessNodeProgress; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); List historicActivityList = activityService.getActivityListByProcessInstanceId( processInstance.getId()); // if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { // 仿钉钉流程设计器 BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List nodeProgresses = new LinkedList<>(); Map activityInstanceMap = CollectionUtils.convertMap(historicActivityList, HistoricActivityInstance::getActivityId); // TODO 回退节点需要处理。 List returnNodePosition = new ArrayList<>(); SimpleModelUtils.traverseNodeToBuildNodeProgress(processInstance, simpleModel, historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition); respVO.setNodeProgressList(nodeProgresses); } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO 待实现 } return respVO; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1. 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 2. 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 3. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId( processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { // 流程已经结束 respVO.setApproveNodeList(respBO.getApproveNodeList()); } else { // 区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { // 4.1 仿钉钉流程设计器, 构建未运行节点的审批信息 List approveNodeList = respBO.getApproveNodeList(); BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance.getId(), simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); approveNodeList.addAll(notRunApproveNodes); respVO.setApproveNodeList(approveNodeList); } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器, 构建未运行节点的审批信息 respVO.setApproveNodeList(respBO.getApproveNodeList()); } } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstanceId 流程实例 Id * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO simpleModelNode , Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstanceId, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstanceId, node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } // TODO 条件分支如何预测待研究 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 待处理活动 只有 "userTask" 和 "endEvent"。 活动需要处理 List pendingActivityNodes = new ArrayList<>(); // 运行的节点 activityId。 Set runNodeIds = new HashSet<>(); // 1. 遍历所有已运行和运行中的活动。 获取待处理的活动 historicActivityList.forEach(activity -> { runNodeIds.add(activity.getActivityId()); if (BpmSimpleModelNodeType.isRecordNode(activity.getActivityType())) { pendingActivityNodes.add(activity); } }); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = CollectionUtils.convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = CollectionUtils.convertMultiMap( CollectionUtils.filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3. 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setName(activity.getActivityName()) .setId(activity.getId()) .setStartTime(DateUtils.of(activity.getStartTime())) .setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 if (START_USER_NODE_ID.equals(activity.getActivityId())) { nodeProgress.setNodeType(START_USER_NODE.getType()); } else { nodeProgress.setNodeType(APPROVE_NODE.getType()); } HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); ApproveTaskInfo approveTask = convertApproveTaskInfo(task); List approveTasks = CollUtil.newArrayList(approveTask); // 处理加签任务 List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { approveTasks.addAll(CollectionUtils.convertList(addSignTasks, this::convertApproveTaskInfo)); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO() .setApproveNodeList(nodeProgressList) .setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)); approveTask.setReason(FlowableUtils.getTaskReason(task)); if (StrUtil.isNotEmpty(task.getAssignee())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getAssignee())); approveTask.setAssigneeUser(BeanUtils.toBean(adminUserResp, User.class)); } if (StrUtil.isNotEmpty(task.getOwner())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getOwner())); approveTask.setOwnerUser(BeanUtils.toBean(adminUserResp, User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java new file mode 100644 index 000000000..ddffec22c --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.bpm.service.task.bo; + +import lombok.Data; + +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; + +/** + * @author jason + */ +@Data +public class AlreadyRunApproveNodeRespBO { + + private List approveNodeList; + + private Set runNodeIds; + +} From 98e62211c604383a73cb3ec291ec0f9a78a77a3e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 17 Sep 2024 11:06:38 +0800 Subject: [PATCH 080/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9A=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BB=BB=E5=8A=A1=E7=9A=84=E8=AE=B0=E5=BD=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/definition/BpmSimpleModelNodeType.java | 4 +++- .../module/bpm/enums/task/BpmTaskStatusEnum.java | 1 + .../instance/BpmProcessInstanceProgressRespVO.java | 14 ++++++++------ .../flowable/core/enums/BpmnModelConstants.java | 2 +- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../task/bo/AlreadyRunApproveNodeRespBO.java | 10 +++++++++- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 621a7a5da..36ad0e5ee 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -36,7 +36,9 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); - public static final String BPMN_USER_TASK_TYPE ="userTask"; + + public static final String BPMN_USER_TASK_TYPE = "userTask"; + private final Integer type; private final String bpmnType; private final String name; diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index cddb11bf2..c29efbf61 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -12,6 +12,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum BpmTaskStatusEnum { + NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java index e98361551..92bfd42b9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java @@ -15,7 +15,7 @@ public class BpmProcessInstanceProgressRespVO { private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED) - private List approveNodeList; + private List approveNodes; @Schema(description = "审批节点信息") @Data @@ -56,28 +56,30 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") private String nickname; - @Schema(description = "用户头像", example = "芋艿") + @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") private String avatar; } + @Schema(description = "审批任务信息") @Data public static class ApproveTaskInfo { - @Schema(description = "任务编号", example = "1") + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private String id; - @Schema(description = "任务所属人") + @Schema(description = "任务所属人", example = "1024") private User ownerUser; - @Schema(description = "任务分配人") + @Schema(description = "任务分配人", example = "2048") private User assigneeUser; - @Schema(description = "任务状态", example = "1") + @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer status; // 参见 BpmTaskStatusEnum 枚举 @Schema(description = "审批意见", example = "同意") private String reason; + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 53e75c5d9..25772d5f3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -110,7 +110,7 @@ public interface BpmnModelConstants { String START_EVENT_NODE_NAME = "开始"; /** - * 发起人节点 Id + * 发起人节点 ID */ String START_USER_NODE_ID = "StartUserNode"; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index ada5c2d87..c94e34bb8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1. 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 2. 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 3. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId( processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { // 流程已经结束 respVO.setApproveNodeList(respBO.getApproveNodeList()); } else { // 区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { // 4.1 仿钉钉流程设计器, 构建未运行节点的审批信息 List approveNodeList = respBO.getApproveNodeList(); BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance.getId(), simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); approveNodeList.addAll(notRunApproveNodes); respVO.setApproveNodeList(approveNodeList); } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器, 构建未运行节点的审批信息 respVO.setApproveNodeList(respBO.getApproveNodeList()); } } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstanceId 流程实例 Id * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO simpleModelNode , Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstanceId, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstanceId, node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } // TODO 条件分支如何预测待研究 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 待处理活动 只有 "userTask" 和 "endEvent"。 活动需要处理 List pendingActivityNodes = new ArrayList<>(); // 运行的节点 activityId。 Set runNodeIds = new HashSet<>(); // 1. 遍历所有已运行和运行中的活动。 获取待处理的活动 historicActivityList.forEach(activity -> { runNodeIds.add(activity.getActivityId()); if (BpmSimpleModelNodeType.isRecordNode(activity.getActivityType())) { pendingActivityNodes.add(activity); } }); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = CollectionUtils.convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = CollectionUtils.convertMultiMap( CollectionUtils.filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3. 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setName(activity.getActivityName()) .setId(activity.getId()) .setStartTime(DateUtils.of(activity.getStartTime())) .setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 if (START_USER_NODE_ID.equals(activity.getActivityId())) { nodeProgress.setNodeType(START_USER_NODE.getType()); } else { nodeProgress.setNodeType(APPROVE_NODE.getType()); } HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); ApproveTaskInfo approveTask = convertApproveTaskInfo(task); List approveTasks = CollUtil.newArrayList(approveTask); // 处理加签任务 List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { approveTasks.addAll(CollectionUtils.convertList(addSignTasks, this::convertApproveTaskInfo)); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO() .setApproveNodeList(nodeProgressList) .setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)); approveTask.setReason(FlowableUtils.getTaskReason(task)); if (StrUtil.isNotEmpty(task.getAssignee())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getAssignee())); approveTask.setAssigneeUser(BeanUtils.toBean(adminUserResp, User.class)); } if (StrUtil.isNotEmpty(task.getOwner())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getOwner())); approveTask.setOwnerUser(BeanUtils.toBean(adminUserResp, User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance.getId(), simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstanceId 流程实例 Id * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstanceId, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstanceId, node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } // TODO 条件分支如何预测待研究 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 待处理活动:只有 "userTask" 和 "endEvent" 需要处理 // TODO @jason:这种,其实可以使用 CollectionUtils.filterList() 处理;这样的话,相比下面 311 到 318 左右的处理,会读起来更简洁。性能也没啥差别。 List pendingActivityNodes = new ArrayList<>(); // 1.2 运行的节点 activityId // TODO @jason:这种,其实使用 CollectionUtils.convertSet 处理 Set runNodeIds = new HashSet<>(); // 遍历所有已运行和运行中的活动。 获取待处理的活动 historicActivityList.forEach(activity -> { runNodeIds.add(activity.getActivityId()); if (BpmSimpleModelNodeType.isRecordNode(activity.getActivityType())) { pendingActivityNodes.add(activity); } }); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3. 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks // TODO @jason:建议先构建 List,再一次性 convert。减少一些 adminApi 的调用哈 ApproveTaskInfo approveTask = convertApproveTaskInfo(task); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, this::convertApproveTaskInfo)); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); // TODO @jason:assignee 和 owner 建议批量读取 if (StrUtil.isNotEmpty(task.getAssignee())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getAssignee())); approveTask.setAssigneeUser(BeanUtils.toBean(adminUserResp, User.class)); } if (StrUtil.isNotEmpty(task.getOwner())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getOwner())); approveTask.setOwnerUser(BeanUtils.toBean(adminUserResp, User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java index ddffec22c..f9f50effd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java @@ -8,13 +8,21 @@ import java.util.Set; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; /** + * 已经进行中的审批节点 Response BO + * * @author jason */ @Data public class AlreadyRunApproveNodeRespBO { - private List approveNodeList; + /** + * 审批节点信息数组 + */ + private List approveNodes; + /** + * 进行中的节点 ID 数组 + */ private Set runNodeIds; } From bb359c5f403b5346c4bd8a9d6cb1dc5c13ffb5f0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 17 Sep 2024 15:35:05 +0800 Subject: [PATCH 081/102] =?UTF-8?q?=E5=90=88=E5=B9=B6=20master=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=88=B0=20bpm=20=E5=BC=80=E5=8F=91=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/convert/definition/BpmModelConvert.java | 1 - 1 file changed, 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index c78b87c7e..45b544fe1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmPro import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Deployment; From 105c02c10935c39efd0f66efc27bc177d77b33d5 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 18 Sep 2024 23:26:42 +0800 Subject: [PATCH 082/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index c94e34bb8..fecb84729 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance.getId(), simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstanceId 流程实例 Id * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstanceId, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstanceId, node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } // TODO 条件分支如何预测待研究 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 待处理活动:只有 "userTask" 和 "endEvent" 需要处理 // TODO @jason:这种,其实可以使用 CollectionUtils.filterList() 处理;这样的话,相比下面 311 到 318 左右的处理,会读起来更简洁。性能也没啥差别。 List pendingActivityNodes = new ArrayList<>(); // 1.2 运行的节点 activityId // TODO @jason:这种,其实使用 CollectionUtils.convertSet 处理 Set runNodeIds = new HashSet<>(); // 遍历所有已运行和运行中的活动。 获取待处理的活动 historicActivityList.forEach(activity -> { runNodeIds.add(activity.getActivityId()); if (BpmSimpleModelNodeType.isRecordNode(activity.getActivityType())) { pendingActivityNodes.add(activity); } }); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3. 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks // TODO @jason:建议先构建 List,再一次性 convert。减少一些 adminApi 的调用哈 ApproveTaskInfo approveTask = convertApproveTaskInfo(task); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, this::convertApproveTaskInfo)); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); // TODO @jason:assignee 和 owner 建议批量读取 if (StrUtil.isNotEmpty(task.getAssignee())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getAssignee())); approveTask.setAssigneeUser(BeanUtils.toBean(adminUserResp, User.class)); } if (StrUtil.isNotEmpty(task.getOwner())) { AdminUserRespDTO adminUserResp = adminUserApi.getUser(NumberUtil.parseLong(task.getOwner())); approveTask.setOwnerUser(BeanUtils.toBean(adminUserResp, User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance.getId(), simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstanceId 流程实例 Id * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstanceId, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstanceId, node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } // TODO 条件分支如何预测待研究 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = CollectionUtils.filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = CollectionUtils.convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 25d4c4105abd692abdc3a0c82a34d78f38b9b640 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 19 Sep 2024 09:53:00 +0800 Subject: [PATCH 083/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=AE=A1=E6=89=B9=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E9=A2=84=E6=B5=8B=E6=9D=A1=E4=BB=B6=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index fecb84729..095ccc462 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance.getId(), simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstanceId 流程实例 Id * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstanceId, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(String processInstanceId, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstanceId, node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } // TODO 条件分支如何预测待研究 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstanceId, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = CollectionUtils.filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = CollectionUtils.convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.DEFAULT_FLOW_ATTRIBUTE; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 预测条件表达式的值 Boolean eval = evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)); // 是否默认的序列 Boolean defaultFlow = BooleanUtil.isTrue(MapUtil.getBool(conditionNode.getAttributes(), DEFAULT_FLOW_ATTRIBUTE)); // 满足一个条件, 遍历该分支并 if (eval || defaultFlow) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = CollectionUtils.filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = CollectionUtils.convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return BooleanUtil.isBoolean(result.getClass()) ? BooleanUtil.isTrue((Boolean) result) : Boolean.FALSE; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 6cc577eaaab6db0aa6d39a3104075b0823b2f662 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 21 Sep 2024 10:31:43 +0800 Subject: [PATCH 084/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9A=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BB=BB=E5=8A=A1=E7=9A=84=E8=AE=B0=E5=BD=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 095ccc462..5bde9e264 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.DEFAULT_FLOW_ATTRIBUTE; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 预测条件表达式的值 Boolean eval = evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)); // 是否默认的序列 Boolean defaultFlow = BooleanUtil.isTrue(MapUtil.getBool(conditionNode.getAttributes(), DEFAULT_FLOW_ATTRIBUTE)); // 满足一个条件, 遍历该分支并 if (eval || defaultFlow) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = CollectionUtils.filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = CollectionUtils.convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return BooleanUtil.isBoolean(result.getClass()) ? BooleanUtil.isTrue((Boolean) result) : Boolean.FALSE; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.DEFAULT_FLOW_ATTRIBUTE; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if (evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)) // 预测条件表达式的值 || BooleanUtil.isTrue(MapUtil.getBool(conditionNode.getAttributes(), DEFAULT_FLOW_ATTRIBUTE))) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); // TODO @jason:Boolean.TRUE.equals(result) 是不是就可以啦? return BooleanUtil.isBoolean(result.getClass()) ? BooleanUtil.isTrue((Boolean) result) : Boolean.FALSE; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 5a1c3bdef57fae50a664ab2f671dee2fef7e4228 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 21 Sep 2024 10:54:12 +0800 Subject: [PATCH 085/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E6=9D=A1=E4=BB=B6=E8=8A=82?= =?UTF-8?q?=E7=82=B9=20review=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmSimpleModeConditionType.java | 12 +++- .../vo/model/simple/BpmSimpleModelNodeVO.java | 69 +++++++++++++++++-- .../SimpleModelConditionGroups.java | 63 ----------------- .../flowable/core/util/SimpleModelUtils.java | 28 +++----- .../task/BpmProcessInstanceServiceImpl.java | 2 +- 5 files changed, 83 insertions(+), 91 deletions(-) delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java index f6dd04365..234ec7e47 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.bpm.enums.definition; import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * 仿钉钉的流程器设计器条件节点的条件类型 * @@ -11,16 +14,23 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BpmSimpleModeConditionType { +public enum BpmSimpleModeConditionType implements IntArrayValuable { EXPRESSION(1, "条件表达式"), RULE(2, "条件规则"); + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray(); + private final Integer type; + private final String name; public static BpmSimpleModeConditionType valueOf(Integer type) { return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); } + @Override + public int[] array() { + return ARRAYS; + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index e444085b4..502511753 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -41,15 +41,19 @@ public class BpmSimpleModelNodeVO { @Schema(description = "条件节点") private List conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用 - @Schema(description = "节点的属性") - private Map attributes; // TODO @jason:这个字段,目前只有条件表达式使用;是不是搞的更巨像。TODO @芋艿 + @Schema(description = "条件类型", example = "1") + @InEnum(BpmSimpleModeConditionType.class) + private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE - // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到 + @Schema(description = "条件表达式", example = "${day>3}") + private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + + @Schema(description = "是否默认条件", example = "true") + private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE /** - * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 + * 条件组 */ - @JsonIgnore - private String attachNodeId; + private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE @Schema(description = "候选人策略", example = "30") @InEnum(BpmTaskCandidateStrategyEnum.class) @@ -75,6 +79,12 @@ public class BpmSimpleModelNodeVO { @Schema(description = "操作按钮设置", example = "[]") private List buttonsSetting; // 用于审批节点 + // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到 + /** + * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 + */ + @JsonIgnore + private String attachNodeId; /** * 审批节点拒绝处理 */ @@ -160,6 +170,51 @@ public class BpmSimpleModelNodeVO { private Boolean enable; } - // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 + @Schema(description = "条件组") + @Data + @Valid + public static class ConditionGroups { + @Schema(description = "条件组下的条件关系是否为与关系", example = "true") + @NotNull(message = "条件关系不能为空") + private Boolean and; + + @Schema(description = "条件组下的条件", example = "[]") + @NotEmpty(message = "条件不能为空") + private List conditions; + } + + @Schema(description = "条件") + @Data + @Valid + public static class Condition { + + @Schema(description = "条件下的规则关系是否为与关系", example = "true") + @NotNull(message = "规则关系不能为空") + private Boolean and; + + @Schema(description = "条件下的规则", example = "[]") + @NotEmpty(message = "规则不能为空") + private List rules; + } + + @Schema(description = "条件规则") + @Data + @Valid + public static class ConditionRule { + + @Schema(description = "运行符号", example = "==") + @NotEmpty(message = "运行符号不能为空") + private String opCode; + + @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId") + @NotEmpty(message = "运算符左边的值不能为空") + private String leftSide; + + @Schema(description = "运算符右边的值", example = "1") + @NotEmpty(message = "运算符右边的值不能为空") + private String rightSide; + } + + // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java deleted file mode 100644 index d8dffc8df..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel; - -import lombok.Data; - -import java.util.List; - -/** - * 仿钉钉流程设计器条件节点的条件组 Model - * - * @author jason - */ -@Data -public class SimpleModelConditionGroups { - - /** - * 条件组的逻辑关系是否为与的关系 - */ - private Boolean and; - - /** - * 条件组下的条件 - */ - private List conditions; - - @Data - public static class SimpleModelCondition { - - /** - * 条件下面多个规则的逻辑关系是否为与的关系 - */ - private Boolean and; - - - /** - * 条件下的规则 - */ - private List rules; - } - - @Data - public static class ConditionRule { - - /** - * 类型. TODO 暂时未定义, 未想好 - */ - private Integer type; - - /** - * 运行符号. 例如 == < - */ - private String opCode; - - /** - * 运算符左边的值 - */ - private String leftSide; - - /** - * 运算符右边的值 - */ - private String rightSide; - } -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index cffaa61b2..4ff539fe6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -1,13 +1,12 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; -import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; @@ -15,7 +14,6 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; @@ -35,7 +33,6 @@ import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStar import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** @@ -219,17 +216,12 @@ public class SimpleModelUtils { * @param conditionNode 条件节点 */ public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) { - Integer conditionType = MapUtil.getInt(conditionNode.getAttributes(), CONDITION_TYPE_ATTRIBUTE); - BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionType); + BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionNode.getConditionType()); String conditionExpression = null; if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) { - conditionExpression = MapUtil.getStr(conditionNode.getAttributes(), CONDITION_EXPRESSION_ATTRIBUTE); - } - if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { - SimpleModelConditionGroups conditionGroups = BeanUtil.toBean(MapUtil.get(conditionNode.getAttributes(), - CONDITION_GROUPS_ATTRIBUTE, new TypeReference>() { - }), - SimpleModelConditionGroups.class); + conditionExpression = conditionNode.getConditionExpression(); + } else if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { + ConditionGroups conditionGroups = conditionNode.getConditionGroups(); if (conditionGroups != null && CollUtil.isNotEmpty(conditionGroups.getConditions())) { List strConditionGroups = conditionGroups.getConditions().stream().map(item -> { if (CollUtil.isNotEmpty(item.getRules())) { @@ -246,7 +238,6 @@ public class SimpleModelUtils { }).toList(); conditionExpression = String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); } - } // TODO 待增加其它类型 return conditionExpression; @@ -400,8 +391,7 @@ public class SimpleModelUtils { // TODO @jason:setName // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论 - // @芋艿 感觉聚合网关(合并网关)还是从前端传过来好理解一点。 - // 并行聚合网关 + // 并行聚合网关有程序创建。前端不需要传入 ParallelGateway joinParallelGateway = new ParallelGateway(); joinParallelGateway.setId(node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX); return CollUtil.newArrayList(parallelGateway, joinParallelGateway); @@ -436,7 +426,7 @@ public class SimpleModelUtils { exclusiveGateway.setId(node.getId()); // 寻找默认的序列流 BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), - item -> BooleanUtil.isTrue(MapUtil.getBool(item.getAttributes(), DEFAULT_FLOW_ATTRIBUTE))); + item -> BooleanUtil.isTrue(item.getDefaultFlow())); if (defaultSeqFlow != null) { exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); } @@ -453,8 +443,8 @@ public class SimpleModelUtils { if (isFork) { Assert.notEmpty(node.getConditionNodes(), "条件节点不能为空"); // 寻找默认的序列流 - BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), - item -> BooleanUtil.isTrue(MapUtil.getBool(item.getAttributes(), DEFAULT_FLOW_ATTRIBUTE))); + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne( + node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getDefaultFlow())); if (defaultSeqFlow != null) { inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 5bde9e264..ff5317d38 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.DEFAULT_FLOW_ATTRIBUTE; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if (evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)) // 预测条件表达式的值 || BooleanUtil.isTrue(MapUtil.getBool(conditionNode.getAttributes(), DEFAULT_FLOW_ATTRIBUTE))) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); // TODO @jason:Boolean.TRUE.equals(result) 是不是就可以啦? return BooleanUtil.isBoolean(result.getClass()) ? BooleanUtil.isTrue((Boolean) result) : Boolean.FALSE; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if (evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); // TODO @jason:Boolean.TRUE.equals(result) 是不是就可以啦? return BooleanUtil.isBoolean(result.getClass()) ? BooleanUtil.isTrue((Boolean) result) : Boolean.FALSE; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From ac2ae25a8790fbb5a9c5035ffe7c58b2dbfa8dd6 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sat, 21 Sep 2024 11:12:54 +0800 Subject: [PATCH 086/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E5=86=B2=E7=AA=81=E8=A7=A3?= =?UTF-8?q?=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index ff5317d38..d1e1df0d9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if (evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); // TODO @jason:Boolean.TRUE.equals(result) 是不是就可以啦? return BooleanUtil.isBoolean(result.getClass()) ? BooleanUtil.isTrue((Boolean) result) : Boolean.FALSE; } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if (evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 1ae726f3125ba9a96e1792ea90eac6d9e439ba70 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 23 Sep 2024 10:38:59 +0800 Subject: [PATCH 087/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=20=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=B5=81=E7=A8=8B=E6=9C=AA=E5=BC=80?= =?UTF-8?q?=E5=A7=8B=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceStatusEnum.java | 2 +- .../task/BpmProcessInstanceController.java | 8 ++--- .../vo/instance/BpmApprovalDetailReqVO.java | 15 +++++++++ ...spVO.java => BpmApprovalDetailRespVO.java} | 12 +++---- .../candidate/BpmTaskCandidateInvoker.java | 2 +- .../candidate/BpmTaskCandidateStrategy.java | 31 ++++++++++++++----- ...mTaskCandidateDeptLeaderMultiStrategy.java | 3 +- .../BpmTaskCandidateDeptLeaderStrategy.java | 3 +- .../BpmTaskCandidateDeptMemberStrategy.java | 8 +---- .../BpmTaskCandidateGroupStrategy.java | 3 +- .../BpmTaskCandidatePostStrategy.java | 3 +- .../BpmTaskCandidateRoleStrategy.java | 12 +------ ...idateStartUserDeptLeaderMultiStrategy.java | 11 ++++++- ...kCandidateStartUserDeptLeaderStrategy.java | 12 ++++++- ...mTaskCandidateStartUserSelectStrategy.java | 12 +++++++ .../BpmTaskCandidateStartUserStrategy.java | 18 +++++------ .../BpmTaskCandidateUserStrategy.java | 7 +---- .../task/BpmProcessInstanceService.java | 9 ++++-- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../task/bo/AlreadyRunApproveNodeRespBO.java | 6 ++-- 20 files changed, 109 insertions(+), 70 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java rename yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/{BpmProcessInstanceProgressRespVO.java => BpmApprovalDetailRespVO.java} (90%) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java index 12cf9b6dc..86c5b349f 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -15,7 +15,7 @@ import java.util.Arrays; @Getter @AllArgsConstructor public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { - + NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), REJECT(3, "审批不通过"), diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index f91bf1f91..3ab2830fa 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -165,12 +165,12 @@ public class BpmProcessInstanceController { return success(processInstanceService.getProcessInstanceFormFieldsPermission(reqVO)); } - @GetMapping("/get-progress") - @Operation(summary = "获得流程实例的进度") + @GetMapping("/get-approval-detail") + @Operation(summary = "获得审批详情") @Parameter(name = "id", description = "流程实例的编号", required = true) @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") - public CommonResult getProcessInstanceProgress(@RequestParam("id") String id) { - return success(processInstanceService.getProcessInstanceProgress(id)); + public CommonResult getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { + return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java new file mode 100644 index 000000000..981adf475 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 审批详情 Request VO") +@Data +public class BpmApprovalDetailReqVO { + + @Schema(description = "流程定义的编号", example = "1024") + private String processDefinitionId; + + @Schema(description = "流程实例的编号", example = "1024") + private String processInstanceId; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java similarity index 90% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java index 92bfd42b9..cfe633766 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceProgressRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -7,19 +7,19 @@ import java.time.LocalDateTime; import java.util.List; -@Schema(description = "管理后台 - 流程实例的进度 Response VO") +@Schema(description = "管理后台 - 审批详情 Response VO") @Data -public class BpmProcessInstanceProgressRespVO { +public class BpmApprovalDetailRespVO { @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED) - private List approveNodes; + private List approveNodes; @Schema(description = "审批节点信息") @Data - public static class ApproveNodeInfo { + public static class ApprovalNodeInfo { @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") private String id; @@ -39,7 +39,7 @@ public class BpmProcessInstanceProgressRespVO { private LocalDateTime endTime; @Schema(description = "审批节点的任务信息") - private List tasks; + private List tasks; @Schema(description = "候选人用户列表") private List candidateUserList; // 用于未运行任务节点 @@ -63,7 +63,7 @@ public class BpmProcessInstanceProgressRespVO { @Schema(description = "审批任务信息") @Data - public static class ApproveTaskInfo { + public static class ApprovalTaskInfo { @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private String id; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index 8b9f98ea5..9598f9131 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -110,7 +110,7 @@ public class BpmTaskCandidateInvoker { // 1.3 移除发起人的用户 removeStartUserIfSkip(execution, userIds); - // 2. 移除被禁用的用户 + // 2. 移除被禁用的用户 TODO @芋艿 移除禁用的用户是否应该放在 1.1 之后 removeDisableUsers(userIds); return userIds; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java index 937a1a3c5..c5043d0a9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -2,13 +2,14 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; import java.util.Collections; import java.util.Set; /** * BPM 任务的候选人的策略接口 - * + *

* 例如说:分配审批人 * * @author 芋道源码 @@ -38,27 +39,43 @@ public interface BpmTaskCandidateStrategy { return true; } + /** + * 基于候选人参数,获得任务的候选用户们 + * + * @param param 执行任务 + * @return 用户编号集合 + */ + default Set calculateUsers(String param) { + return Collections.emptySet(); + } + /** * 基于执行任务,获得任务的候选用户们 * * @param execution 执行任务 * @return 用户编号集合 */ - Set calculateUsers(DelegateExecution execution, String param); + default Set calculateUsers(DelegateExecution execution, String param) { + return calculateUsers(param); + } + /** * 基于流程实例,获得任务的候选用户们 - * + *

* 目的:用于获取未执行节点的候选用户们 * - * @param processInstanceId 流程实例编号 - * @param param 节点的参数 + * @param startUserId 流程发起人编号 + * @param processInstance 流程实例编号 + * @param activityId 活动 Id (对应 Bpmn XML id) + * @param param 节点的参数 * @return 用户编号集合 */ - default Set calculateUsers(String processInstanceId, String param) { - return Collections.emptySet(); + default Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { + return calculateUsers(param); } // TODO @芋艿:后续可以抽象一个 calculateUsers(String param),默认 calculateUsers 和 calculateUsers 调用它 + // TODO @芋艿 加了, review 一下 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java index ce4ec5225..175b15cf9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.dept.DeptApi; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.Set; @@ -37,7 +36,7 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs } @Override - public Set calculateUsers(DelegateExecution execution, String param) { + public Set calculateUsers(String param) { String[] params = param.split("\\|"); return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1])); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java index 485552f91..a8ab6c993 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.List; @@ -37,7 +36,7 @@ public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrat } @Override - public Set calculateUsers(DelegateExecution execution, String param) { + public Set calculateUsers(String param) { Set deptIds = StrUtils.splitToLongSet(param); List depts = deptApi.getDeptList(deptIds); return convertSet(depts, DeptRespDTO::getLeaderUserId); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java index 21788771b..73a680dec 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.List; @@ -40,12 +39,7 @@ public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrat } @Override - public Set calculateUsers(DelegateExecution execution, String param) { - return calculateUsers((String) null, param); - } - - @Override - public Set calculateUsers(String processInstanceId, String param) { + public Set calculateUsers(String param) { Set deptIds = StrUtils.splitToLongSet(param); List users = adminUserApi.getUserListByDeptIds(deptIds); return convertSet(users, AdminUserRespDTO::getId); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java index bc161886b..6fb1def39 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.Collection; @@ -38,7 +37,7 @@ public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { } @Override - public Set calculateUsers(DelegateExecution execution, String param) { + public Set calculateUsers(String param) { Set groupIds = StrUtils.splitToLongSet(param); List groups = userGroupService.getUserGroupList(groupIds); return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java index 3f2ae58f1..d213ff529 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.system.api.dept.PostApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.List; @@ -40,7 +39,7 @@ public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { } @Override - public Set calculateUsers(DelegateExecution execution, String param) { + public Set calculateUsers(String param) { Set postIds = StrUtils.splitToLongSet(param); List users = adminUserApi.getUserListByPostIds(postIds); return convertSet(users, AdminUserRespDTO::getId); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java index dcc1d5c0b..d4dd50490 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat import cn.iocoder.yudao.module.system.api.permission.PermissionApi; import cn.iocoder.yudao.module.system.api.permission.RoleApi; import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.Set; @@ -36,16 +35,7 @@ public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { } @Override - public Set calculateUsers(DelegateExecution execution, String param) { - return calculateUsersByParam(param); - } - - @Override - public Set calculateUsers(String processInstanceId, String param) { - return calculateUsersByParam(param); - } - - private Set calculateUsersByParam(String param) { + public Set calculateUsers(String param) { Set roleIds = StrUtils.splitToLongSet(param); return permissionApi.getUserRoleIdListByRoleIds(roleIds); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java index d901b34fc..db52c8ba5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java @@ -58,7 +58,16 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan // 获取发起人的 multi 部门负责人 DeptRespDTO dept = getStartUserDept(startUserId); if (dept == null) { - return new HashSet<>(); + return new HashSet<>(); + } + return getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 + } + + @Override + public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { + DeptRespDTO dept = getStartUserDept(startUserId); + if (dept == null) { + return new HashSet<>(); } return getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java index 1d8a6feff..68ea8adc8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java @@ -56,11 +56,21 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); // 获取发起人的部门负责人 + return getStartUserDeptLeader(startUserId, param); + } + + @Override + public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { + // 获取发起人的部门负责人 + return getStartUserDeptLeader(startUserId, param); + } + + private Set getStartUserDeptLeader(Long startUserId, String param) { DeptRespDTO dept = getStartUserDept(startUserId); if (dept == null) { return new HashSet<>(); } - Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 + Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java index ef31d8885..3e2a1bcdc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java @@ -49,6 +49,18 @@ public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidate return new LinkedHashSet<>(assignees); } + @Override + public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { + if (processInstance == null) { + return Collections.emptySet(); + } + Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); + Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId()); + // 获得审批人 + List assignees = startUserSelectAssignees.get(activityId); + return new LinkedHashSet<>(assignees); + } + @Override public boolean isParamRequired() { return false; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java index 690885586..1f1c79df3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import jakarta.annotation.Resource; import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -13,7 +14,7 @@ import java.util.Set; /** * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类 - * + *

* 适合场景:用于需要发起人信息复核等场景 * * @author jason @@ -31,7 +32,8 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate } @Override - public void validateParam(String param) {} + public void validateParam(String param) { + } @Override public boolean isParamRequired() { @@ -40,17 +42,13 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate @Override public Set calculateUsers(DelegateExecution execution, String param) { - return getStartUserOfProcessInstance(execution.getProcessInstanceId()); + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); } @Override - public Set calculateUsers(String processInstanceId, String param) { - return getStartUserOfProcessInstance(processInstanceId); - } - - private Set getStartUserOfProcessInstance(String processInstanceId) { - String startUserId = processInstanceService.getProcessInstance(processInstanceId).getStartUserId(); - return SetUtils.asSet(Long.valueOf(startUserId)); + public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { + return SetUtils.asSet(startUserId); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java index 7a665d7c6..6f75db193 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java @@ -32,12 +32,7 @@ public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { } @Override - public Set calculateUsers(DelegateExecution execution, String param) { - return StrUtils.splitToLongSet(param); - } - - @Override - public Set calculateUsers(String processInstanceId, String param) { + public Set calculateUsers(String param) { return StrUtils.splitToLongSet(param); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 26fde8888..ed3b66c1f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -94,12 +94,15 @@ public interface BpmProcessInstanceService { // TODO @芋艿:重点在 review 下 /** - * 获取流程实例的进度 + * 获取审批详情。 + *

+ * 可以是准备发起的流程, 进行中的流程, 已经结束的流程 * - * @param id 流程 Id + * @param loginUserId 登录人的用户编号 + * @param reqVO 请求信息 * @return 流程实例的进度 */ - BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id); + BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO); // ========== Update 写入相关方法 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index d1e1df0d9..933183f22 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmProcessInstanceProgressRespVO getProcessInstanceProgress(String id) { // 1.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); BpmProcessInstanceProgressRespVO respVO = new BpmProcessInstanceProgressRespVO() .setStatus(processInstanceStatus); // 2. 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); respVO.setApproveNodes(respBO.getApproveNodes()); // 3. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return respVO; } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); ProcessInstance runProcessInstance = getProcessInstance(processInstance.getId()); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(runProcessInstance, simpleModel, respBO.getRunNodeIds(), notRunApproveNodes); respVO.getApproveNodes().addAll(notRunApproveNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); nodeProgress.setCandidateUserList(getNotRunTaskCandidateUserList(processInstance.getId(), node.getCandidateStrategy(), node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if (evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode)) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApproveNodeInfo nodeProgress = new ApproveNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApproveNodeInfo nodeProgress = new ApproveNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApproveTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApproveTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApproveTaskInfo approveTask = BeanUtils.toBean(task, ApproveTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(String processInstanceId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); // 正在运行的流程实例 ProcessInstance runProcessInstance = null; // 已经运行的节点 Ids (BPMN XML 节点 Id) Set runNodeIds = new HashSet<>(); Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, runProcessInstance, simpleModel, runNodeIds, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } nodeProgress.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java index f9f50effd..561da0601 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java @@ -5,7 +5,7 @@ import lombok.Data; import java.util.List; import java.util.Set; -import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo; +import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; /** * 已经进行中的审批节点 Response BO @@ -18,10 +18,10 @@ public class AlreadyRunApproveNodeRespBO { /** * 审批节点信息数组 */ - private List approveNodes; + private List approveNodes; /** - * 进行中的节点 ID 数组 + * 已运行的节点 ID 数组 */ private Set runNodeIds; From b6c78ad04fa042dd77da3502a4b6580f09e18002 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 25 Sep 2024 22:27:32 +0800 Subject: [PATCH 088/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1=20-=20=E8=8E=B7=E5=8F=96=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E5=AD=97=E6=AE=B5=E6=9D=83=E9=99=90=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceController.java | 8 ++-- .../vo/instance/BpmApprovalDetailReqVO.java | 10 +++++ .../BpmFormFieldsPermissionReqVO.java | 40 +++++++++++++++++++ ...cessInstanceFormFieldsPermissionReqVO.java | 23 ----------- .../BpmTaskCandidateUserStrategy.java | 1 - .../task/BpmProcessInstanceService.java | 6 +-- .../task/BpmProcessInstanceServiceImpl.java | 2 +- 7 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 3ab2830fa..a96c27147 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -158,11 +158,11 @@ public class BpmProcessInstanceController { } @GetMapping("/get-form-fields-permission") - @Operation(summary = "获得流程实例表单字段权限") + @Operation(summary = "获得表单字段权限") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") - public CommonResult> getProcessInstanceFormFieldsPermission( - @Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { - return success(processInstanceService.getProcessInstanceFormFieldsPermission(reqVO)); + public CommonResult> getFormFieldsPermission( + @Valid BpmFormFieldsPermissionReqVO reqVO) { + return success(processInstanceService.getFormFieldsPermission(reqVO)); } @GetMapping("/get-approval-detail") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java index 981adf475..43bf8abf8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; import lombok.Data; @Schema(description = "管理后台 - 审批详情 Request VO") @@ -12,4 +15,11 @@ public class BpmApprovalDetailReqVO { @Schema(description = "流程实例的编号", example = "1024") private String processInstanceId; + + @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") + @JsonIgnore + public boolean isValidProcessParam() { + return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java new file mode 100644 index 000000000..1b5ea33b5 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import lombok.Data; + +/** + * @author jason + */ +@Schema(description = "管理后台 - 表单字段权限 Request VO") +@Data +public class BpmFormFieldsPermissionReqVO { + + @Schema(description = "流程定义的编号", example = "1024") + private String processDefinitionId; + + @Schema(description = "流程实例的编号", example = "1024") + private String processInstanceId; + + @Schema(description = "流程活动编号", example = "StartUserNode") + private String activityId; // 对应 BPMN XML 节点 Id + + @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") + private String taskId; // UserTask 对应的Id + + @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") + @JsonIgnore + public boolean isValidProcessParam() { + return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId); + } + + @AssertTrue(message = "流程活动编号和流程任务编号编号不能同时为空") + @JsonIgnore + public boolean isValidActivityParam() { + return StrUtil.isNotEmpty(activityId) || StrUtil.isNotEmpty(taskId); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java deleted file mode 100644 index 069e6ecae..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceFormFieldsPermissionReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -/** - * @author jason - */ -@Schema(description = "管理后台 - 流程实例表单字段权限 Request VO") -@Data -public class BpmProcessInstanceFormFieldsPermissionReqVO { - - @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotEmpty(message = "流程实例的编号不能为空") - private String id; - - @Schema(description = "流程活动编号", example = "StartUserNode") - private String activityId; // 对应 BPMN XML 节点 Id - - @Schema(description = "流程任务的编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") - private String taskId; // UserTask 对应的Id -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java index 6f75db193..8098ebe2e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.Set; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index ed3b66c1f..b59146f01 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -85,12 +85,12 @@ public interface BpmProcessInstanceService { @Valid BpmProcessInstancePageReqVO pageReqVO); /** - * 获得流程实例表单字段权限 + * 获得表单字段权限 * * @param reqVO 请求消息 * @return 表单字段权限 */ - Map getProcessInstanceFormFieldsPermission(@Valid BpmProcessInstanceFormFieldsPermissionReqVO reqVO); + Map getFormFieldsPermission(@Valid BpmFormFieldsPermissionReqVO reqVO); // TODO @芋艿:重点在 review 下 /** @@ -102,7 +102,7 @@ public interface BpmProcessInstanceService { * @param reqVO 请求信息 * @return 流程实例的进度 */ - BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO); + BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); // ========== Update 写入相关方法 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 933183f22..efd6c9080 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getProcessInstanceFormFieldsPermission(BpmProcessInstanceFormFieldsPermissionReqVO reqVO) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getId()); // 1.1 查询流程实例. 不存在返回 null if (processInstance == null) { return null; } // 1.2 通过流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); // 正在运行的流程实例 ProcessInstance runProcessInstance = null; // 已经运行的节点 Ids (BPMN XML 节点 Id) Set runNodeIds = new HashSet<>(); Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, runProcessInstance, simpleModel, runNodeIds, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } nodeProgress.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); // 正在运行的流程实例 ProcessInstance runProcessInstance = null; // 已经运行的节点 Ids (BPMN XML 节点 Id) Set runNodeIds = new HashSet<>(); Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, runProcessInstance, simpleModel, runNodeIds, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } nodeProgress.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 31df372ab5fe020ab034e6a453c69719758cd34c Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 26 Sep 2024 13:36:28 +0800 Subject: [PATCH 089/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1=20-=20=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=AE=B0=E5=BD=95=E4=BF=AE=E6=94=B9=E5=88=97=E8=A1=A8?= =?UTF-8?q?=EF=BC=8C=E5=90=88=E5=B9=B6=E6=AD=A3=E5=9C=A8=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=9A=84=E4=BC=9A=E7=AD=BE=E5=92=8C=E6=88=96=E7=AD=BE=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/flowable/core/util/SimpleModelUtils.java | 9 ++++++--- .../service/task/BpmProcessInstanceServiceImpl.java | 2 +- .../service/task/bo/AlreadyRunApproveNodeRespBO.java | 10 +++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 4ff539fe6..f9e74e981 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -26,8 +26,7 @@ import java.util.Objects; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.RANDOM; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.RATIO; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; @@ -288,6 +287,10 @@ public class SimpleModelUtils { return node != null && node.getId() != null; } + public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { + return APPROVE_NODE.getType().equals(node.getType()) && SEQUENTIAL.getMethod().equals(node.getApproveMethod()); + } + private static List buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) { List list = new ArrayList<>(); switch (nodeType) { @@ -523,7 +526,7 @@ public class SimpleModelUtils { multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(false); userTask.setLoopCharacteristics(multiInstanceCharacteristics); - } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) { + } else if (approveMethodEnum == SEQUENTIAL) { multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index efd6c9080..f8dda1f4c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.APPROVE; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); // 正在运行的流程实例 ProcessInstance runProcessInstance = null; // 已经运行的节点 Ids (BPMN XML 节点 Id) Set runNodeIds = new HashSet<>(); Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, runProcessInstance, simpleModel, runNodeIds, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } nodeProgress.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(nodeProgress); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, approveNodeList); } }); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行和运行中的任务 List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 转换为节点审批信息 List nodeProgressList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType nodeProgress.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); nodeProgress.setStatus(FlowableUtils.getTaskStatus(task)); // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } nodeProgress.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { nodeProgress.setNodeType(APPROVE_NODE.getType()); nodeProgress.setStatus(APPROVE.getStatus()); } return nodeProgress; }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(nodeProgressList).setRunNodeIds(runNodeIds); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批,并且正在运行的审批信息包含该节点。需要加上其它候选人信息 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); approvalNodeInfo.setCandidateUserList(getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java index 561da0601..cc8384be5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.task.bo; import lombok.Data; import java.util.List; +import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; @@ -21,8 +22,15 @@ public class AlreadyRunApproveNodeRespBO { private List approveNodes; /** - * 已运行的节点 ID 数组 + * 已运行的节点 ID 数组 (对应 Bpmn XML 节点 id) */ private Set runNodeIds; + /** + * 正在运行的节点的审批信息 ( key: activityId. value: 审批信息 ) + *

+ * 用于依次审批。 需要加上候选人信息 + */ + private Map runningApprovalNodes; + } From 7f6d214ea9ec539690546d7b5c60a77b716b04e8 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 26 Sep 2024 17:53:22 +0800 Subject: [PATCH 090/102] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1=20-=20=E8=8E=B7=E5=8F=96=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=AE=B0=E5=BD=95=E4=BF=AE=E6=94=B9,=20=E4=BE=9D?= =?UTF-8?q?=E6=AC=A1=E5=AE=A1=E6=89=B9=E8=8A=82=E7=82=B9=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9C=AA=E5=AE=A1=E6=89=B9=E4=BA=BA=E5=80=99=E9=80=89=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/candidate/strategy/BpmTaskCandidateUserStrategy.java | 4 +++- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java index 8098ebe2e..17449aafa 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; +import cn.hutool.core.text.StrPool; import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; +import java.util.LinkedHashSet; import java.util.Set; /** @@ -32,7 +34,7 @@ public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { @Override public Set calculateUsers(String param) { - return StrUtils.splitToLongSet(param); + return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index f8dda1f4c..e82231296 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批,并且正在运行的审批信息包含该节点。需要加上其它候选人信息 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); approvalNodeInfo.setCandidateUserList(getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); List userList = adminUserApi.getUserList(userIds); return CollectionUtils.convertList(userList, item -> BeanUtils.toBean(item, User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = convertMap(adminUserApi.getUserList(userIds),AdminUserRespDTO::getId); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> { orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class)); }); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From d42c63e8fd61900ab757aa8ed6974bf2fc2dddf0 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Thu, 26 Sep 2024 23:45:04 +0800 Subject: [PATCH 091/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=91=E4=BB=BB=E5=8A=A1=E7=9A=84=E5=80=99=E9=80=89?= =?UTF-8?q?=E4=BA=BA=E7=9A=84=E7=AD=96=E7=95=A5,=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=A2=AB=E7=A6=81=E7=94=A8=E7=9A=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../candidate/BpmTaskCandidateInvoker.java | 2 +- .../candidate/BpmTaskCandidateStrategy.java | 16 +++++++- ...skCandidateAbstractDeptLeaderStrategy.java | 6 ++- .../BpmTaskCandidateAbstractStrategy.java | 37 +++++++++++++++++++ .../BpmTaskCandidateAssignEmptyStrategy.java | 12 +++--- ...mTaskCandidateDeptLeaderMultiStrategy.java | 5 ++- .../BpmTaskCandidateDeptLeaderStrategy.java | 12 ++++-- .../BpmTaskCandidateDeptMemberStrategy.java | 13 ++++--- .../BpmTaskCandidateExpressionStrategy.java | 11 +++++- .../BpmTaskCandidateGroupStrategy.java | 12 ++++-- .../BpmTaskCandidatePostStrategy.java | 13 ++++--- .../BpmTaskCandidateRoleStrategy.java | 7 +++- ...idateStartUserDeptLeaderMultiStrategy.java | 15 ++++---- ...kCandidateStartUserDeptLeaderStrategy.java | 15 ++++---- ...mTaskCandidateStartUserSelectStrategy.java | 16 ++++++-- .../BpmTaskCandidateStartUserStrategy.java | 16 ++++++-- .../BpmTaskCandidateUserStrategy.java | 8 ++-- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../BpmTaskCandidateInvokerTest.java | 16 ++++++-- 19 files changed, 168 insertions(+), 66 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index 9598f9131..cdd7deb4b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -111,7 +111,7 @@ public class BpmTaskCandidateInvoker { removeStartUserIfSkip(execution, userIds); // 2. 移除被禁用的用户 TODO @芋艿 移除禁用的用户是否应该放在 1.1 之后 - removeDisableUsers(userIds); + // removeDisableUsers(userIds); @芋艿 把这个移到了 BpmTaskCandidateStrategy 下面。 看一下是否可以 return userIds; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java index c5043d0a9..f78716ace 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -56,7 +56,9 @@ public interface BpmTaskCandidateStrategy { * @return 用户编号集合 */ default Set calculateUsers(DelegateExecution execution, String param) { - return calculateUsers(param); + Set users = calculateUsers(param); + removeDisableUsers(users); + return users; } @@ -72,9 +74,19 @@ public interface BpmTaskCandidateStrategy { * @return 用户编号集合 */ default Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { - return calculateUsers(param); + Set users = calculateUsers(param); + removeDisableUsers(users); + return users; } + + /** + * 移除被禁用的用户 + * + * @param users 用户 Ids + */ + void removeDisableUsers(Set users); + // TODO @芋艿:后续可以抽象一个 calculateUsers(String param),默认 calculateUsers 和 calculateUsers 调用它 // TODO @芋艿 加了, review 一下 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java index 7a6e7a9e1..548f448d5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import java.util.*; @@ -13,11 +14,12 @@ import java.util.*; * * @author jason */ -public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy implements BpmTaskCandidateStrategy { +public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { protected DeptApi deptApi; - public BpmTaskCandidateAbstractDeptLeaderStrategy(DeptApi deptApi) { + public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(adminUserApi); this.deptApi = deptApi; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java new file mode 100644 index 000000000..8ff2bdaab --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; + +import java.util.Map; +import java.util.Set; + +/** + * {@link BpmTaskCandidateStrategy} 抽象类 + * + * @author jason + */ +public abstract class BpmTaskCandidateAbstractStrategy implements BpmTaskCandidateStrategy { + + protected AdminUserApi adminUserApi; + + public BpmTaskCandidateAbstractStrategy(AdminUserApi adminUserApi) { + this.adminUserApi = adminUserApi; + } + + @Override + public void removeDisableUsers(Set users) { + if (CollUtil.isEmpty(users)) { + return; + } + Map userMap = adminUserApi.getUserMap(users); + users.removeIf(id -> { + AdminUserRespDTO user = userMap.get(id); + return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); + }); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java index 0f2fac678..f09c82ec0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import jakarta.annotation.Resource; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; @@ -19,10 +18,11 @@ import java.util.Set; * @author kyle */ @Component -public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy { - @Resource - private AdminUserApi adminUserApi; + public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) { + super(adminUserApi); + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { @@ -38,7 +38,9 @@ public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStra // 情况一:指定人员审批 Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement()); if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { - return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); + HashSet users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); + removeDisableUsers(users); + return users; } // 情况二:流程管理员 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java index 175b15cf9..450dc33ba 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import org.springframework.stereotype.Component; import java.util.Set; @@ -17,8 +18,8 @@ import java.util.Set; @Component public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { - public BpmTaskCandidateDeptLeaderMultiStrategy(DeptApi deptApi) { - super(deptApi); + public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(adminUserApi, deptApi); } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java index a8ab6c993..bc049d01e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import jakarta.annotation.Resource; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import org.springframework.stereotype.Component; import java.util.List; @@ -19,10 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. * @author kyle */ @Component -public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { - @Resource - private DeptApi deptApi; + private final DeptApi deptApi; + + public BpmTaskCandidateDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(adminUserApi); + this.deptApi = deptApi; + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java index 73a680dec..d4a5c7e05 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import jakarta.annotation.Resource; import org.springframework.stereotype.Component; import java.util.List; @@ -20,12 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. * @author kyle */ @Component -public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstractStrategy { - @Resource - private DeptApi deptApi; - @Resource - private AdminUserApi adminUserApi; + private final DeptApi deptApi; + + public BpmTaskCandidateDeptMemberStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(adminUserApi); + this.deptApi = deptApi; + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java index e0f9dabe5..1e48cdf94 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java @@ -4,6 +4,7 @@ import cn.hutool.core.convert.Convert; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; @@ -15,7 +16,11 @@ import java.util.Set; * @author 芋道源码 */ @Component -public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstractStrategy { + + public BpmTaskCandidateExpressionStrategy(AdminUserApi adminUserApi) { + super(adminUserApi); + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { @@ -30,7 +35,9 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat @Override public Set calculateUsers(DelegateExecution execution, String param) { Object result = FlowableUtils.getExpressionValue(execution, param); - return Convert.toSet(Long.class, result); + Set users = Convert.toSet(Long.class, result); + removeDisableUsers(users); + return users; } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java index 6fb1def39..9a239f7bb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; -import jakarta.annotation.Resource; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import org.springframework.stereotype.Component; import java.util.Collection; @@ -20,10 +20,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. * @author kyle */ @Component -public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrategy { - @Resource - private BpmUserGroupService userGroupService; + private final BpmUserGroupService userGroupService; + + public BpmTaskCandidateGroupStrategy(AdminUserApi adminUserApi, BpmUserGroupService userGroupService) { + super(adminUserApi); + this.userGroupService = userGroupService; + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java index d213ff529..18dd4a493 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat import cn.iocoder.yudao.module.system.api.dept.PostApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import jakarta.annotation.Resource; import org.springframework.stereotype.Component; import java.util.List; @@ -20,12 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. * @author kyle */ @Component -public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidatePostStrategy extends BpmTaskCandidateAbstractStrategy { - @Resource - private PostApi postApi; - @Resource - private AdminUserApi adminUserApi; + private final PostApi postApi; + + public BpmTaskCandidatePostStrategy(AdminUserApi adminUserApi, PostApi postApi) { + super(adminUserApi); + this.postApi = postApi; + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java index d4dd50490..0693f036f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.permission.PermissionApi; import cn.iocoder.yudao.module.system.api.permission.RoleApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; @@ -16,13 +17,17 @@ import java.util.Set; * @author kyle */ @Component -public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy { @Resource private RoleApi roleApi; @Resource private PermissionApi permissionApi; + public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) { + super(adminUserApi); + } + @Override public BpmTaskCandidateStrategyEnum getStrategy() { return BpmTaskCandidateStrategyEnum.ROLE; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java index db52c8ba5..c751dd5a0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java @@ -32,11 +32,8 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan @Lazy private BpmProcessInstanceService processInstanceService; - @Resource - private AdminUserApi adminUserApi; - - public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(DeptApi deptApi) { - super(deptApi); + public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(adminUserApi, deptApi); } @Override @@ -60,7 +57,9 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan if (dept == null) { return new HashSet<>(); } - return getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 + Set users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 + removeDisableUsers(users); + return users; } @Override @@ -69,7 +68,9 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan if (dept == null) { return new HashSet<>(); } - return getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 + Set users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 + removeDisableUsers(users); + return users; } /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java index 68ea8adc8..38698c62c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java @@ -32,16 +32,13 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat @Lazy // 避免循环依赖 private BpmProcessInstanceService processInstanceService; - @Resource - private AdminUserApi adminUserApi; - @Override public BpmTaskCandidateStrategyEnum getStrategy() { return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; } - public BpmTaskCandidateStartUserDeptLeaderStrategy(DeptApi deptApi) { - super(deptApi); + public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { + super(adminUserApi, deptApi); } @Override @@ -56,13 +53,17 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); // 获取发起人的部门负责人 - return getStartUserDeptLeader(startUserId, param); + Set users = getStartUserDeptLeader(startUserId, param); + removeDisableUsers(users); + return users; } @Override public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { // 获取发起人的部门负责人 - return getStartUserDeptLeader(startUserId, param); + Set users = getStartUserDeptLeader(startUserId, param); + removeDisableUsers(users); + return users; } private Set getStartUserDeptLeader(Long startUserId, String param) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java index 3e2a1bcdc..af9438c56 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; @@ -23,12 +23,16 @@ import java.util.*; * @author 芋道源码 */ @Component -public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy { @Resource @Lazy // 延迟加载,避免循环依赖 private BpmProcessInstanceService processInstanceService; + public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) { + super(adminUserApi); + } + @Override public BpmTaskCandidateStrategyEnum getStrategy() { return BpmTaskCandidateStrategyEnum.START_USER_SELECT; @@ -46,7 +50,9 @@ public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidate execution.getProcessInstanceId()); // 获得审批人 List assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); - return new LinkedHashSet<>(assignees); + Set users = new LinkedHashSet<>(assignees); + removeDisableUsers(users); + return users; } @Override @@ -58,7 +64,9 @@ public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidate Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId()); // 获得审批人 List assignees = startUserSelectAssignees.get(activityId); - return new LinkedHashSet<>(assignees); + Set users = new LinkedHashSet<>(assignees); + removeDisableUsers(users); + return users; } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java index 1f1c79df3..ddc990c61 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.runtime.ProcessInstance; @@ -20,12 +20,16 @@ import java.util.Set; * @author jason */ @Component -public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy { @Resource @Lazy // 延迟加载,避免循环依赖 private BpmProcessInstanceService processInstanceService; + public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) { + super(adminUserApi); + } + @Override public BpmTaskCandidateStrategyEnum getStrategy() { return BpmTaskCandidateStrategyEnum.START_USER; @@ -43,12 +47,16 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate @Override public Set calculateUsers(DelegateExecution execution, String param) { ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); - return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); + Set users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); + removeDisableUsers(users); + return users; } @Override public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { - return SetUtils.asSet(startUserId); + Set users = SetUtils.asSet(startUserId); + removeDisableUsers(users); + return users; } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java index 17449aafa..9982f7eb2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import jakarta.annotation.Resource; import org.springframework.stereotype.Component; import java.util.LinkedHashSet; @@ -17,10 +16,11 @@ import java.util.Set; * @author kyle */ @Component -public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { +public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrategy { - @Resource - private AdminUserApi adminUserApi; + public BpmTaskCandidateUserStrategy(AdminUserApi adminUserApi) { + super(adminUserApi); + } @Override public BpmTaskCandidateStrategyEnum getStrategy() { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index e82231296..6a4ff9ec0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = convertMap(adminUserApi.getUserList(userIds),AdminUserRespDTO::getId); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> { orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class)); }); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = convertMap(adminUserApi.getUserList(userIds),AdminUserRespDTO::getId); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class))); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java index 702dce3f1..cf08bb11b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java @@ -10,8 +10,8 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -34,15 +34,23 @@ import static org.mockito.Mockito.when; */ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest { - @InjectMocks private BpmTaskCandidateInvoker taskCandidateInvoker; @Mock private AdminUserApi adminUserApi; + @Spy - private BpmTaskCandidateStrategy strategy = new BpmTaskCandidateUserStrategy(); + private BpmTaskCandidateStrategy strategy ; + @Spy - private List strategyList = Collections.singletonList(strategy); + private List strategyList ; + + @BeforeEach + public void setUp() { + strategy = new BpmTaskCandidateUserStrategy(adminUserApi); // 创建strategy实例 + strategyList = Collections.singletonList(strategy); // 创建strategyList + taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi); + } @Test public void testCalculateUsers() { From e85385d1239577b82f233556d17eba73c5b39eff Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 27 Sep 2024 12:37:09 +0800 Subject: [PATCH 092/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E3=80=91=20code=20review=20=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 6a4ff9ec0..ae60d77f8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // TODO @jason:会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人? // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = convertMap(adminUserApi.getUserList(userIds),AdminUserRespDTO::getId); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class))); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class))); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From 62e75a0bfe77c796a64bbd10c68eb1b2cb2f58de Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 1 Oct 2024 23:40:08 +0800 Subject: [PATCH 093/102] =?UTF-8?q?=E3=80=90=E9=97=AE=E9=A2=98=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91=20=E8=8E=B7=E5=8F=96=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E3=80=82=E4=BF=AE=E5=A4=8D=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E7=9A=84=E7=8A=B6=E6=80=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index ae60d77f8..34245b870 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(APPROVE.getStatus()); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class))); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class))); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From abea0edf2306d61b7ade028bcf7e2e6f9282fbd5 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 2 Oct 2024 09:53:28 +0800 Subject: [PATCH 094/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=91=20=E6=B5=81=E7=A8=8B=E6=8A=84=E9=80=81?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E8=BF=94=E5=9B=9E=20activityId=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/task/BpmProcessInstanceCopyController.java | 8 ++++---- .../admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java | 2 ++ .../bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java | 1 - .../bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java | 2 +- .../service/task/BpmProcessInstanceCopyServiceImpl.java | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java index e9f0eb444..8aa4aaaa0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java @@ -60,9 +60,9 @@ public class BpmProcessInstanceCopyController { return success(new PageResult<>(pageResult.getTotal())); } - // 拼接返回 - Map taskNameMap = taskService.getTaskNameByTaskIds( - convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId)); + // 拼接返回 TODO @芋艿。这个 taskName 查询是不是可以不用。 保存的时候 taskName 已经存了, review 一下。 不知道有什么特殊场景 +// Map taskNameMap = taskService.getTaskNameByTaskIds( +// convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId)); Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); Map userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), @@ -70,7 +70,7 @@ public class BpmProcessInstanceCopyController { return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> { MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname())); MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname())); - MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName); +// MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName); MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); })); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java index 4b397fc1c..f5163faa3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java @@ -24,6 +24,8 @@ public class BpmProcessInstanceCopyRespVO { @Schema(description = "流程实例的发起时间") private LocalDateTime processInstanceStartTime; + @Schema(description = "抄送的节点的活动编号") + private String activityId; @Schema(description = "发起抄送的任务编号") private String taskId; @Schema(description = "发起抄送的任务名称") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java index c7d10396f..29da3fcfc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java @@ -57,7 +57,6 @@ public class BpmProcessInstanceCopyDO extends BaseDO { private String activityId; /** * 任务主键 - * // @芋艿 这个 taskId 是不是可以去掉了;TODO 可能要留着,因为得知道是来自哪个 task 的抄送 * 关联 Task 的 id 属性 */ private String taskId; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java index daf93747a..8f23024e8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java @@ -20,7 +20,7 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX selectListByProcessIstanceIdAndActivityId(String processInstanceId, String activityId) { + default List selectListByProcessInstanceIdAndActivityId(String processInstanceId, String activityId) { return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId, BpmProcessInstanceCopyDO::getActivityId, activityId); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index 211f508a5..ad677eb28 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -89,7 +89,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @Override public Set getCopyUserIds(String processInstanceId, String activityId) { - return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcessIstanceIdAndActivityId(processInstanceId, activityId), + return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcessInstanceIdAndActivityId(processInstanceId, activityId), BpmProcessInstanceCopyDO::getUserId); } From 61549f13c04fdbb95b7a91742ba5a0000896fb5f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 3 Oct 2024 11:10:14 +0800 Subject: [PATCH 095/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E8=AF=A6=E6=83=85=E6=96=B0=E6=8E=A5=E5=8F=A3=E7=9A=84?= =?UTF-8?q?=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/task/BpmProcessInstanceStatusEnum.java | 1 + .../task/BpmProcessInstanceCopyController.java | 8 +------- .../task/vo/instance/BpmApprovalDetailRespVO.java | 1 + .../vo/instance/BpmFormFieldsPermissionReqVO.java | 3 --- .../core/candidate/BpmTaskCandidateInvoker.java | 13 ++++++++----- .../core/candidate/BpmTaskCandidateStrategy.java | 5 ----- .../BpmTaskCandidateAssignEmptyStrategy.java | 2 +- ...skCandidateStartUserDeptLeaderMultiStrategy.java | 1 + .../bpm/service/task/BpmProcessInstanceService.java | 2 +- .../service/task/BpmProcessInstanceServiceImpl.java | 2 +- .../task/bo/AlreadyRunApproveNodeRespBO.java | 4 ++-- 11 files changed, 17 insertions(+), 25 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java index 86c5b349f..c635e92ba 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -15,6 +15,7 @@ import java.util.Arrays; @Getter @AllArgsConstructor public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { + NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java index 8aa4aaaa0..8b97d6ae5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; -import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -43,8 +42,6 @@ public class BpmProcessInstanceCopyController { private BpmProcessInstanceCopyService processInstanceCopyService; @Resource private BpmProcessInstanceService processInstanceService; - @Resource - private BpmTaskService taskService; @Resource private AdminUserApi adminUserApi; @@ -60,9 +57,7 @@ public class BpmProcessInstanceCopyController { return success(new PageResult<>(pageResult.getTotal())); } - // 拼接返回 TODO @芋艿。这个 taskName 查询是不是可以不用。 保存的时候 taskName 已经存了, review 一下。 不知道有什么特殊场景 -// Map taskNameMap = taskService.getTaskNameByTaskIds( -// convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId)); + // 拼接返回 Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); Map userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), @@ -70,7 +65,6 @@ public class BpmProcessInstanceCopyController { return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> { MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname())); MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname())); -// MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName); MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); })); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java index cfe633766..0a6ceef28 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -42,6 +42,7 @@ public class BpmApprovalDetailRespVO { private List tasks; @Schema(description = "候选人用户列表") + // TODO @jason:candidateUserList => candidateUsers,保持和 tasks 的命名风格一致哈 private List candidateUserList; // 用于未运行任务节点 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java index 1b5ea33b5..c5dc824de 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java @@ -6,9 +6,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.AssertTrue; import lombok.Data; -/** - * @author jason - */ @Schema(description = "管理后台 - 表单字段权限 Request VO") @Data public class BpmFormFieldsPermissionReqVO { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index cdd7deb4b..5ac0a00e6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -102,16 +102,19 @@ public class BpmTaskCandidateInvoker { String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); // 1.1 计算任务的候选人 Set userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); - // 1.2 候选人为空时,根据“审批人为空”的配置补充 + removeDisableUsers(userIds); + // 1.2 移除被禁用的用户 + removeDisableUsers(userIds); + + // 2. 候选人为空时,根据“审批人为空”的配置补充 if (CollUtil.isEmpty(userIds)) { userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) .calculateUsers(execution, param); + // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! } - // 1.3 移除发起人的用户 - removeStartUserIfSkip(execution, userIds); - // 2. 移除被禁用的用户 TODO @芋艿 移除禁用的用户是否应该放在 1.1 之后 - // removeDisableUsers(userIds); @芋艿 把这个移到了 BpmTaskCandidateStrategy 下面。 看一下是否可以 + // 3. 移除发起人的用户 + removeStartUserIfSkip(execution, userIds); return userIds; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java index f78716ace..9057a0ca1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -61,7 +61,6 @@ public interface BpmTaskCandidateStrategy { return users; } - /** * 基于流程实例,获得任务的候选用户们 *

@@ -79,7 +78,6 @@ public interface BpmTaskCandidateStrategy { return users; } - /** * 移除被禁用的用户 * @@ -87,7 +85,4 @@ public interface BpmTaskCandidateStrategy { */ void removeDisableUsers(Set users); - // TODO @芋艿:后续可以抽象一个 calculateUsers(String param),默认 calculateUsers 和 calculateUsers 调用它 - // TODO @芋艿 加了, review 一下 - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java index f09c82ec0..78eda0cab 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java @@ -38,7 +38,7 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac // 情况一:指定人员审批 Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement()); if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { - HashSet users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); + Set users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); removeDisableUsers(users); return users; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java index c751dd5a0..a36db376a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java @@ -58,6 +58,7 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan return new HashSet<>(); } Set users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 + // TODO @jason:这里 removeDisableUsers 的原因是啥呀? removeDisableUsers(users); return users; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index b59146f01..a14624d93 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -96,7 +96,7 @@ public interface BpmProcessInstanceService { /** * 获取审批详情。 *

- * 可以是准备发起的流程, 进行中的流程, 已经结束的流程 + * 可以是准备发起的流程、进行中的流程、已经结束的流程 * * @param loginUserId 登录人的用户编号 * @param reqVO 请求信息 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 34245b870..e98b6fcc1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1. 创建审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 Long startUserId = loginUserId; // 审批节点信息 List approvalNodes = new ArrayList<>(); // 2. 流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 3. 流程已发起 } else { // 3.1 获取流程实例信息 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } // 3.2 获取流程实例状态 Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 3.3 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 3.4. 流程已经结束 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 4. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 4.1 仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 4.2 BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!runNodeIds.contains(node.getId())) { // 节点未运行。需要进行预测 // 1. 对需要人工审批的审批节点。进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(node.getType()); approvalNodeInfo.setName(node.getName()); approvalNodeInfo.setStatus(NOT_START.getStatus()); Integer candidateStrategy = node.getCandidateStrategy(); if (START_USER_NODE.getType().equals(node.getType())) { candidateStrategy = START_USER.getStrategy(); } approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); } // 2. 对分支节点进行预测 if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支。不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList) ); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 } // 3. 结束节点 if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 节点已经运行。如果是分支节点。需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { // 如果是依次审批, 需要加其它未审批候选人 ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if(user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = CollectionUtils.convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(CollectionUtils.convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。依次审批需要按顺序展示用户 List orderUserList = new ArrayList<>(); userIds.forEach(userId-> orderUserList.add(BeanUtils.toBean(adminUserMap.get(userId), User.class))); return orderUserList; } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java index cc8384be5..4d92d2e77 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java @@ -27,9 +27,9 @@ public class AlreadyRunApproveNodeRespBO { private Set runNodeIds; /** - * 正在运行的节点的审批信息 ( key: activityId. value: 审批信息 ) + * 正在运行的节点的审批信息(key: activityId, value: 审批信息) *

- * 用于依次审批。 需要加上候选人信息 + * 用于依次审批,需要加上候选人信息 */ private Map runningApprovalNodes; From 90ced26b017efe16bca90cc1ac5f5a8b38639daa Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 3 Oct 2024 20:05:31 +0800 Subject: [PATCH 096/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E6=A8=A1=E5=9E=8B=E7=9A=84=E5=AE=9A=E4=B9=89=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B0=81=E5=8F=AF=E4=BB=A5=E5=8F=91=E8=B5=B7?= =?UTF-8?q?=E3=80=81=E8=B0=81=E5=8F=AF=E4=BB=A5=E7=AE=A1=E7=90=86=E7=9A=84?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/base/package-info.java | 4 ++++ .../admin/base/user/UserSimpleBaseVO.java | 19 +++++++++++++++ .../admin/definition/BpmModelController.java | 15 +++++++++++- .../vo/model/BpmModelMetaInfoVO.java | 9 +++++++ .../definition/vo/model/BpmModelRespVO.java | 5 ++++ .../vo/instance/BpmApprovalDetailReqVO.java | 1 + .../vo/instance/BpmApprovalDetailRespVO.java | 1 + .../convert/definition/BpmModelConvert.java | 22 +++++++++++------ .../BpmProcessDefinitionInfoDO.java | 24 +++++++++++++++++++ 9 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/package-info.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/package-info.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/package-info.java new file mode 100644 index 000000000..41ce65081 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/package-info.java @@ -0,0 +1,4 @@ +/** + * 基础包,放一些通用的 VO 类 + */ +package cn.iocoder.yudao.module.bpm.controller.admin.base; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java new file mode 100644 index 000000000..e245b3026 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.base.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户精简信息 VO") +@Data +public class UserSimpleBaseVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String nickname; + + @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") + private String avatar; + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index c5e9d6f10..b0bf11d82 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -15,6 +15,8 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -31,6 +33,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; @@ -51,6 +54,9 @@ public class BpmModelController { @Resource private BpmProcessDefinitionService processDefinitionService; + @Resource + private AdminUserApi adminUserApi; + @GetMapping("/page") @Operation(summary = "获得模型分页") public CommonResult> getModelPage(BpmModelPageReqVO pageVO) { @@ -76,7 +82,14 @@ public class BpmModelController { // 获得 ProcessDefinition Map List processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); Map processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap)); + // 获得 User Map + Set userIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), model -> { + BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); + }); + Map userMap = adminUserApi.getUserMap(userIds); + return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, + formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); } @GetMapping("/get") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index 870febe61..fa82ab1e6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -9,6 +9,8 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.URL; +import java.util.List; + /** * BPM 流程 MetaInfo Response DTO * 主要用于 { Model#setMetaInfo(String)} 的存储 @@ -50,4 +52,11 @@ public class BpmModelMetaInfoVO { @NotNull(message = "是否可见不能为空") private Boolean visible; + @Schema(description = "可发起用户编号数组", example = "[1,2,3]") + private List startUserIds; + + @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") + @NotEmpty(message = "可管理用户编号数组不能为空") + private List managerUserIds; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java index 40f56033b..c828b6463 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java @@ -1,10 +1,12 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.List; @Schema(description = "管理后台 - 流程模型 Response VO") @Data @@ -36,6 +38,9 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO { @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) private String bpmnXml; + @Schema(description = "可发起的用户数组") + private List startUsers; + /** * 最新部署的流程定义 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java index 43bf8abf8..ffe0b0139 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.AssertTrue; import lombok.Data; +// TODO @jason:这个可以简化下,使用 @RequestParam。嘿嘿,主要 VO 项不要太多 @Schema(description = "管理后台 - 审批详情 Request VO") @Data public class BpmApprovalDetailReqVO { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java index 0a6ceef28..283373893 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -47,6 +47,7 @@ public class BpmApprovalDetailRespVO { } + // TODO @jason:可以替换成 UserSimpleBaseVO。简化下 @Schema(description = "用户信息") @Data public static class User { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index 45b544fe1..3e9ddb41a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -2,17 +2,18 @@ package cn.iocoder.yudao.module.bpm.convert.definition; import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Model; @@ -23,6 +24,8 @@ import org.mapstruct.factory.Mappers; import java.util.List; import java.util.Map; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + /** * 流程模型 Convert * @@ -36,14 +39,16 @@ public interface BpmModelConvert { default PageResult buildModelPage(PageResult pageResult, Map formMap, Map categoryMap, Map deploymentMap, - Map processDefinitionMap) { - List list = CollectionUtils.convertList(pageResult.getList(), model -> { + Map processDefinitionMap, + Map userMap) { + List list = convertList(pageResult.getList(), model -> { BpmModelMetaInfoVO metaInfo = buildMetaInfo(model); BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; BpmCategoryDO category = categoryMap.get(model.getCategory()); Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; - return buildModel0(model, metaInfo, form, category, deployment, processDefinition); + List startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; + return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); }); return new PageResult<>(list, pageResult.getTotal()); } @@ -51,7 +56,7 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) { BpmModelMetaInfoVO metaInfo = buildMetaInfo(model); - BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null); + BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); if (ArrayUtil.isNotEmpty(bpmnBytes)) { modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); } @@ -60,7 +65,8 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel0(Model model, BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, - Deployment deployment, ProcessDefinition processDefinition) { + Deployment deployment, ProcessDefinition processDefinition, + List startUsers) { BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) .setKey(model.getKey()).setCategory(model.getCategory()) .setCreateTime(DateUtils.of(model.getCreateTime())); @@ -82,6 +88,8 @@ public interface BpmModelConvert { modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); } } + // User + modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)); return modelRespVO; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index e2382eb1f..d6a3093d8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -119,4 +121,26 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { */ private Boolean visible; + /** + * 可发起用户编号数组 + * + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + * + * 如果为空,则表示“全部可以发起”! + * + * 它和 {@link #visible} 的区别在于: + * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起 + * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的 + */ + @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 + private List startUserIds; + + /** + * 可管理用户编号数组 + * + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + */ + @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 + private List managerUserIds; + } From 9cc8e0d37f296d5ed2bcee39a27be9cbb064abb2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 3 Oct 2024 20:16:32 +0800 Subject: [PATCH 097/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E6=A8=A1=E5=9E=8B=E4=BF=AE=E6=94=B9=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E6=98=AF=E5=90=A6=E4=B8=BA=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E7=9A=84=E7=AE=A1=E7=90=86=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 1 + .../admin/definition/BpmModelController.java | 21 +++++----- .../convert/definition/BpmModelConvert.java | 6 +-- .../service/definition/BpmModelService.java | 17 ++++---- .../definition/BpmModelServiceImpl.java | 40 +++++++++++++------ 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index ec167719c..b41c39253 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -23,6 +23,7 @@ public interface ErrorCodeConstants { "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); + ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程的管理员"); // ========== 流程定义 1-009-003-000 ========== ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index b0bf11d82..28398a702 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; @@ -36,8 +35,8 @@ import java.util.Set; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 流程模型") @RestController @@ -68,7 +67,7 @@ public class BpmModelController { // 拼接数据 // 获得 Form 表单 Set formIds = convertSet(pageResult.getList(), model -> { - BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); return metaInfo != null ? metaInfo.getFormId() : null; }); Map formMap = formService.getFormMap(formIds); @@ -83,8 +82,8 @@ public class BpmModelController { List processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); Map processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); // 获得 User Map - Set userIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), model -> { - BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + Set userIds = convertSetByFlatMap(pageResult.getList(), model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); }); Map userMap = adminUserApi.getUserMap(userIds); @@ -116,7 +115,7 @@ public class BpmModelController { @Operation(summary = "修改模型") @PreAuthorize("@ss.hasPermission('bpm:model:update')") public CommonResult updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) { - modelService.updateModel(modelVO); + modelService.updateModel(getLoginUserId(), modelVO); return success(true); } @@ -125,7 +124,7 @@ public class BpmModelController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('bpm:model:deploy')") public CommonResult deployModel(@RequestParam("id") String id) { - modelService.deployModel(id); + modelService.deployModel(getLoginUserId(), id); return success(true); } @@ -133,7 +132,7 @@ public class BpmModelController { @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态") @PreAuthorize("@ss.hasPermission('bpm:model:update')") public CommonResult updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) { - modelService.updateModelState(reqVO.getId(), reqVO.getState()); + modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState()); return success(true); } @@ -150,7 +149,7 @@ public class BpmModelController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('bpm:model:delete')") public CommonResult deleteModel(@RequestParam("id") String id) { - modelService.deleteModel(id); + modelService.deleteModel(getLoginUserId(), id); return success(true); } @@ -167,7 +166,7 @@ public class BpmModelController { @Operation(summary = "保存仿钉钉流程设计模型") @PreAuthorize("@ss.hasPermission('bpm:model:update')") public CommonResult updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) { - modelService.updateSimpleModel(reqVO); + modelService.updateSimpleModel(getLoginUserId(), reqVO); return success(Boolean.TRUE); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index 3e9ddb41a..db8366c1e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -42,7 +42,7 @@ public interface BpmModelConvert { Map processDefinitionMap, Map userMap) { List list = convertList(pageResult.getList(), model -> { - BpmModelMetaInfoVO metaInfo = buildMetaInfo(model); + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; BpmCategoryDO category = categoryMap.get(model.getCategory()); Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; @@ -55,7 +55,7 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) { - BpmModelMetaInfoVO metaInfo = buildMetaInfo(model); + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); if (ArrayUtil.isNotEmpty(bpmnBytes)) { modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); @@ -100,7 +100,7 @@ public interface BpmModelConvert { model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); } - default BpmModelMetaInfoVO buildMetaInfo(Model model) { + default BpmModelMetaInfoVO parseMetaInfo(Model model) { return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index 84f7a440f..a2dcba480 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -59,31 +59,35 @@ public interface BpmModelService { /** * 修改流程模型 * + * @param userId 用户编号 * @param updateReqVO 更新信息 */ - void updateModel(@Valid BpmModelSaveReqVO updateReqVO); + void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); /** * 将流程模型,部署成一个流程定义 * + * @param userId 用户编号 * @param id 编号 */ - void deployModel(String id); + void deployModel(Long userId, String id); /** * 删除模型 * + * @param userId 用户编号 * @param id 编号 */ - void deleteModel(String id); + void deleteModel(Long userId, String id); /** * 修改模型的状态,实际更新的部署的流程定义的状态 * + * @param userId 用户编号 * @param id 编号 * @param state 状态 */ - void updateModelState(String id, Integer state); + void updateModelState(Long userId, String id, Integer state); /** * 获得流程定义编号对应的 BPMN Model @@ -106,10 +110,9 @@ public interface BpmModelService { /** * 更新仿钉钉流程设计模型 * + * @param userId 用户编号 * @param reqVO 请求信息 */ - void updateSimpleModel(@Valid BpmSimpleModelUpdateReqVO reqVO); - - // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案?【重要】 + void updateSimpleModel(Long userId, @Valid BpmSimpleModelUpdateReqVO reqVO); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 0441bff20..64ba6ef16 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.service.definition; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -109,9 +110,9 @@ public class BpmModelServiceImpl implements BpmModelService { @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 - public void updateModel(@Valid BpmModelSaveReqVO updateReqVO) { + public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) { // 1. 校验流程模型存在 - Model model = validateModelExists(updateReqVO.getId()); + Model model = validateModelManager(updateReqVO.getId(), userId); // 修改流程定义 BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); @@ -127,19 +128,32 @@ public class BpmModelServiceImpl implements BpmModelService { return model; } -// // 更新 BPMN XML -// saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml()); + /** + * 校验是否有流程模型的管理权限 + * + * @param id 流程模型编号 + * @param userId 用户编号 + * @return 流程模型 + */ + private Model validateModelManager(String id, Long userId) { + Model model = validateModelExists(id); + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { + throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER); + } + return model; + } @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 - public void deployModel(String id) { + public void deployModel(Long userId, String id) { // 1.1 校验流程模型存在 - Model model = validateModelExists(id); + Model model = validateModelManager(id, userId); // 1.2 校验流程图 byte[] bpmnBytes = getModelBpmnXML(model.getId()); validateBpmnXml(bpmnBytes); // 1.3 校验表单已配 - BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); BpmFormDO form = validateFormConfig(metaInfo); // 1.4 校验任务分配规则已配置 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); @@ -179,9 +193,9 @@ public class BpmModelServiceImpl implements BpmModelService { @Override @Transactional(rollbackFor = Exception.class) - public void deleteModel(String id) { + public void deleteModel(Long userId, String id) { // 校验流程模型存在 - Model model = validateModelExists(id); + Model model = validateModelManager(id, userId); // 执行删除 repositoryService.deleteModel(id); @@ -190,9 +204,9 @@ public class BpmModelServiceImpl implements BpmModelService { } @Override - public void updateModelState(String id, Integer state) { + public void updateModelState(Long userId, String id, Integer state) { // 1.1 校验流程模型存在 - Model model = validateModelExists(id); + Model model = validateModelManager(id, userId); // 1.2 校验流程定义存在 ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId()); if (definition == null) { @@ -217,9 +231,9 @@ public class BpmModelServiceImpl implements BpmModelService { } @Override - public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) { + public void updateSimpleModel(Long userId, BpmSimpleModelUpdateReqVO reqVO) { // 1. 校验流程模型存在 - Model model = validateModelExists(reqVO.getId()); + Model model = validateModelManager(reqVO.getId(), userId); // 2.1 JSON 转换成 bpmnModel BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); From 742c2967debc17687beeb35d3e86a9594e444c83 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 3 Oct 2024 20:44:46 +0800 Subject: [PATCH 098/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=8F=91=E8=B5=B7=E6=97=B6=EF=BC=8C=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E6=9C=89=E5=8F=91=E8=B5=B7=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E7=9A=84=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/enums/ErrorCodeConstants.java | 1 + .../definition/BpmProcessDefinitionController.java | 6 +++++- .../definition/BpmProcessDefinitionService.java | 9 +++++++++ .../definition/BpmProcessDefinitionServiceImpl.java | 13 +++++++++++++ .../service/task/BpmProcessInstanceServiceImpl.java | 2 +- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index b41c39253..6e32decc0 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -37,6 +37,7 @@ public interface ErrorCodeConstants { ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置"); ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在"); + ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); // ========== 流程任务 1-009-005-000 ========== ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java index 4e7a6243b..542803c6a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java @@ -34,6 +34,7 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 流程定义") @RestController @@ -87,9 +88,12 @@ public class BpmProcessDefinitionController { // 1.2 移除不可见的流程定义 Map processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap( convertSet(list, ProcessDefinition::getId)); + Long userId = getLoginUserId(); list.removeIf(processDefinition -> { BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId()); - return processDefinitionInfo != null && Boolean.FALSE.equals(processDefinitionInfo.getVisible()); + return processDefinitionInfo == null // 不存在 + || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见 + || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起 }); // 2. 拼接 VO 返回 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java index c949e0a70..fdeb5a4f5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java @@ -135,6 +135,15 @@ public interface BpmProcessDefinitionService { */ ProcessDefinition getActiveProcessDefinition(String key); + /** + * 判断用户是否可以使用该流程定义,进行流程的发起 + * + * @param processDefinition 流程定义 + * @param userId 用户编号 + * @return 是否可以发起流程 + */ + boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId); + /** * 获得 ids 对应的 Deployment Map * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index 30623c333..01abad2f8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -85,6 +85,19 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ .processDefinitionKey(key).active().singleResult(); } + @Override + public boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId) { + if (processDefinition == null) { + return false; + } + // 为空,则所有人都可以发起 + if (CollUtil.isEmpty(processDefinition.getStartUserIds())) { + return true; + } + // 不为空,则需要存在里面 + return processDefinition.getStartUserIds().contains(userId); + } + @Override public List getDeploymentList(Set ids) { if (CollUtil.isEmpty(ids)) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 3d552c501..53e6dea17 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From ad0d9d10c8f741f46a921a59face1566b072ec20 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 4 Oct 2024 11:14:02 +0800 Subject: [PATCH 099/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9ABpmTaskCa?= =?UTF-8?q?ndidateAssignEmptyStrategy=20=E8=AF=BB=E5=8F=96=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E7=AE=A1=E7=90=86=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmTaskCandidateAssignEmptyStrategy.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java index 78eda0cab..d6bd19caf 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java @@ -1,11 +1,16 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.HashSet; @@ -20,6 +25,10 @@ import java.util.Set; @Component public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy { + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessDefinitionService processDefinitionService; + public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) { super(adminUserApi); } @@ -45,8 +54,9 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac // 情况二:流程管理员 if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { - // TODO 芋艿:需要等待流程实例的管理员支持 - throw new UnsupportedOperationException("暂时实现!!!"); + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId()); + Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId()); + return new HashSet<>(processDefinition.getManagerUserIds()); } // 都不满足,还是返回空 From 4687dfbb9732a6e1a435066d248e6a2907052cb4 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 4 Oct 2024 11:47:39 +0800 Subject: [PATCH 100/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E7=9A=84=E6=B5=81=E7=A8=8B=EF=BC=8C=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=20NPE=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/convert/definition/BpmModelConvert.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index db8366c1e..5d5ced5d3 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -21,6 +21,7 @@ import org.flowable.engine.repository.ProcessDefinition; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -101,7 +102,17 @@ public interface BpmModelConvert { } default BpmModelMetaInfoVO parseMetaInfo(Model model) { - return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + if (vo == null) { + return null; + } + if (vo.getManagerUserIds() == null) { + vo.setManagerUserIds(Collections.emptyList()); + } + if (vo.getStartUserIds() == null) { + vo.setStartUserIds(Collections.emptyList()); + } + return vo; } } From f1872b70ee401b5ea62e26384d5e7bd951f2e794 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 4 Oct 2024 17:05:51 +0800 Subject: [PATCH 101/102] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E7=9A=84=20bpm=20=E8=AF=A6=E6=83=85=EF=BC=8Cbpmn=20=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E5=99=A8=E4=B9=9F=E8=BF=94=E5=9B=9E=20node=20?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 53e6dea17..5014b94f6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // TODO @芋艿 依次审批 会把未审批人放在 candidateUserList 字段 review 一下 // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息。 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO 芋艿:需要把 start 节点加出来 // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 respVO.setApproveNodes(approvalNodes); } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set runNodeIds, Map runningApprovalNodes, List approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息 final Multimap runningTask = ArrayListMultimap.create(); // 运行中的任务 List approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List approveTasks = CollUtil.newArrayList(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file From f299bf8a3614a18bc39b6f4aeff037b4c7eb3e06 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 4 Oct 2024 17:11:25 +0800 Subject: [PATCH 102/102] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=9A=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=94=A8=E4=B8=8D=E5=88=B0=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/enums/task/BpmTaskStatusEnum.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index c29efbf61..f577fc020 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -58,9 +58,5 @@ public enum BpmTaskStatusEnum { APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(), RETURN.getStatus(), APPROVING.getStatus()); } - public static boolean isEndStatusButNotApproved(Integer status) { - return ObjectUtils.equalsAny(status, - REJECT.getStatus(), CANCEL.getStatus(), RETURN.getStatus()); - } }