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 24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
25 25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
26 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 29 // ========== 流程定义 1-009-003-000 ==========
29 30 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
... ... @@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
40 41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
41 42 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
42 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 46 // ========== 流程任务 1-009-005-000 ==========
45 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 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 192 @GetMapping("/get-bpmn-model-view")
182 193 @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
183 194 @Parameter(name = "id", description = "流程实例的编号", required = true)
184 195 public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
185 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 24 MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
25 25 POST(22, "岗位"),
26 26 USER(30, "用户"),
  27 + APPROVE_USER_SELECT(34, "审批人,在审批时选择下一个节点的审批人"), // 审批人,在审批时选择下一个节点的审批人
27 28 START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
28 29 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
29 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 30 */
31 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 39 * 流程实例的变量 - 发起用户 ID
34 40 *
35 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 808 // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
809 809 if (currentElement instanceof ExclusiveGateway) {
810 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 812 // 遍历满足条件的 SequenceFlow 路径
825 813 if (matchSequenceFlow != null) {
826 814 simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
827 815 }
828   - return;
829 816 }
830   -
831 817 // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
832   - if (currentElement instanceof InclusiveGateway) {
  818 + else if (currentElement instanceof InclusiveGateway) {
833 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 821 // 遍历满足条件的 SequenceFlow 路径
847 822 matchSequenceFlows.forEach(
848 823 flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
849 824 }
850   -
851 825 // 情况:ParallelGateway 并行,都满足,都走
852   - if (currentElement instanceof ParallelGateway) {
  826 + else if (currentElement instanceof ParallelGateway) {
853 827 Gateway gateway = (Gateway) currentElement;
854 828 // 遍历子节点
855 829 gateway.getOutgoingFlows().forEach(
856 830 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
857   - return;
858 831 }
859 832 }
860 833  
... ... @@ -883,6 +856,10 @@ public class BpmnModelUtils {
883 856 if (targetElement == null) {
884 857 continue;
885 858 }
  859 + // 如果是结束节点,直接返回
  860 + if (targetElement instanceof EndEvent) {
  861 + break;
  862 + }
886 863 // 情况一:处理不同类型的网关
887 864 if (targetElement instanceof Gateway) {
888 865 Gateway gateway = (Gateway) targetElement;
... ... @@ -911,8 +888,25 @@ public class BpmnModelUtils {
911 888 */
912 889 private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
913 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 910 SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
917 911 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
918 912 && (evalConditionExpress(variables, flow.getConditionExpression())));
... ... @@ -924,13 +918,7 @@ public class BpmnModelUtils {
924 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 931 */
944 932 private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
945 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 954 Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
947 955 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
948 956 && evalConditionExpress(variables, flow.getConditionExpression()));
... ... @@ -954,15 +962,10 @@ public class BpmnModelUtils {
954 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 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 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 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
17 17 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
18 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 20 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
20 21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
21 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 24 import jakarta.annotation.Resource;
24 25 import jakarta.validation.Valid;
25 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 28 import org.flowable.common.engine.impl.db.SuspensionState;
30 29 import org.flowable.engine.HistoryService;
31 30 import org.flowable.engine.RepositoryService;
... ... @@ -48,6 +47,7 @@ import java.util.Objects;
48 47 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
49 48 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
50 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 53 * 流程模型实现:主要进行 Flowable {@link Model} 的维护
... ... @@ -243,7 +243,18 @@ public class BpmModelServiceImpl implements BpmModelService {
243 243 if (startEvent == null) {
244 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 258 List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
248 259 userTasks.forEach(userTask -> {
249 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 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 109 * 获取流程实例的 BPMN 模型视图
101 110 *
102 111 * @param id 流程实例的编号
... ... @@ -172,4 +181,5 @@ public interface BpmProcessInstanceService {
172 181 * @param instance 流程任务
173 182 */
174 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 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
12 12 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
13 13 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  14 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
14 15 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
15 16 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
16 17 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
  18 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
17 19 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
18 20 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
19 21 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
... ... @@ -55,6 +57,7 @@ import org.flowable.engine.history.HistoricProcessInstanceQuery;
55 57 import org.flowable.engine.repository.ProcessDefinition;
56 58 import org.flowable.engine.runtime.ProcessInstance;
57 59 import org.flowable.engine.runtime.ProcessInstanceBuilder;
  60 +import org.flowable.task.api.Task;
58 61 import org.flowable.task.api.history.HistoricTaskInstance;
59 62 import org.springframework.context.annotation.Lazy;
60 63 import org.springframework.stereotype.Service;
... ... @@ -166,7 +169,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
166 169 Long startUserId = loginUserId; // 流程发起人
167 170 HistoricProcessInstance historicProcessInstance = null; // 流程实例
168 171 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态
169   - Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量
  172 + Map<String, Object> processVariables = new HashMap<>(); // 流程变量
170 173 // 1.2 如果是流程已发起的场景,则使用流程实例的数据
171 174 if (reqVO.getProcessInstanceId() != null) {
172 175 historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());
... ... @@ -177,10 +180,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
177 180 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
178 181 // 合并 DB 和前端传递的流量变量,以前端的为主
179 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 190 // 1.3 读取其它相关数据
186 191 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
... ... @@ -217,6 +222,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
217 222 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
218 223 processDefinitionInfo,
219 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 230 // 4. 拼接最终数据
222 231 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
... ... @@ -224,6 +233,68 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
224 233 }
225 234  
226 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 298 @SuppressWarnings("unchecked")
228 299 public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
229 300 BpmProcessInstancePageReqVO pageReqVO) {
... ... @@ -317,9 +388,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
317 388 * 获得【已结束】的活动节点们
318 389 */
319 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 394 // 遍历 tasks 列表,只处理已结束的 UserTask
324 395 // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities
325 396 // 的话,它无法成为一个节点
... ... @@ -331,7 +402,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
331 402 .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey())
332 403 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
333 404 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
334   - BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
  405 + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
335 406 .setStatus(FlowableUtils.getTaskStatus(task))
336 407 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
337 408 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
... ... @@ -553,7 +624,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
553 624 }
554 625  
555 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 628 Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
558 629 startUserId, processDefinitionId, processVariables);
559 630 return new ArrayList<>(userIds);
... ... @@ -589,11 +660,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
589 660 Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
590 661 activityInstance -> activityInstance.getEndTime() != null
591 662 && ObjectUtil.notEqual(activityInstance.getActivityType(),
592   - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
  663 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
593 664 Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
594 665 activityInstance -> activityInstance.getEndTime() != null
595 666 && ObjectUtil.equals(activityInstance.getActivityType(),
596   - BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
  667 + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
597 668 // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉
598 669 finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds);
599 670 // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。
... ... @@ -645,8 +716,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
645 716 }
646 717  
647 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 721 // 1.1 校验流程定义
651 722 if (definition == null) {
652 723 throw exception(PROCESS_DEFINITION_NOT_EXISTS);
... ... @@ -675,11 +746,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
675 746 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
676 747 BpmProcessInstanceStatusEnum.RUNNING.getStatus());
677 748 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为
678   - // true,不影响没配置
679   - // skipExpression 的节点
  749 + // true,不影响没配置
  750 + // skipExpression 的节点
680 751 if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
  752 + // 设置流程变量,发起人自选审批人
681 753 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
682 754 startUserSelectAssignees);
  755 +// // 设置流程变量,审批人自选审批人
  756 +// variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,
  757 +// startUserSelectAssignees);
683 758 }
684 759  
685 760 // 3. 创建流程
... ... @@ -711,17 +786,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
711 786  
712 787 private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition,
713 788 Map<String, List<Long>> startUserSelectAssignees,
714   - Map<String,Object> variables) {
  789 + Map<String, Object> variables) {
715 790 // 1. 获取预测的节点信息
716 791 BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO()
717 792 .setProcessDefinitionId(definition.getId())
718 793 .setProcessVariables(variables));
719 794 List<ActivityNode> activityNodes = detailRespVO.getActivityNodes();
720   - if (CollUtil.isEmpty(activityNodes)){
  795 + if (CollUtil.isEmpty(activityNodes)) {
721 796 return;
722 797 }
723 798  
724   - // 2.1 移除掉不是发起人自选审批人节点
  799 + // 2.1 移除掉不是发起人或者审批人自选审批人节点
725 800 activityNodes.removeIf(task ->
726 801 ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy()));
727 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 552 // 2.3 调用 BPM complete 去完成任务
553 553 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
554 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 558 runtimeService.setVariables(task.getProcessInstanceId(), variables);
572 559 taskService.complete(task.getId(), variables, true);
573 560 } else {
... ... @@ -591,42 +578,59 @@ public class BpmTaskServiceImpl implements BpmTaskService {
591 578 * @param nextAssignees 下一个节点审批人集合(参数)
592 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 582 Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
  583 + // 下一个节点参数为空,不做处理,表示流程正常流转,无需选择下一个节点的审判人
  584 + if (CollUtil.isEmpty(nextAssignees)){
  585 + return variables;
  586 + }
596 587 // 1. 获取当前任务节点的信息
597 588 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
598 589 // 2. 获取下一个将要执行的节点集合
599 590 List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
600   -
  591 + Map<String, List<Long>> processVariables = new HashMap<>();
601 592 // 3. 循环下一个将要执行的节点集合
602 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 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 /**
... ...