Commit 8a5638bdeaea249a0687bdfc8200e01c8d62a26c

Authored by 芋道源码
Committed by Gitee
2 parents d1fead11 1b678bd7

!1271 feat:审批通过时,查询下一个执行节点,校验流程执行正确与否

Merge pull request !1271 from SamllNorth_Lee/feature/bpm
Showing 11 changed files with 334 additions and 107 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -24,6 +24,7 @@ public interface ErrorCodeConstants { @@ -24,6 +24,7 @@ public interface ErrorCodeConstants {
24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); 24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); 25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
26 ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); 26 ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
  27 + ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_RULE_TYPE_NOT_APPROVE_USER_SELECT = new ErrorCode(1_009_002_008, "部署流程失败,原因:BPMN 流程图中,用户任务({})的规则类型不能是【审批人自选】");
27 28
28 // ========== 流程定义 1-009-003-000 ========== 29 // ========== 流程定义 1-009-003-000 ==========
29 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); 30 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@@ -40,6 +41,7 @@ public interface ErrorCodeConstants { @@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
40 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); 41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
41 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消"); 42 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
42 ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败"); 43 ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
  44 + ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "任务({})的下一个执行节点审批人未配置");
43 45
44 // ========== 流程任务 1-009-005-000 ========== 46 // ========== 流程任务 1-009-005-000 ==========
45 ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); 47 ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -178,10 +178,22 @@ public class BpmProcessInstanceController { @@ -178,10 +178,22 @@ public class BpmProcessInstanceController {
178 return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); 178 return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
179 } 179 }
180 180
  181 + @GetMapping("/get-next-approval-nodes")
  182 + @Operation(summary = "获取下一个执行的流程节点")
  183 + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
  184 + @SuppressWarnings("unchecked")
  185 + public CommonResult<List<BpmApprovalDetailRespVO.ActivityNode>> getNextApprovalNodes(@Valid BpmApprovalDetailReqVO reqVO) {
  186 + if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
  187 + reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
  188 + }
  189 + return success(processInstanceService.getNextApprovalNodes(getLoginUserId(), reqVO));
  190 + }
  191 +
181 @GetMapping("/get-bpmn-model-view") 192 @GetMapping("/get-bpmn-model-view")
182 @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") 193 @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
183 @Parameter(name = "id", description = "流程实例的编号", required = true) 194 @Parameter(name = "id", description = "流程实例的编号", required = true)
184 public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) { 195 public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
185 return success(processInstanceService.getProcessInstanceBpmnModelView(id)); 196 return success(processInstanceService.getProcessInstanceBpmnModelView(id));
186 } 197 }
  198 +
187 } 199 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  8 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  9 +import com.google.common.collect.Sets;
  10 +import jakarta.annotation.Resource;
  11 +import org.flowable.bpmn.model.BpmnModel;
  12 +import org.flowable.engine.delegate.DelegateExecution;
  13 +import org.flowable.engine.runtime.ProcessInstance;
  14 +import org.springframework.context.annotation.Lazy;
  15 +import org.springframework.stereotype.Component;
  16 +
  17 +import java.util.LinkedHashSet;
  18 +import java.util.List;
  19 +import java.util.Map;
  20 +
  21 +/**
  22 + * 审批人自选 {@link BpmTaskCandidateUserStrategy} 实现类
  23 + * 审批人在审批时选择下一个节点的审批人
  24 + *
  25 + * @author smallNorthLee
  26 + */
  27 +@Component
  28 +public class BpmTaskCandidateApproveUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
  29 +
  30 + @Resource
  31 + @Lazy // 延迟加载,避免循环依赖
  32 + private BpmProcessInstanceService processInstanceService;
  33 +
  34 + @Override
  35 + public BpmTaskCandidateStrategyEnum getStrategy() {
  36 + return BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT;
  37 + }
  38 +
  39 + @Override
  40 + public void validateParam(String param) {}
  41 +
  42 + @Override
  43 + public boolean isParamRequired() {
  44 + return false;
  45 + }
  46 +
  47 + @Override
  48 + public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  49 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
  50 + Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
  51 + Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance);
  52 + Assert.notNull(approveUserSelectAssignees, "流程实例({}) 的下一个执行节点审批人不能为空",
  53 + execution.getProcessInstanceId());
  54 + if (approveUserSelectAssignees == null) {
  55 + return Sets.newLinkedHashSet();
  56 + }
  57 + // 获得审批人
  58 + List<Long> assignees = approveUserSelectAssignees.get(execution.getCurrentActivityId());
  59 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
  60 + }
  61 +
  62 + @Override
  63 + public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  64 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  65 + if (processVariables == null) {
  66 + return Sets.newLinkedHashSet();
  67 + }
  68 + // 流程预测时会使用,允许审批人为空,如果为空前端会弹出提示选择下一个节点审批人,避免流程无法进行,审批时会真正校验节点是否配置审批人
  69 + Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processVariables);
  70 + if (approveUserSelectAssignees == null) {
  71 + return Sets.newLinkedHashSet();
  72 + }
  73 + // 获得审批人
  74 + List<Long> assignees = approveUserSelectAssignees.get(activityId);
  75 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
  76 + }
  77 +
  78 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
@@ -24,6 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable&lt;Integer&gt; { @@ -24,6 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable&lt;Integer&gt; {
24 MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), 24 MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
25 POST(22, "岗位"), 25 POST(22, "岗位"),
26 USER(30, "用户"), 26 USER(30, "用户"),
  27 + APPROVE_USER_SELECT(34, "审批人,在审批时选择下一个节点的审批人"), // 审批人,在审批时选择下一个节点的审批人
27 START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 28 START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
28 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 29 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
29 START_USER_DEPT_LEADER(37, "发起人部门负责人"), 30 START_USER_DEPT_LEADER(37, "发起人部门负责人"),
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 { @@ -30,6 +30,12 @@ public class BpmnVariableConstants {
30 */ 30 */
31 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; 31 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
32 /** 32 /**
  33 + * 流程实例的变量 - 审批人选择的审批人 Map
  34 + *
  35 + * @see ProcessInstance#getProcessVariables()
  36 + */
  37 + public static final String PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES = "PROCESS_APPROVE_USER_SELECT_ASSIGNEES";
  38 + /**
33 * 流程实例的变量 - 发起用户 ID 39 * 流程实例的变量 - 发起用户 ID
34 * 40 *
35 * @see ProcessInstance#getProcessVariables() 41 * @see ProcessInstance#getProcessVariables()
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -808,53 +808,26 @@ public class BpmnModelUtils { @@ -808,53 +808,26 @@ public class BpmnModelUtils {
808 // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的 808 // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
809 if (currentElement instanceof ExclusiveGateway) { 809 if (currentElement instanceof ExclusiveGateway) {
810 // 查找满足条件的 SequenceFlow 路径 810 // 查找满足条件的 SequenceFlow 路径
811 - Gateway gateway = (Gateway) currentElement;  
812 - // TODO @小北:当一个网关节点下存在多个满足的并行节点时,只查询一个节点流程流转会存在问题。需要优化,具体见issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/761  
813 - SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),  
814 - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())  
815 - && (evalConditionExpress(variables, flow.getConditionExpression())));  
816 - if (matchSequenceFlow == null) {  
817 - matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),  
818 - flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));  
819 - // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的  
820 - if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {  
821 - matchSequenceFlow = gateway.getOutgoingFlows().get(0);  
822 - }  
823 - } 811 + SequenceFlow matchSequenceFlow = findMatchSequenceFlow((Gateway) currentElement, variables);
824 // 遍历满足条件的 SequenceFlow 路径 812 // 遍历满足条件的 SequenceFlow 路径
825 if (matchSequenceFlow != null) { 813 if (matchSequenceFlow != null) {
826 simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements); 814 simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
827 } 815 }
828 - return;  
829 } 816 }
830 -  
831 // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的 817 // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
832 - if (currentElement instanceof InclusiveGateway) { 818 + else if (currentElement instanceof InclusiveGateway) {
833 // 查找满足条件的 SequenceFlow 路径 819 // 查找满足条件的 SequenceFlow 路径
834 - Gateway gateway = (Gateway) currentElement;  
835 - Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),  
836 - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())  
837 - && evalConditionExpress(variables, flow.getConditionExpression()));  
838 - if (CollUtil.isEmpty(matchSequenceFlows)) {  
839 - matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),  
840 - flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));  
841 - // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的  
842 - if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {  
843 - matchSequenceFlows = gateway.getOutgoingFlows();  
844 - }  
845 - } 820 + Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlows((Gateway) currentElement, variables);
846 // 遍历满足条件的 SequenceFlow 路径 821 // 遍历满足条件的 SequenceFlow 路径
847 matchSequenceFlows.forEach( 822 matchSequenceFlows.forEach(
848 flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements)); 823 flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
849 } 824 }
850 -  
851 // 情况:ParallelGateway 并行,都满足,都走 825 // 情况:ParallelGateway 并行,都满足,都走
852 - if (currentElement instanceof ParallelGateway) { 826 + else if (currentElement instanceof ParallelGateway) {
853 Gateway gateway = (Gateway) currentElement; 827 Gateway gateway = (Gateway) currentElement;
854 // 遍历子节点 828 // 遍历子节点
855 gateway.getOutgoingFlows().forEach( 829 gateway.getOutgoingFlows().forEach(
856 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); 830 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
857 - return;  
858 } 831 }
859 } 832 }
860 833
@@ -883,6 +856,10 @@ public class BpmnModelUtils { @@ -883,6 +856,10 @@ public class BpmnModelUtils {
883 if (targetElement == null) { 856 if (targetElement == null) {
884 continue; 857 continue;
885 } 858 }
  859 + // 如果是结束节点,直接返回
  860 + if (targetElement instanceof EndEvent) {
  861 + break;
  862 + }
886 // 情况一:处理不同类型的网关 863 // 情况一:处理不同类型的网关
887 if (targetElement instanceof Gateway) { 864 if (targetElement instanceof Gateway) {
888 Gateway gateway = (Gateway) targetElement; 865 Gateway gateway = (Gateway) targetElement;
@@ -911,8 +888,25 @@ public class BpmnModelUtils { @@ -911,8 +888,25 @@ public class BpmnModelUtils {
911 */ 888 */
912 private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel, 889 private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
913 Map<String, Object> variables, List<FlowNode> nextFlowNodes) { 890 Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
914 - // TODO @小北: 这里和 simulateNextFlowElements 中有重复代码,是否重构??每个网关节点拆分出方法应该比较合理化,@芋艿  
915 - // TODO @小北:ok,把 simulateNextFlowElements 里面处理网关的,复用这个方法,可以么? 891 + // 查找满足条件的 SequenceFlow 路径
  892 + SequenceFlow matchSequenceFlow = findMatchSequenceFlow(gateway, variables);
  893 + // 遍历满足条件的 SequenceFlow 路径
  894 + if (matchSequenceFlow != null) {
  895 + FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
  896 + if (targetElement instanceof FlowNode) {
  897 + nextFlowNodes.add((FlowNode) targetElement);
  898 + }
  899 + }
  900 + }
  901 +
  902 + /**
  903 + * 处理排它网关(Exclusive Gateway),选择符合条件的路径
  904 + *
  905 + * @param gateway 排他网关
  906 + * @param variables 流程变量
  907 + * @return 符合条件的路径
  908 + */
  909 + private static SequenceFlow findMatchSequenceFlow(Gateway gateway, Map<String, Object> variables){
916 SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), 910 SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
917 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) 911 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
918 && (evalConditionExpress(variables, flow.getConditionExpression()))); 912 && (evalConditionExpress(variables, flow.getConditionExpression())));
@@ -924,13 +918,7 @@ public class BpmnModelUtils { @@ -924,13 +918,7 @@ public class BpmnModelUtils {
924 matchSequenceFlow = gateway.getOutgoingFlows().get(0); 918 matchSequenceFlow = gateway.getOutgoingFlows().get(0);
925 } 919 }
926 } 920 }
927 - // 遍历满足条件的 SequenceFlow 路径  
928 - if (matchSequenceFlow != null) {  
929 - FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());  
930 - if (targetElement instanceof FlowNode) {  
931 - nextFlowNodes.add((FlowNode) targetElement);  
932 - }  
933 - } 921 + return matchSequenceFlow;
934 } 922 }
935 923
936 /** 924 /**
@@ -943,6 +931,26 @@ public class BpmnModelUtils { @@ -943,6 +931,26 @@ public class BpmnModelUtils {
943 */ 931 */
944 private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, 932 private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
945 Map<String, Object> variables, List<FlowNode> nextFlowNodes) { 933 Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  934 + // 查找满足条件的 SequenceFlow 路径集合
  935 + Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlows(gateway, variables);
  936 + // 遍历满足条件的 SequenceFlow 路径,获取目标节点
  937 + matchSequenceFlows.forEach(flow -> {
  938 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  939 + if (targetElement instanceof FlowNode) {
  940 + nextFlowNodes.add((FlowNode) targetElement);
  941 + }
  942 + });
  943 + }
  944 +
  945 + /**
  946 + * 处理排它网关(Inclusive Gateway),选择符合条件的路径
  947 + *
  948 + * @param gateway 排他网关
  949 + * @param variables 流程变量
  950 + * @return 符合条件的路径
  951 + */
  952 + private static Collection<SequenceFlow> findMatchSequenceFlows(Gateway gateway, Map<String, Object> variables) {
  953 + // 查找满足条件的 SequenceFlow 路径
946 Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), 954 Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
947 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) 955 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
948 && evalConditionExpress(variables, flow.getConditionExpression())); 956 && evalConditionExpress(variables, flow.getConditionExpression()));
@@ -954,15 +962,10 @@ public class BpmnModelUtils { @@ -954,15 +962,10 @@ public class BpmnModelUtils {
954 matchSequenceFlows = gateway.getOutgoingFlows(); 962 matchSequenceFlows = gateway.getOutgoingFlows();
955 } 963 }
956 } 964 }
957 - // 遍历满足条件的 SequenceFlow 路径,获取目标节点  
958 - matchSequenceFlows.forEach(flow -> {  
959 - FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());  
960 - if (targetElement instanceof FlowNode) {  
961 - nextFlowNodes.add((FlowNode) targetElement);  
962 - }  
963 - }); 965 + return matchSequenceFlows;
964 } 966 }
965 967
  968 +
966 /** 969 /**
967 * 处理并行网关 970 * 处理并行网关
968 * 971 *
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
@@ -201,6 +201,31 @@ public class FlowableUtils { @@ -201,6 +201,31 @@ public class FlowableUtils {
201 } 201 }
202 202
203 /** 203 /**
  204 + * 获得流程实例的审批用户选择的下一个节点的审批人 Map
  205 + *
  206 + * @param processInstance 流程实例
  207 + * @return 审批用户选择的下一个节点的审批人Map
  208 + */
  209 + public static Map<String, List<Long>> getApproveUserSelectAssignees(ProcessInstance processInstance) {
  210 + return processInstance != null ? getApproveUserSelectAssignees(processInstance.getProcessVariables()) : null;
  211 + }
  212 +
  213 + /**
  214 + * 获得流程实例的审批用户选择的下一个节点的审批人 Map
  215 + *
  216 + * @param processVariables 流程变量
  217 + * @return 审批用户选择的下一个节点的审批人Map Map
  218 + */
  219 + @SuppressWarnings("unchecked")
  220 + public static Map<String, List<Long>> getApproveUserSelectAssignees(Map<String, Object> processVariables) {
  221 + if (processVariables == null) {
  222 + return null;
  223 + }
  224 + return (Map<String, List<Long>>) processVariables.get(
  225 + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
  226 + }
  227 +
  228 + /**
204 * 获得流程实例的摘要 229 * 获得流程实例的摘要
205 * 230 *
206 * 仅有 {@link BpmModelFormTypeEnum#getType()} 表单,才有摘要。 231 * 仅有 {@link BpmModelFormTypeEnum#getType()} 表单,才有摘要。
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; @@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
17 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; 17 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
18 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; 18 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
  19 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
19 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; 22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
@@ -23,9 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; @@ -23,9 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
23 import jakarta.annotation.Resource; 24 import jakarta.annotation.Resource;
24 import jakarta.validation.Valid; 25 import jakarta.validation.Valid;
25 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
26 -import org.flowable.bpmn.model.BpmnModel;  
27 -import org.flowable.bpmn.model.StartEvent;  
28 -import org.flowable.bpmn.model.UserTask; 27 +import org.flowable.bpmn.model.*;
29 import org.flowable.common.engine.impl.db.SuspensionState; 28 import org.flowable.common.engine.impl.db.SuspensionState;
30 import org.flowable.engine.HistoryService; 29 import org.flowable.engine.HistoryService;
31 import org.flowable.engine.RepositoryService; 30 import org.flowable.engine.RepositoryService;
@@ -48,6 +47,7 @@ import java.util.Objects; @@ -48,6 +47,7 @@ import java.util.Objects;
48 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 47 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
49 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 48 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
50 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 49 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
  50 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseCandidateStrategy;
51 51
52 /** 52 /**
53 * 流程模型实现:主要进行 Flowable {@link Model} 的维护 53 * 流程模型实现:主要进行 Flowable {@link Model} 的维护
@@ -243,7 +243,18 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -243,7 +243,18 @@ public class BpmModelServiceImpl implements BpmModelService {
243 if (startEvent == null) { 243 if (startEvent == null) {
244 throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS); 244 throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS);
245 } 245 }
246 - // 2. 校验 UserTask 的 name 都配置了 246 + // 2. 校验第一个用户任务的规则类型是否为 审批人自选,如果是则抛出异常,第一个用户任务的规则类型不允许是审批人自选,因为会出现无审批人的情况
  247 + List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
  248 + if (CollUtil.isNotEmpty(outgoingFlows)){
  249 + // 2.1 获取第一个用户任务节点
  250 + FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
  251 + // 2.2 获取审批人策略
  252 + Integer candidateStrategy = parseCandidateStrategy(targetFlowElement);
  253 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())){
  254 + throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_RULE_TYPE_NOT_APPROVE_USER_SELECT, targetFlowElement.getName());
  255 + }
  256 + }
  257 + // 3. 校验 UserTask 的 name 都配置了
247 List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); 258 List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
248 userTasks.forEach(userTask -> { 259 userTasks.forEach(userTask -> {
249 if (StrUtil.isEmpty(userTask.getName())) { 260 if (StrUtil.isEmpty(userTask.getName())) {
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
@@ -97,6 +97,15 @@ public interface BpmProcessInstanceService { @@ -97,6 +97,15 @@ public interface BpmProcessInstanceService {
97 BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); 97 BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
98 98
99 /** 99 /**
  100 + * 获取下一个执行节点信息。
  101 + *
  102 + * @param loginUserId 登录人的用户编号
  103 + * @param reqVO 请求信息
  104 + * @return 下一个执行节点信息
  105 + */
  106 + List<BpmApprovalDetailRespVO.ActivityNode> getNextApprovalNodes(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
  107 +
  108 + /**
100 * 获取流程实例的 BPMN 模型视图 109 * 获取流程实例的 BPMN 模型视图
101 * 110 *
102 * @param id 流程实例的编号 111 * @param id 流程实例的编号
@@ -172,4 +181,5 @@ public interface BpmProcessInstanceService { @@ -172,4 +181,5 @@ public interface BpmProcessInstanceService {
172 * @param instance 流程任务 181 * @param instance 流程任务
173 */ 182 */
174 void processProcessInstanceCompleted(ProcessInstance instance); 183 void processProcessInstanceCompleted(ProcessInstance instance);
  184 +
175 } 185 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -11,9 +11,11 @@ import cn.hutool.core.util.StrUtil; @@ -11,9 +11,11 @@ import cn.hutool.core.util.StrUtil;
11 import cn.iocoder.yudao.framework.common.pojo.PageResult; 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
12 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 12 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
13 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 13 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  14 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
14 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 15 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
15 import cn.iocoder.yudao.framework.common.util.object.PageUtils; 16 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
16 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; 17 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
  18 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
17 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; 19 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
18 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 20 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
19 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; 21 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
@@ -55,6 +57,7 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery; @@ -55,6 +57,7 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery;
55 import org.flowable.engine.repository.ProcessDefinition; 57 import org.flowable.engine.repository.ProcessDefinition;
56 import org.flowable.engine.runtime.ProcessInstance; 58 import org.flowable.engine.runtime.ProcessInstance;
57 import org.flowable.engine.runtime.ProcessInstanceBuilder; 59 import org.flowable.engine.runtime.ProcessInstanceBuilder;
  60 +import org.flowable.task.api.Task;
58 import org.flowable.task.api.history.HistoricTaskInstance; 61 import org.flowable.task.api.history.HistoricTaskInstance;
59 import org.springframework.context.annotation.Lazy; 62 import org.springframework.context.annotation.Lazy;
60 import org.springframework.stereotype.Service; 63 import org.springframework.stereotype.Service;
@@ -166,7 +169,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -166,7 +169,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
166 Long startUserId = loginUserId; // 流程发起人 169 Long startUserId = loginUserId; // 流程发起人
167 HistoricProcessInstance historicProcessInstance = null; // 流程实例 170 HistoricProcessInstance historicProcessInstance = null; // 流程实例
168 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 171 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态
169 - Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 172 + Map<String, Object> processVariables = new HashMap<>(); // 流程变量
170 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 173 // 1.2 如果是流程已发起的场景,则使用流程实例的数据
171 if (reqVO.getProcessInstanceId() != null) { 174 if (reqVO.getProcessInstanceId() != null) {
172 historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); 175 historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());
@@ -177,10 +180,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -177,10 +180,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
177 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); 180 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
178 // 合并 DB 和前端传递的流量变量,以前端的为主 181 // 合并 DB 和前端传递的流量变量,以前端的为主
179 Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables(); 182 Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables();
180 - if (CollUtil.isNotEmpty(processVariables)) {  
181 - historicVariables.putAll(processVariables); 183 + if (CollUtil.isNotEmpty(historicVariables)) {
  184 + processVariables.putAll(historicVariables);
182 } 185 }
183 - processVariables = historicVariables; 186 + }
  187 + if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) {
  188 + processVariables.putAll(reqVO.getProcessVariables());
184 } 189 }
185 // 1.3 读取其它相关数据 190 // 1.3 读取其它相关数据
186 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( 191 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
@@ -217,6 +222,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -217,6 +222,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
217 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, 222 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
218 processDefinitionInfo, 223 processDefinitionInfo,
219 processVariables, activities); 224 processVariables, activities);
  225 + // 3.3 如果是发起动作,activityId为开始节点,不校验审批人自选节点
  226 + if (ObjUtil.isNotNull(reqVO.getActivityId()) && ObjUtil.equals(reqVO.getActivityId(),BpmnModelConstants.START_USER_NODE_ID)){
  227 + simulateActivityNodes.removeIf(node -> BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
  228 + }
220 229
221 // 4. 拼接最终数据 230 // 4. 拼接最终数据
222 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, 231 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@@ -224,6 +233,68 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -224,6 +233,68 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
224 } 233 }
225 234
226 @Override 235 @Override
  236 + public List<ActivityNode> getNextApprovalNodes(Long loginUserId, BpmApprovalDetailReqVO reqVO) {
  237 + // 1.1 校验任务存在
  238 + Task task = taskService.getTask(reqVO.getTaskId());
  239 + if (task == null) {
  240 + throw exception(TASK_NOT_EXISTS);
  241 + }
  242 + // 1.2 校验任务是否由当前用户审批
  243 + if (StrUtil.isNotBlank(task.getAssignee())
  244 + && ObjectUtil.notEqual(loginUserId, NumberUtils.parseLong(task.getAssignee()))) {
  245 + throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
  246 + }
  247 + // 1.3 校验流程实例存在
  248 + ProcessInstance instance = getProcessInstance(task.getProcessInstanceId());
  249 + if (instance == null) {
  250 + throw exception(PROCESS_INSTANCE_NOT_EXISTS);
  251 + }
  252 + // 1.4 校验BpmnModel
  253 + BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(task.getProcessDefinitionId());
  254 + if (bpmnModel == null) {
  255 + return null;
  256 + }
  257 + //1.5 流程实例是否存在
  258 + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId());
  259 + if (historicProcessInstance == null) {
  260 + throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
  261 + }
  262 +
  263 + // 2 设置流程变量
  264 + Map<String, Object> processVariables = new HashMap<>();
  265 + // 2.1 获取历史中流程变量
  266 + Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables();
  267 + if (CollUtil.isNotEmpty(historicVariables)) {
  268 + processVariables.putAll(historicVariables);
  269 + }
  270 + // 2.2 合并前端传递的流程变量,以前端为准
  271 + if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) {
  272 + processVariables.putAll(reqVO.getProcessVariables());
  273 + }
  274 + // 3 获取当前任务节点的信息
  275 + FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
  276 + // 3.1 获取下一个将要执行的节点集合
  277 + List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
  278 + return convertList(nextFlowNodes, node -> {
  279 + List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
  280 + loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables);
  281 + // 3.2 获取节点的审批人信息
  282 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(candidateUserIds);
  283 + // 3.3 获取节点的审批人部门信息
  284 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  285 + // 3.4 存在一个节点多人审批的情况,组装审批人信息
  286 + List<UserSimpleBaseVO> candidateUsers = new ArrayList<>();
  287 + userMap.forEach((key, value) -> candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(key, userMap, deptMap)));
  288 + return new ActivityNode().setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
  289 + .setId(node.getId())
  290 + .setName(node.getName())
  291 + .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
  292 + .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
  293 + .setCandidateUsers(candidateUsers);
  294 + });
  295 + }
  296 +
  297 + @Override
227 @SuppressWarnings("unchecked") 298 @SuppressWarnings("unchecked")
228 public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, 299 public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
229 BpmProcessInstancePageReqVO pageReqVO) { 300 BpmProcessInstancePageReqVO pageReqVO) {
@@ -317,9 +388,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -317,9 +388,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
317 * 获得【已结束】的活动节点们 388 * 获得【已结束】的活动节点们
318 */ 389 */
319 private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, 390 private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,
320 - BpmProcessDefinitionInfoDO processDefinitionInfo,  
321 - HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,  
322 - List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { 391 + BpmProcessDefinitionInfoDO processDefinitionInfo,
  392 + HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,
  393 + List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
323 // 遍历 tasks 列表,只处理已结束的 UserTask 394 // 遍历 tasks 列表,只处理已结束的 UserTask
324 // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 395 // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities
325 // 的话,它无法成为一个节点 396 // 的话,它无法成为一个节点
@@ -331,7 +402,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -331,7 +402,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
331 .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) 402 .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey())
332 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() 403 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
333 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别 404 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
334 - BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) 405 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
335 .setStatus(FlowableUtils.getTaskStatus(task)) 406 .setStatus(FlowableUtils.getTaskStatus(task))
336 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) 407 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
337 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) 408 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
@@ -553,7 +624,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -553,7 +624,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
553 } 624 }
554 625
555 private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, 626 private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId,
556 - Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { 627 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
557 Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, 628 Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
558 startUserId, processDefinitionId, processVariables); 629 startUserId, processDefinitionId, processVariables);
559 return new ArrayList<>(userIds); 630 return new ArrayList<>(userIds);
@@ -589,11 +660,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -589,11 +660,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
589 Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, 660 Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
590 activityInstance -> activityInstance.getEndTime() != null 661 activityInstance -> activityInstance.getEndTime() != null
591 && ObjectUtil.notEqual(activityInstance.getActivityType(), 662 && ObjectUtil.notEqual(activityInstance.getActivityType(),
592 - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); 663 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
593 Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, 664 Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
594 activityInstance -> activityInstance.getEndTime() != null 665 activityInstance -> activityInstance.getEndTime() != null
595 && ObjectUtil.equals(activityInstance.getActivityType(), 666 && ObjectUtil.equals(activityInstance.getActivityType(),
596 - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); 667 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
597 // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 668 // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉
598 finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds); 669 finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds);
599 // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 670 // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。
@@ -645,8 +716,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -645,8 +716,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
645 } 716 }
646 717
647 private String createProcessInstance0(Long userId, ProcessDefinition definition, 718 private String createProcessInstance0(Long userId, ProcessDefinition definition,
648 - Map<String, Object> variables, String businessKey,  
649 - Map<String, List<Long>> startUserSelectAssignees) { 719 + Map<String, Object> variables, String businessKey,
  720 + Map<String, List<Long>> startUserSelectAssignees) {
650 // 1.1 校验流程定义 721 // 1.1 校验流程定义
651 if (definition == null) { 722 if (definition == null) {
652 throw exception(PROCESS_DEFINITION_NOT_EXISTS); 723 throw exception(PROCESS_DEFINITION_NOT_EXISTS);
@@ -675,11 +746,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -675,11 +746,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
675 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 746 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
676 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); 747 BpmProcessInstanceStatusEnum.RUNNING.getStatus());
677 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为 748 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为
678 - // true,不影响没配置  
679 - // skipExpression 的节点 749 + // true,不影响没配置
  750 + // skipExpression 的节点
680 if (CollUtil.isNotEmpty(startUserSelectAssignees)) { 751 if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
  752 + // 设置流程变量,发起人自选审批人
681 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, 753 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
682 startUserSelectAssignees); 754 startUserSelectAssignees);
  755 +// // 设置流程变量,审批人自选审批人
  756 +// variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,
  757 +// startUserSelectAssignees);
683 } 758 }
684 759
685 // 3. 创建流程 760 // 3. 创建流程
@@ -711,17 +786,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -711,17 +786,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
711 786
712 private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition, 787 private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition,
713 Map<String, List<Long>> startUserSelectAssignees, 788 Map<String, List<Long>> startUserSelectAssignees,
714 - Map<String,Object> variables) { 789 + Map<String, Object> variables) {
715 // 1. 获取预测的节点信息 790 // 1. 获取预测的节点信息
716 BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO() 791 BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO()
717 .setProcessDefinitionId(definition.getId()) 792 .setProcessDefinitionId(definition.getId())
718 .setProcessVariables(variables)); 793 .setProcessVariables(variables));
719 List<ActivityNode> activityNodes = detailRespVO.getActivityNodes(); 794 List<ActivityNode> activityNodes = detailRespVO.getActivityNodes();
720 - if (CollUtil.isEmpty(activityNodes)){ 795 + if (CollUtil.isEmpty(activityNodes)) {
721 return; 796 return;
722 } 797 }
723 798
724 - // 2.1 移除掉不是发起人自选审批人节点 799 + // 2.1 移除掉不是发起人或者审批人自选审批人节点
725 activityNodes.removeIf(task -> 800 activityNodes.removeIf(task ->
726 ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy())); 801 ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy()));
727 // 2.2 流程发起时要先获取当前流程的预测走向节点,发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了 802 // 2.2 流程发起时要先获取当前流程的预测走向节点,发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -552,22 +552,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -552,22 +552,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
552 // 2.3 调用 BPM complete 去完成任务 552 // 2.3 调用 BPM complete 去完成任务
553 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 553 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
554 if (CollUtil.isNotEmpty(reqVO.getVariables())) { 554 if (CollUtil.isNotEmpty(reqVO.getVariables())) {
555 - Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());  
556 // 校验传递的参数中是否为下一个将要执行的任务节点 555 // 校验传递的参数中是否为下一个将要执行的任务节点
557 - validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);  
558 - // 如果有下一个审批人,则设置到流程变量中  
559 - // TODO @小北:validateNextAssignees 升级成 validateAndSetNextAssignees,然后里面吧下面这一小段逻辑,抽进去如何?  
560 - if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {  
561 - // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖  
562 - // TODO @小北:这里有个需要讨论的点,微信哈;  
563 - // TODO 因为 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES 定位是发起人,那么审批人选择的,放在 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES。目前想到两个方案:  
564 - // TODO 方案一:增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,然后设置到这里面。然后,BpmTaskCandidateStartUserSelectStrategy 也从这里读  
565 - // TODO 方案二:也是增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,根据节点审批人类型,放到 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES、PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES  
566 - // TODO 方案三:融合成 PROCESS_INSTANCE_VARIABLE_USER_SELECT_ASSIGNEES,不再区分是发起人选择、还是审批人选择。  
567 - Map<String, List<Long>> hisProcessVariables = FlowableUtils.getStartUserSelectAssignees(instance.getProcessVariables());  
568 - hisProcessVariables.putAll(reqVO.getNextAssignees());  
569 - variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, hisProcessVariables);  
570 - } 556 + Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
  557 + bpmnModel, reqVO.getNextAssignees(), instance);
571 runtimeService.setVariables(task.getProcessInstanceId(), variables); 558 runtimeService.setVariables(task.getProcessInstanceId(), variables);
572 taskService.complete(task.getId(), variables, true); 559 taskService.complete(task.getId(), variables, true);
573 } else { 560 } else {
@@ -591,42 +578,59 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -591,42 +578,59 @@ public class BpmTaskServiceImpl implements BpmTaskService {
591 * @param nextAssignees 下一个节点审批人集合(参数) 578 * @param nextAssignees 下一个节点审批人集合(参数)
592 * @param processInstance 流程实例 579 * @param processInstance 流程实例
593 */ 580 */
594 - private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel, 581 + private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
595 Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) { 582 Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
  583 + // 下一个节点参数为空,不做处理,表示流程正常流转,无需选择下一个节点的审判人
  584 + if (CollUtil.isEmpty(nextAssignees)){
  585 + return variables;
  586 + }
596 // 1. 获取当前任务节点的信息 587 // 1. 获取当前任务节点的信息
597 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); 588 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
598 // 2. 获取下一个将要执行的节点集合 589 // 2. 获取下一个将要执行的节点集合
599 List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); 590 List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
600 - 591 + Map<String, List<Long>> processVariables = new HashMap<>();
601 // 3. 循环下一个将要执行的节点集合 592 // 3. 循环下一个将要执行的节点集合
602 for (FlowNode nextFlowNode : nextFlowNodes) { 593 for (FlowNode nextFlowNode : nextFlowNodes) {
603 - // 3.1 获取下一个将要执行节点的属性(是否为自选审批人等)  
604 - // TODO @小北:public static Integer parseCandidateStrategy(FlowElement userTask) 使用这个工具方法哈。  
605 - Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();  
606 - List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);  
607 - if (CollUtil.isEmpty(elements)) {  
608 - continue; 594 + // 3.1 获取下一个将要执行节点中的审批人策略
  595 + Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
  596 + // 3.2 判断节点是否为执行节点,仅校验节点
  597 + if (!nextAssignees.containsKey(nextFlowNode.getId())) {
  598 + throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
609 } 599 }
610 - // 3.2 获取节点中的审批人策略  
611 - Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());  
612 - // 3.3 获取流程实例中的发起人自选审批人  
613 - Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());  
614 - List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());  
615 - // 3.4 如果节点中的审批人策略为 发起人自选,并且该节点的审批人为空  
616 - if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {  
617 - // 先判断前端传递的参数节点节点是否为将要执行的节点  
618 - // TODO @小北:!nextAssignees.containsKey(nextFlowNode.getId())、和 CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) 是不是等价的?  
619 - if (!nextAssignees.containsKey(nextFlowNode.getId())) {  
620 - throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName()); 600 + // 3.3 获取节点中的审批人
  601 + List<Long> assignees = nextAssignees.get(nextFlowNode.getId());
  602 + // 3.4 流程变量
  603 + // 3.5 如果节点中的审批人策略为 发起人自选
  604 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
  605 + processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
  606 + if(processVariables == null){
  607 + processVariables = new HashMap<>();
621 } 608 }
622 - // 如果前端传递的节点为空,则抛出异常  
623 - // TODO @小北:换一个错误码哈。  
624 - if (CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) { 609 + List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
  610 + // 如果当前节点已经存在审批人,则不允许覆盖
  611 + if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
  612 + continue;
  613 + }
  614 + // 如果节点存在,但未配置审批人
  615 + if (CollUtil.isEmpty(assignees)){
625 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); 616 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
626 } 617 }
  618 + // 校验通过的全部节点和审批人
  619 + processVariables.put(nextFlowNode.getId(), assignees);
  620 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
  621 + }
  622 + // 3.6 如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
  623 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())){
  624 + // 如果节点存在,但未配置审批人
  625 + if (CollUtil.isEmpty(assignees)) {
  626 + throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
  627 + }
  628 + // 校验通过的全部节点和审批人
  629 + processVariables.put(nextFlowNode.getId(), assignees);
  630 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
627 } 631 }
628 - // TODO @小北:加一个“审批人选择”的校验;  
629 } 632 }
  633 + return variables;
630 } 634 }
631 635
632 /** 636 /**