Browse Source

!1276 fix: 发起流程报错,返回的VO缺少字段,icon为null时,copyTo转换异常问题
Merge pull request !1276 from SamllNorth_Lee/feature/bpm

芋道源码 5 months ago
parent
commit
44083c96c3

+ 5 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java

@@ -44,6 +44,11 @@ public class BpmModelMetaInfoVO {
     private Integer formType;
     @Schema(description = "表单编号", example = "1024")
     private Long formId; // formType 为 NORMAL 使用,必须非空
+
+    @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String formConf;
+    @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> formFields;
     @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
     private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
     @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")

+ 3 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java

@@ -21,6 +21,9 @@ public class BpmModelSaveReqVO extends BpmModelMetaInfoVO {
     @NotEmpty(message = "流程名称不能为空")
     private String name;
 
+    @Schema(description = "表单名字", example = "请假表单")
+    private String formName;
+
     @Schema(description = "流程分类", example = "1")
     private String category;
 

+ 7 - 6
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java

@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "管理后台 - 流程定义 Response VO")
 @Data
@@ -19,7 +20,10 @@ public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
     @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
     private String name;
 
-    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
+    @Schema(description = "表单名字", example = "请假表单")
+    private String formName;
+
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao")
     private String key;
 
     @Schema(description = "流程分类", example = "1")
@@ -27,14 +31,11 @@ public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
     @Schema(description = "流程分类名字", example = "请假")
     private String categoryName;
 
-    @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
-    private String modelId;
-
     @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
 
-    @Schema(description = "表单名字", example = "请假表单")
-    private String formName;
+    @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
+    private String modelId;
 
     @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer suspensionState; // 参见 SuspensionState 枚举

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

@@ -907,9 +907,16 @@ public class BpmnModelUtils {
      * @return 符合条件的路径
      */
     private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
-        SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
-                flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
-                        && (evalConditionExpress(variables, flow.getConditionExpression())));
+        // TODO 表单无可编辑字段时variables为空,流程走向会出现问题,比如流程审批过程中无需要修改的字段值,
+        SequenceFlow matchSequenceFlow;
+        if (CollUtil.isNotEmpty(variables)){
+             matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
+                    flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
+                            && (evalConditionExpress(variables, flow.getConditionExpression())));
+        }else {
+            matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
+                    flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
+        }
         if (matchSequenceFlow == null) {
             matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                     flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));

+ 15 - 18
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java

@@ -40,9 +40,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -209,11 +207,11 @@ public class BpmModelServiceImpl implements BpmModelService {
     public void deployModel(Long userId, String id) {
         // 1.1 校验流程模型存在
         Model model = validateModelManager(id, userId);
+        BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
         // 1.2 校验流程图
         byte[] bpmnBytes = getModelBpmnXML(model.getId());
-        validateBpmnXml(bpmnBytes);
+        validateBpmnXml(bpmnBytes, metaInfo.getType());
         // 1.3 校验表单已配
-        BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
         BpmFormDO form = validateFormConfig(metaInfo);
         // 1.4 校验任务分配规则已配置
         taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
@@ -233,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService {
         repositoryService.saveModel(model);
     }
 
-    private void validateBpmnXml(byte[] bpmnBytes) {
+    private void validateBpmnXml(byte[] bpmnBytes, int type) {
         BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
         if (bpmnModel == null) {
             throw exception(MODEL_NOT_EXISTS);
@@ -243,24 +241,23 @@ public class BpmModelServiceImpl implements BpmModelService {
         if (startEvent == null) {
             throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS);
         }
-        // 2. 校验第一个用户任务的规则类型是否为“审批人自选”,如果是则抛出异常。原因是,流程发起后,直接进入第一个用户任务,会出现无审批人的情况
-        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
-        // TODO @小北:可能极端情况下,startEvent 后面接了个 serviceTask,接着才是 userTask。。。
-        // TODO @小北:simple 因为第一个任务是发起人,可能要找第二个任务???
-        if (CollUtil.isNotEmpty(outgoingFlows)) {
-            FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
-            Integer candidateStrategy = parseCandidateStrategy(targetFlowElement);
-            if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
-                throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, targetFlowElement.getName());
-            }
-        }
-        // 3. 校验 UserTask 的 name 都配置了
+        // 2. 校验 UserTask 的 name 都配置了
         List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
         userTasks.forEach(userTask -> {
             if (StrUtil.isEmpty(userTask.getName())) {
                 throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId());
             }
         });
+        // 3. 校验第一个用户任务节点的规则类型是否为“审批人自选”
+        Map<Integer, UserTask> userTaskMap = new HashMap<>();
+        // BPMN 设计器,校验第一个用户任务节点
+        userTaskMap.put(BpmModelTypeEnum.BPMN.getType(), userTasks.get(0));
+        // SIMPLE 设计器,第一个节点固定为发起人所以校验第二个用户任务节点
+        userTaskMap.put(BpmModelTypeEnum.SIMPLE.getType(), userTasks.get(1));
+        Integer candidateStrategy = parseCandidateStrategy(userTaskMap.get(type));
+        if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
+            throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, userTaskMap.get(type).getName());
+        }
     }
 
     @Override

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

@@ -221,8 +221,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                 processDefinitionInfo,
                 processVariables, activities);
         // 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点
-        // TODO @小北:ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID) 够啦,不用判空
-        if (ObjUtil.isNotNull(reqVO.getActivityId()) && ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
+        if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
             simulateActivityNodes.removeIf(node ->
                     BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
         }

+ 31 - 34
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -558,18 +558,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
                 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
         // 2.3 调用 BPM complete 去完成任务
-        // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
-        if (CollUtil.isNotEmpty(reqVO.getVariables())) {
-            // 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
-            Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
-                    bpmnModel, reqVO.getNextAssignees(), instance);
-            // 完成任务
-            runtimeService.setVariables(task.getProcessInstanceId(), variables);
-            taskService.complete(task.getId(), variables, true);
-        } else {
-            // 完成任务
-            taskService.complete(task.getId());
-        }
+        // 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
+        Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
+                bpmnModel, reqVO.getNextAssignees(), instance);
+        // 完成任务
+        runtimeService.setVariables(task.getProcessInstanceId(), variables);
+        taskService.complete(task.getId(), variables, true);
 
         // 【加签专属】处理加签任务
         handleParentTaskIfSign(task.getParentTaskId());
@@ -590,38 +584,31 @@ public class BpmTaskServiceImpl implements BpmTaskService {
      */
     private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
                                                             Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
-        // 下一个节点参数为空,不做处理,表示流程正常流转,无需选择下一个节点的审判人
-        // TODO @小北:会出现漏选,其实需要的情况哇?
-        if (CollUtil.isEmpty(nextAssignees)) {
-            return variables;
-        }
         // 1. 获取下一个将要执行的节点集合
         FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
         List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
-
         // 2. 循环下一个将要执行的节点集合
-        Map<String, List<Long>> processVariables = new HashMap<>();
+        Map<String, List<Long>> processVariables;
         for (FlowNode nextFlowNode : nextFlowNodes) {
-            if (!nextAssignees.containsKey(nextFlowNode.getId())) {
-                throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
-            }
-            List<Long> assignees = nextAssignees.get(nextFlowNode.getId());
-            // 2.1 情况一:如果节点中的审批人策略为 发起人自选
+            // 获取任务节点中的审批人策略
             Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
+            // 2.1 情况一:如果节点中的审批人策略为 发起人自选
             if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
-                processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
-                if (processVariables == null) {
-                    processVariables = new HashMap<>();
-                }
-                List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
-                // 特殊:如果当前节点已经存在审批人,则不允许覆盖
-                if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
-                    continue;
-                }
                 // 如果节点存在,但未配置审批人
+                List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
                 if (CollUtil.isEmpty(assignees)) {
                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
                 }
+                processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
+                if (processVariables == null){
+                    processVariables = new HashMap<>();
+                }else {
+                    List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
+                    // 特殊:如果当前节点已经存在审批人,则不允许覆盖
+                    if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
+                        continue;
+                    }
+                }
                 // 校验通过的全部节点和审批人
                 processVariables.put(nextFlowNode.getId(), assignees);
                 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
@@ -629,12 +616,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
             if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
                 // 如果节点存在,但未配置审批人
+                List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
                 if (CollUtil.isEmpty(assignees)) {
                     throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
                 }
+                processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
+                if (processVariables == null){
+                    processVariables = new HashMap<>();
+                }else  {
+                    List<Long> approveUserSelectAssignee = processVariables.get(nextFlowNode.getId());
+                    // 特殊:如果当前节点已经存在审批人,则不允许覆盖
+                    if (CollUtil.isNotEmpty(approveUserSelectAssignee)) {
+                        continue;
+                    }
+                }
                 // 校验通过的全部节点和审批人
                 processVariables.put(nextFlowNode.getId(), assignees);
-                // TODO @小北:是不是要类似情况一的做法,通过 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES 拿一下?因为如果 task1 是审批人自选,task2 是审批人自选,看着会覆盖?
                 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
             }
         }