Commit 44083c96c390e6d627a9bd507ebe8b9662c06c7f

Authored by 芋道源码
Committed by Gitee
2 parents 40b3c494 42eb89b2

!1276 fix: 发起流程报错,返回的VO缺少字段,icon为null时,copyTo转换异常问题

Merge pull request !1276 from SamllNorth_Lee/feature/bpm
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 {
44 44 private Integer formType;
45 45 @Schema(description = "表单编号", example = "1024")
46 46 private Long formId; // formType 为 NORMAL 使用,必须非空
  47 +
  48 + @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
  49 + private String formConf;
  50 + @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
  51 + private List<String> formFields;
47 52 @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
48 53 private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
49 54 @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
... ...
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 {
21 21 @NotEmpty(message = "流程名称不能为空")
22 22 private String name;
23 23  
  24 + @Schema(description = "表单名字", example = "请假表单")
  25 + private String formName;
  26 +
24 27 @Schema(description = "流程分类", example = "1")
25 28 private String category;
26 29  
... ...
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;
5 5 import lombok.Data;
6 6  
7 7 import java.time.LocalDateTime;
  8 +import java.util.List;
8 9  
9 10 @Schema(description = "管理后台 - 流程定义 Response VO")
10 11 @Data
... ... @@ -19,7 +20,10 @@ public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
19 20 @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
20 21 private String name;
21 22  
22   - @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
  23 + @Schema(description = "表单名字", example = "请假表单")
  24 + private String formName;
  25 +
  26 + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao")
23 27 private String key;
24 28  
25 29 @Schema(description = "流程分类", example = "1")
... ... @@ -27,14 +31,11 @@ public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
27 31 @Schema(description = "流程分类名字", example = "请假")
28 32 private String categoryName;
29 33  
30   - @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
31   - private String modelId;
32   -
33 34 @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
34 35 private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
35 36  
36   - @Schema(description = "表单名字", example = "请假表单")
37   - private String formName;
  37 + @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
  38 + private String modelId;
38 39  
39 40 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
40 41 private Integer suspensionState; // 参见 SuspensionState 枚举
... ...
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 {
907 907 * @return 符合条件的路径
908 908 */
909 909 private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
910   - SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
911   - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
912   - && (evalConditionExpress(variables, flow.getConditionExpression())));
  910 + // TODO 表单无可编辑字段时variables为空,流程走向会出现问题,比如流程审批过程中无需要修改的字段值,
  911 + SequenceFlow matchSequenceFlow;
  912 + if (CollUtil.isNotEmpty(variables)){
  913 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  914 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  915 + && (evalConditionExpress(variables, flow.getConditionExpression())));
  916 + }else {
  917 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  918 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
  919 + }
913 920 if (matchSequenceFlow == null) {
914 921 matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
915 922 flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
... ...
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;
40 40 import org.springframework.transaction.annotation.Transactional;
41 41 import org.springframework.validation.annotation.Validated;
42 42  
43   -import java.util.List;
44   -import java.util.Map;
45   -import java.util.Objects;
  43 +import java.util.*;
46 44  
47 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
48 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
... ... @@ -209,11 +207,11 @@ public class BpmModelServiceImpl implements BpmModelService {
209 207 public void deployModel(Long userId, String id) {
210 208 // 1.1 校验流程模型存在
211 209 Model model = validateModelManager(id, userId);
  210 + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
212 211 // 1.2 校验流程图
213 212 byte[] bpmnBytes = getModelBpmnXML(model.getId());
214   - validateBpmnXml(bpmnBytes);
  213 + validateBpmnXml(bpmnBytes, metaInfo.getType());
215 214 // 1.3 校验表单已配
216   - BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
217 215 BpmFormDO form = validateFormConfig(metaInfo);
218 216 // 1.4 校验任务分配规则已配置
219 217 taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
... ... @@ -233,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService {
233 231 repositoryService.saveModel(model);
234 232 }
235 233  
236   - private void validateBpmnXml(byte[] bpmnBytes) {
  234 + private void validateBpmnXml(byte[] bpmnBytes, int type) {
237 235 BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
238 236 if (bpmnModel == null) {
239 237 throw exception(MODEL_NOT_EXISTS);
... ... @@ -243,24 +241,23 @@ public class BpmModelServiceImpl implements BpmModelService {
243 241 if (startEvent == null) {
244 242 throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS);
245 243 }
246   - // 2. 校验第一个用户任务的规则类型是否为“审批人自选”,如果是则抛出异常。原因是,流程发起后,直接进入第一个用户任务,会出现无审批人的情况
247   - List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
248   - // TODO @小北:可能极端情况下,startEvent 后面接了个 serviceTask,接着才是 userTask。。。
249   - // TODO @小北:simple 因为第一个任务是发起人,可能要找第二个任务???
250   - if (CollUtil.isNotEmpty(outgoingFlows)) {
251   - FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
252   - Integer candidateStrategy = parseCandidateStrategy(targetFlowElement);
253   - if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
254   - throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, targetFlowElement.getName());
255   - }
256   - }
257   - // 3. 校验 UserTask 的 name 都配置了
  244 + // 2. 校验 UserTask 的 name 都配置了
258 245 List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
259 246 userTasks.forEach(userTask -> {
260 247 if (StrUtil.isEmpty(userTask.getName())) {
261 248 throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId());
262 249 }
263 250 });
  251 + // 3. 校验第一个用户任务节点的规则类型是否为“审批人自选”
  252 + Map<Integer, UserTask> userTaskMap = new HashMap<>();
  253 + // BPMN 设计器,校验第一个用户任务节点
  254 + userTaskMap.put(BpmModelTypeEnum.BPMN.getType(), userTasks.get(0));
  255 + // SIMPLE 设计器,第一个节点固定为发起人所以校验第二个用户任务节点
  256 + userTaskMap.put(BpmModelTypeEnum.SIMPLE.getType(), userTasks.get(1));
  257 + Integer candidateStrategy = parseCandidateStrategy(userTaskMap.get(type));
  258 + if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
  259 + throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, userTaskMap.get(type).getName());
  260 + }
264 261 }
265 262  
266 263 @Override
... ...
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
221 221 processDefinitionInfo,
222 222 processVariables, activities);
223 223 // 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点
224   - // TODO @小北:ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID) 够啦,不用判空
225   - if (ObjUtil.isNotNull(reqVO.getActivityId()) && ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
  224 + if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
226 225 simulateActivityNodes.removeIf(node ->
227 226 BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
228 227 }
... ...
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 {
558 558 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
559 559 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
560 560 // 2.3 调用 BPM complete 去完成任务
561   - // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
562   - if (CollUtil.isNotEmpty(reqVO.getVariables())) {
563   - // 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
564   - Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
565   - bpmnModel, reqVO.getNextAssignees(), instance);
566   - // 完成任务
567   - runtimeService.setVariables(task.getProcessInstanceId(), variables);
568   - taskService.complete(task.getId(), variables, true);
569   - } else {
570   - // 完成任务
571   - taskService.complete(task.getId());
572   - }
  561 + // 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
  562 + Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
  563 + bpmnModel, reqVO.getNextAssignees(), instance);
  564 + // 完成任务
  565 + runtimeService.setVariables(task.getProcessInstanceId(), variables);
  566 + taskService.complete(task.getId(), variables, true);
573 567  
574 568 // 【加签专属】处理加签任务
575 569 handleParentTaskIfSign(task.getParentTaskId());
... ... @@ -590,38 +584,31 @@ public class BpmTaskServiceImpl implements BpmTaskService {
590 584 */
591 585 private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
592 586 Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
593   - // 下一个节点参数为空,不做处理,表示流程正常流转,无需选择下一个节点的审判人
594   - // TODO @小北:会出现漏选,其实需要的情况哇?
595   - if (CollUtil.isEmpty(nextAssignees)) {
596   - return variables;
597   - }
598 587 // 1. 获取下一个将要执行的节点集合
599 588 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
600 589 List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
601   -
602 590 // 2. 循环下一个将要执行的节点集合
603   - Map<String, List<Long>> processVariables = new HashMap<>();
  591 + Map<String, List<Long>> processVariables;
604 592 for (FlowNode nextFlowNode : nextFlowNodes) {
605   - if (!nextAssignees.containsKey(nextFlowNode.getId())) {
606   - throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
607   - }
608   - List<Long> assignees = nextAssignees.get(nextFlowNode.getId());
609   - // 2.1 情况一:如果节点中的审批人策略为 发起人自选
  593 + // 获取任务节点中的审批人策略
610 594 Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
  595 + // 2.1 情况一:如果节点中的审批人策略为 发起人自选
611 596 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
612   - processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
613   - if (processVariables == null) {
614   - processVariables = new HashMap<>();
615   - }
616   - List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
617   - // 特殊:如果当前节点已经存在审批人,则不允许覆盖
618   - if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
619   - continue;
620   - }
621 597 // 如果节点存在,但未配置审批人
  598 + List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
622 599 if (CollUtil.isEmpty(assignees)) {
623 600 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
624 601 }
  602 + processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
  603 + if (processVariables == null){
  604 + processVariables = new HashMap<>();
  605 + }else {
  606 + List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
  607 + // 特殊:如果当前节点已经存在审批人,则不允许覆盖
  608 + if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
  609 + continue;
  610 + }
  611 + }
625 612 // 校验通过的全部节点和审批人
626 613 processVariables.put(nextFlowNode.getId(), assignees);
627 614 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
... ... @@ -629,12 +616,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
629 616 // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
630 617 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
631 618 // 如果节点存在,但未配置审批人
  619 + List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
632 620 if (CollUtil.isEmpty(assignees)) {
633 621 throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
634 622 }
  623 + processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
  624 + if (processVariables == null){
  625 + processVariables = new HashMap<>();
  626 + }else {
  627 + List<Long> approveUserSelectAssignee = processVariables.get(nextFlowNode.getId());
  628 + // 特殊:如果当前节点已经存在审批人,则不允许覆盖
  629 + if (CollUtil.isNotEmpty(approveUserSelectAssignee)) {
  630 + continue;
  631 + }
  632 + }
635 633 // 校验通过的全部节点和审批人
636 634 processVariables.put(nextFlowNode.getId(), assignees);
637   - // TODO @小北:是不是要类似情况一的做法,通过 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES 拿一下?因为如果 task1 是审批人自选,task2 是审批人自选,看着会覆盖?
638 635 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
639 636 }
640 637 }
... ...