Parcourir la source

Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm

jason il y a 6 mois
Parent
commit
b88c09f48d

+ 6 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java

@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.bpm.controller.admin.task;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
@@ -162,7 +164,11 @@ public class BpmProcessInstanceController {
     @Operation(summary = "获得审批详情")
     @Parameter(name = "id", description = "流程实例的编号", required = true)
     @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    @SuppressWarnings("unchecked")
     public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
+        if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
+            reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
+        }
         return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
     }
 

+ 3 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java

@@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO {
     @Schema(description = "流程变量")
     private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
 
+    @Schema(description = "流程变量")
+    private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量
+
     @Schema(description = "流程实例的编号", example = "1024")
     private String processInstanceId;  // 使用场景:流程已发起时候传流程实例 ID
 

+ 15 - 8
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java

@@ -800,7 +800,7 @@ public class BpmnModelUtils {
             Gateway gateway = (Gateway) currentElement;
             SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                     flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
-                            && evalConditionExpress(variables, flow.getConditionExpression()));
+                            && (evalConditionExpress(variables, flow.getConditionExpression())));
             if (matchSequenceFlow == null) {
                 matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                         flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
@@ -821,8 +821,8 @@ public class BpmnModelUtils {
             // 查找满足条件的 SequenceFlow 路径
             Gateway gateway = (Gateway) currentElement;
             Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
-                    flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
-                            && evalConditionExpress(variables, flow.getConditionExpression()));
+                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
+                                && evalConditionExpress(variables, flow.getConditionExpression()));
             if (CollUtil.isEmpty(matchSequenceFlows)) {
                 matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
                         flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
@@ -850,18 +850,25 @@ public class BpmnModelUtils {
      * 计算条件表达式是否为 true 满足条件
      *
      * @param variables 流程实例
-     * @param express 条件表达式
+     * @param expression 条件表达式
      * @return 是否满足条件
      */
-    public static boolean evalConditionExpress(Map<String, Object> variables, String express) {
-        if (express == null) {
+    public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
+        if (expression == null) {
             return Boolean.FALSE;
         }
+        // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
+        if (variables == null) {
+            variables = new HashMap<>();
+        }
+
+        // 执行计算
         try {
-            Object result = FlowableUtils.getExpressionValue(variables, express);
+            Object result = FlowableUtils.getExpressionValue(variables, expression);
             return Boolean.TRUE.equals(result);
         } catch (FlowableException ex) {
-            log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", express, variables, ex);
+            // 为什么使用 info 日志?原因是,expression 如果从 variables 取不到值,会报错。实际这种情况下,可以忽略
+            log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, variables, ex);
             return Boolean.FALSE;
         }
     }

+ 4 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -811,8 +811,8 @@ public class SimpleModelUtils {
         if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) {
             // 查找满足条件的 BpmSimpleModelNodeVO 节点
             BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
-                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
-                            && evalConditionExpress(variables, conditionNode.getConditionSetting()));
+                        conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
+                                && evalConditionExpress(variables, conditionNode.getConditionSetting()));
             if (matchConditionNode == null) {
                 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                         conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));
@@ -826,8 +826,8 @@ public class SimpleModelUtils {
         if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) {
             // 查找满足条件的 BpmSimpleModelNodeVO 节点
             Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
-                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
-                            && evalConditionExpress(variables, conditionNode.getConditionSetting()));
+                        conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
+                                && evalConditionExpress(variables, conditionNode.getConditionSetting()));
             if (CollUtil.isEmpty(matchConditionNodes)) {
                 matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
                         conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));

+ 19 - 11
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java

@@ -29,7 +29,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
@@ -210,7 +210,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
         // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到
         // B,会不会表单权限不一致哈。
         BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());
-
         // 3.2 预测未运行节点的审批信息
         List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
                 processDefinitionInfo,
@@ -499,6 +498,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
         // 3. 抄送节点
         if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人
                 BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) {
+            List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
+                    startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
+            activityNode.setCandidateUserIds(candidateUserIds);
             return activityNode;
         }
         return null;
@@ -648,7 +650,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
             throw exception(PROCESS_INSTANCE_START_USER_CAN_START);
         }
         // 1.3 校验发起人自选审批人
-        validateStartUserSelectAssignees(definition, startUserSelectAssignees);
+        validateStartUserSelectAssignees(userId, definition, startUserSelectAssignees, variables);
 
         // 2. 创建流程实例
         if (variables == null) {
@@ -693,17 +695,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
         return instance.getId();
     }
 
-    private void validateStartUserSelectAssignees(ProcessDefinition definition,
-            Map<String, List<Long>> startUserSelectAssignees) {
-        // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表
-        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
-        List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);
-        if (CollUtil.isEmpty(tasks)) {
+    private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition,
+                                                  Map<String, List<Long>> startUserSelectAssignees,
+                                                  Map<String,Object> variables) {
+        // 1. 获取预测的节点信息
+        BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO()
+                .setProcessDefinitionId(definition.getId())
+                .setProcessVariables(variables));
+        List<ActivityNode> activityNodes = detailRespVO.getActivityNodes();
+        if (CollUtil.isEmpty(activityNodes)){
             return;
         }
 
-        // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了
-        tasks.forEach(task -> {
+        // 2.1 移除掉不是发起人自选审批人节点
+        activityNodes.removeIf(task ->
+                ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy()));
+        // 2.2 流程发起时要先获取当前流程的预测走向节点,发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了
+        activityNodes.forEach(task -> {
             List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
             if (CollUtil.isEmpty(assignees)) {
                 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());