Commit 10bbe741b0b57d3694331d3f23efd396607271c6

Authored by 芋道源码
Committed by Gitee
2 parents 7a6d1bdd 11858ca0

!1249 feat: 子流程

Merge pull request !1249 from Lesan/feature/bpm-子流程
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessStartUserEmptyTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * BPM 当子流程发起人为空时类型枚举
  12 + *
  13 + * @author Lesan
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum BpmChildProcessStartUserEmptyTypeEnum implements ArrayValuable<Integer> {
  18 +
  19 + MAIN_PROCESS_START_USER(1, "同主流程发起人"),
  20 + CHILD_PROCESS_ADMIN(2, "子流程管理员"),
  21 + MAIN_PROCESS_ADMIN(3, "主流程管理员");
  22 +
  23 + private final Integer type;
  24 + private final String name;
  25 +
  26 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserEmptyTypeEnum::getType).toArray(Integer[]::new);
  27 +
  28 + public static BpmChildProcessStartUserEmptyTypeEnum typeOf(Integer type) {
  29 + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
  30 + }
  31 +
  32 + @Override
  33 + public Integer[] array() {
  34 + return ARRAYS;
  35 + }
  36 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessStartUserTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * BPM 子流程发起人类型枚举
  12 + *
  13 + * @author Lesan
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum BpmChildProcessStartUserTypeEnum implements ArrayValuable<Integer> {
  18 +
  19 + MAIN_PROCESS_START_USER(1, "同主流程发起人"),
  20 + FROM_FORM(2, "表单");
  21 +
  22 + private final Integer type;
  23 + private final String name;
  24 +
  25 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserTypeEnum::getType).toArray(Integer[]::new);
  26 +
  27 + public static BpmChildProcessStartUserTypeEnum typeOf(Integer type) {
  28 + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
  29 + }
  30 +
  31 + @Override
  32 + public Integer[] array() {
  33 + return ARRAYS;
  34 + }
  35 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
@@ -30,6 +30,9 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -30,6 +30,9 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable&lt;Integer&gt; {
30 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"), 30 DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
31 TRIGGER_NODE(15, "触发器", "serviceTask"), 31 TRIGGER_NODE(15, "触发器", "serviceTask"),
32 32
  33 + CHILD_PROCESS(20, "子流程", "callActivity"),
  34 + ASYNC_CHILD_PROCESS(21, "异步子流程", "callActivity"),
  35 +
33 // 50 ~ 条件分支 36 // 50 ~ 条件分支
34 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 37 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
35 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), 38 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
@@ -26,6 +26,7 @@ public enum BpmReasonEnum { @@ -26,6 +26,7 @@ public enum BpmReasonEnum {
26 TIMEOUT_REJECT("审批超时,系统自动不通过"), 26 TIMEOUT_REJECT("审批超时,系统自动不通过"),
27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), 27 ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), 28 ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
  29 + ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"),
29 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), 30 ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
30 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), 31 ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
31 ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), 32 ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -10,6 +10,7 @@ import jakarta.validation.Valid; @@ -10,6 +10,7 @@ import jakarta.validation.Valid;
10 import jakarta.validation.constraints.NotEmpty; 10 import jakarta.validation.constraints.NotEmpty;
11 import jakarta.validation.constraints.NotNull; 11 import jakarta.validation.constraints.NotNull;
12 import lombok.Data; 12 import lombok.Data;
  13 +import org.flowable.bpmn.model.IOParameter;
13 import org.hibernate.validator.constraints.URL; 14 import org.hibernate.validator.constraints.URL;
14 15
15 import java.util.List; 16 import java.util.List;
@@ -123,6 +124,8 @@ public class BpmSimpleModelNodeVO { @@ -123,6 +124,8 @@ public class BpmSimpleModelNodeVO {
123 */ 124 */
124 private TriggerSetting triggerSetting; 125 private TriggerSetting triggerSetting;
125 126
  127 + private ChildProcessSetting childProcessSetting;
  128 +
126 @Schema(description = "任务监听器") 129 @Schema(description = "任务监听器")
127 @Valid 130 @Valid
128 @Data 131 @Data
@@ -401,4 +404,55 @@ public class BpmSimpleModelNodeVO { @@ -401,4 +404,55 @@ public class BpmSimpleModelNodeVO {
401 private Set<String> deleteFields; 404 private Set<String> deleteFields;
402 } 405 }
403 } 406 }
  407 +
  408 + @Schema(description = "子流程节点配置")
  409 + @Data
  410 + @Valid
  411 + public static class ChildProcessSetting {
  412 +
  413 + @Schema(description = "被调用流程", example = "xxx")
  414 + @NotEmpty(message = "被调用流程不能为空")
  415 + private String calledElement;
  416 +
  417 + @Schema(description = "被调用流程名称", example = "xxx")
  418 + @NotEmpty(message = "被调用流程名称不能为空")
  419 + private String calledElementName;
  420 +
  421 + @Schema(description = "是否异步", example = "false")
  422 + @NotNull(message = "是否异步不能为空")
  423 + private Boolean async;
  424 +
  425 + @Schema(description = "输入参数(主->子)", example = "[]")
  426 + private List<IOParameter> inVariable;
  427 +
  428 + @Schema(description = "输出参数(子->主)", example = "[]")
  429 + private List<IOParameter> outVariable;
  430 +
  431 + @Schema(description = "是否自动跳过子流程发起节点", example = "false")
  432 + @NotNull(message = "是否自动跳过子流程发起节点不能为空")
  433 + private Boolean skipStartUserNode;
  434 +
  435 + @Schema(description = "子流程发起人配置", example = "{}")
  436 + private StartUserSetting startUserSetting;
  437 +
  438 + @Schema(description = "子流程发起人配置")
  439 + @Data
  440 + @Valid
  441 + public static class StartUserSetting {
  442 +
  443 + @Schema(description = "子流程发起人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  444 + @NotNull(message = "子流程发起人类型")
  445 + @InEnum(BpmChildProcessStartUserTypeEnum.class)
  446 + private Integer type;
  447 +
  448 + @Schema(description = "表单", example = "xxx")
  449 + private String formField;
  450 +
  451 + @Schema(description = "当子流程发起人为空时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  452 + @InEnum(BpmChildProcessStartUserEmptyTypeEnum.class)
  453 + private Integer emptyHandleType;
  454 +
  455 + }
  456 +
  457 + }
404 } 458 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
@@ -52,6 +52,13 @@ public class BpmnVariableConstants { @@ -52,6 +52,13 @@ public class BpmnVariableConstants {
52 public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; 52 public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
53 53
54 /** 54 /**
  55 + * 流程实例的变量 - 用于判断流程是否需要跳过发起人节点
  56 + *
  57 + * @see ProcessInstance#getProcessVariables()
  58 + */
  59 + public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE";
  60 +
  61 + /**
55 * 流程实例的变量 - 流程开始时间 62 * 流程实例的变量 - 流程开始时间
56 * 63 *
57 * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题 64 * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2
3 import cn.hutool.core.util.ObjectUtil; 3 import cn.hutool.core.util.ObjectUtil;
  4 +import cn.hutool.core.util.StrUtil;
4 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.core.KeyValue; 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -240,7 +241,7 @@ public class FlowableUtils { @@ -240,7 +241,7 @@ public class FlowableUtils {
240 return formFieldsMap.entrySet().stream() 241 return formFieldsMap.entrySet().stream()
241 .limit(3) 242 .limit(3)
242 .map(entry -> new KeyValue<>(entry.getValue().getTitle(), 243 .map(entry -> new KeyValue<>(entry.getValue().getTitle(),
243 - processVariables.getOrDefault(entry.getValue().getField(), "").toString())) 244 + StrUtil.toStringOrEmpty(processVariables.getOrDefault(entry.getValue().getField(), ""))))
244 .collect(Collectors.toList()); 245 .collect(Collectors.toList());
245 } 246 }
246 247
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert; @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.map.MapUtil; 5 import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
10 import cn.iocoder.yudao.module.bpm.enums.definition.*; 11 import cn.iocoder.yudao.module.bpm.enums.definition.*;
@@ -16,12 +17,15 @@ import org.flowable.bpmn.BpmnAutoLayout; @@ -16,12 +17,15 @@ import org.flowable.bpmn.BpmnAutoLayout;
16 import org.flowable.bpmn.constants.BpmnXMLConstants; 17 import org.flowable.bpmn.constants.BpmnXMLConstants;
17 import org.flowable.bpmn.model.Process; 18 import org.flowable.bpmn.model.Process;
18 import org.flowable.bpmn.model.*; 19 import org.flowable.bpmn.model.*;
  20 +import org.flowable.engine.delegate.ExecutionListener;
19 import org.flowable.engine.delegate.TaskListener; 21 import org.flowable.engine.delegate.TaskListener;
20 import org.springframework.util.MultiValueMap; 22 import org.springframework.util.MultiValueMap;
21 23
22 import java.util.*; 24 import java.util.*;
23 25
24 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 26 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
  27 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
  28 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS;
25 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 29 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
26 import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION; 30 import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION;
27 import static java.util.Arrays.asList; 31 import static java.util.Arrays.asList;
@@ -42,7 +46,8 @@ public class SimpleModelUtils { @@ -42,7 +46,8 @@ public class SimpleModelUtils {
42 List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), 46 List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
43 new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(), 47 new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(),
44 new DelayTimerNodeConvert(), new TriggerNodeConvert(), 48 new DelayTimerNodeConvert(), new TriggerNodeConvert(),
45 - new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert()); 49 + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(),
  50 + new ChildProcessConvert(), new AsyncChildProcessConvert());
46 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); 51 converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
47 } 52 }
48 53
@@ -773,6 +778,80 @@ public class SimpleModelUtils { @@ -773,6 +778,80 @@ public class SimpleModelUtils {
773 778
774 } 779 }
775 780
  781 + private static class ChildProcessConvert implements NodeConvert {
  782 +
  783 + @Override
  784 + public CallActivity convert(BpmSimpleModelNodeVO node) {
  785 + BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting();
  786 + List<IOParameter> inVariable = childProcessSetting.getInVariable() == null ?
  787 + new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariable());
  788 + CallActivity callActivity = new CallActivity();
  789 + callActivity.setId(node.getId());
  790 + callActivity.setName(node.getName());
  791 + callActivity.setCalledElementType("key");
  792 + // 1. 是否异步
  793 + callActivity.setAsynchronous(node.getChildProcessSetting().getAsync());
  794 + // 2. 调用的子流程
  795 + callActivity.setCalledElement(childProcessSetting.getCalledElement());
  796 + callActivity.setProcessInstanceName(childProcessSetting.getCalledElementName());
  797 + // 3. 是否自动跳过子流程发起节点
  798 + if (Boolean.TRUE.equals(childProcessSetting.getSkipStartUserNode())) {
  799 + IOParameter ioParameter = new IOParameter();
  800 + ioParameter.setSourceExpression("true");
  801 + ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
  802 + inVariable.add(ioParameter);
  803 + } else {
  804 + IOParameter ioParameter = new IOParameter();
  805 + ioParameter.setSourceExpression("false");
  806 + ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
  807 + inVariable.add(ioParameter);
  808 + }
  809 + // 4. 主→子变量传递
  810 + // 默认需要传递的一些变量
  811 + // 4.1 流程状态
  812 + IOParameter ioParameter = new IOParameter();
  813 + ioParameter.setSource(PROCESS_INSTANCE_VARIABLE_STATUS);
  814 + ioParameter.setTarget(PROCESS_INSTANCE_VARIABLE_STATUS);
  815 + inVariable.add(ioParameter);
  816 + callActivity.setInParameters(inVariable);
  817 + // 5. 子→主变量传递
  818 + if (childProcessSetting.getOutVariable() != null && !childProcessSetting.getOutVariable().isEmpty()) {
  819 + callActivity.setOutParameters(childProcessSetting.getOutVariable());
  820 + }
  821 + // 6. 子流程发起人配置
  822 + List<FlowableListener> executionListeners = new ArrayList<>();
  823 + FlowableListener flowableListener = new FlowableListener();
  824 + flowableListener.setEvent(ExecutionListener.EVENTNAME_START);
  825 + flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
  826 + flowableListener.setImplementation("${bpmCallActivityListener}");
  827 + FieldExtension fieldExtension = new FieldExtension();
  828 + fieldExtension.setFieldName("listenerConfig");
  829 + fieldExtension.setStringValue(JsonUtils.toJsonString(childProcessSetting.getStartUserSetting()));
  830 + flowableListener.getFieldExtensions().add(fieldExtension);
  831 + executionListeners.add(flowableListener);
  832 + callActivity.setExecutionListeners(executionListeners);
  833 + // 添加节点类型
  834 + addNodeType(node.getType(), callActivity);
  835 + return callActivity;
  836 + }
  837 +
  838 + @Override
  839 + public BpmSimpleModelNodeTypeEnum getType() {
  840 + return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS;
  841 + }
  842 +
  843 + }
  844 +
  845 + private static class AsyncChildProcessConvert extends ChildProcessConvert {
  846 +
  847 + @Override
  848 + public BpmSimpleModelNodeTypeEnum getType() {
  849 + return BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS;
  850 + }
  851 +
  852 + }
  853 +
  854 +
776 private static String buildGatewayJoinId(String id) { 855 private static String buildGatewayJoinId(String id) {
777 return id + "_join"; 856 return id + "_join";
778 } 857 }
@@ -802,6 +881,8 @@ public class SimpleModelUtils { @@ -802,6 +881,8 @@ public class SimpleModelUtils {
802 || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE 881 || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE
803 || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE 882 || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE
804 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE 883 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
  884 + || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
  885 + || nodeType == BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS
805 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { 886 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
806 // 添加元素 887 // 添加元素
807 resultNodes.add(currentNode); 888 resultNodes.add(currentNode);
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -320,6 +320,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -320,6 +320,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
320 // 遍历 tasks 列表,只处理已结束的 UserTask 320 // 遍历 tasks 列表,只处理已结束的 UserTask
321 // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 321 // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities
322 // 的话,它无法成为一个节点 322 // 的话,它无法成为一个节点
  323 + // TODO @芋艿:子流程只有activity,这里获取不到已结束的子流程
323 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); 324 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
324 List<ActivityNode> approvalNodes = convertList(endTasks, task -> { 325 List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
325 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); 326 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
@@ -390,7 +391,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -390,7 +391,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
390 List<HistoricTaskInstance> tasks) { 391 List<HistoricTaskInstance> tasks) {
391 // 构建运行中的任务,基于 activityId 分组 392 // 构建运行中的任务,基于 activityId 分组
392 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null 393 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null
393 - && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); 394 + && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER, ELEMENT_CALL_ACTIVITY)));
394 Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, 395 Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities,
395 HistoricActivityInstance::getActivityId); 396 HistoricActivityInstance::getActivityId);
396 397
@@ -404,7 +405,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -404,7 +405,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
404 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 405 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同
405 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()) 406 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId())
406 .setName(firstActivity.getActivityName()) 407 .setName(firstActivity.getActivityName())
407 - .setNodeType(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别 408 + .setNodeType(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”和"子流程"的识别
408 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) 409 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
409 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) 410 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
410 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) 411 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
@@ -413,6 +414,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -413,6 +414,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
413 // 处理每个任务的 tasks 属性 414 // 处理每个任务的 tasks 属性
414 for (HistoricActivityInstance activity : taskActivities) { 415 for (HistoricActivityInstance activity : taskActivities) {
415 HistoricTaskInstance task = taskMap.get(activity.getTaskId()); 416 HistoricTaskInstance task = taskMap.get(activity.getTaskId());
  417 + if (task == null) {
  418 + continue;
  419 + }
416 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); 420 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));
417 // 加签子任务,需要过滤掉已经完成的加签子任务 421 // 加签子任务,需要过滤掉已经完成的加签子任务
418 List<HistoricTaskInstance> childrenTasks = filterList( 422 List<HistoricTaskInstance> childrenTasks = filterList(
@@ -503,6 +507,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -503,6 +507,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
503 activityNode.setCandidateUserIds(candidateUserIds); 507 activityNode.setCandidateUserIds(candidateUserIds);
504 return activityNode; 508 return activityNode;
505 } 509 }
  510 +
  511 + // 4. 子流程节点
  512 + if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(node.getType()) ||
  513 + BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS.getType().equals(node.getType())) {
  514 + return activityNode;
  515 + }
506 return null; 516 return null;
507 } 517 }
508 518
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
1 package cn.iocoder.yudao.module.bpm.service.task; 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
4 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
6 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
@@ -63,6 +64,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -63,6 +64,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
63 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 64 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
64 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; 65 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
65 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; 66 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
  67 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
66 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 68 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
67 69
68 /** 70 /**
@@ -1212,19 +1214,30 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -1212,19 +1214,30 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1212 } 1214 }
1213 } 1215 }
1214 1216
1215 - // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理  
1216 - if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {  
1217 - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略  
1218 - // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识  
1219 - Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),  
1220 - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); 1217 + // 发起人节点
  1218 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
  1219 + if (bpmnModel == null) {
  1220 + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
  1221 + return;
  1222 + }
  1223 + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  1224 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
  1225 + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
  1226 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1227 + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
  1228 + Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1229 + PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
  1230 + if (userTaskElement.getId().equals(START_USER_NODE_ID) &&
  1231 + (skipStartUserNodeFlag == null || Boolean.TRUE.equals(skipStartUserNodeFlag)) &&
  1232 + !Boolean.TRUE.equals(returnTaskFlag)) {
  1233 + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
  1234 + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
  1235 + return;
  1236 + }
  1237 + // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
  1238 + if (!userTaskElement.getId().equals(START_USER_NODE_ID) &&
  1239 + StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
1221 if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { 1240 if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
1222 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());  
1223 - if (bpmnModel == null) {  
1224 - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());  
1225 - return;  
1226 - }  
1227 - FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());  
1228 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); 1241 Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
1229 1242
1230 // 情况一:自动跳过 1243 // 情况一:自动跳过
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.task.listener;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  7 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
  8 +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  9 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserEmptyTypeEnum;
  10 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserTypeEnum;
  11 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  12 +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
  13 +import jakarta.annotation.Resource;
  14 +import lombok.Setter;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +import org.flowable.engine.delegate.DelegateExecution;
  17 +import org.flowable.engine.delegate.ExecutionListener;
  18 +import org.flowable.engine.impl.el.FixedValue;
  19 +import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
  20 +import org.springframework.stereotype.Component;
  21 +
  22 +import java.util.List;
  23 +
  24 +/**
  25 + * BPM 子流程监听器
  26 + *
  27 + * @author Lesan
  28 + */
  29 +@Component
  30 +@Slf4j
  31 +public class BpmCallActivityListener implements ExecutionListener {
  32 +
  33 + public static final String DELEGATE_EXPRESSION = "${bpmCallActivityListener}";
  34 +
  35 + @Setter
  36 + private FixedValue listenerConfig;
  37 +
  38 + @Resource
  39 + private BpmProcessDefinitionService processDefinitionService;
  40 +
  41 + @Override
  42 + public void notify(DelegateExecution execution) {
  43 + String expressionText = listenerConfig.getExpressionText();
  44 + Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText);
  45 + BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject(expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class);
  46 + // 1. 当发起人来源为表单时
  47 + if (startUserSetting != null &&
  48 + startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.FROM_FORM.getType())) {
  49 + ExecutionEntity parent = (ExecutionEntity) execution.getParent();
  50 + String formField = parent.getVariable(startUserSetting.getFormField(), String.class);
  51 + // 1.1 当表单值为空时
  52 + if (StrUtil.isEmpty(formField)) {
  53 + // 1.1.1 来自主流程发起人
  54 + if (startUserSetting.getEmptyHandleType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())){
  55 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
  56 + return;
  57 + }
  58 + // 1.1.2 来自子流程管理员
  59 + if (startUserSetting.getEmptyHandleType().equals(BpmChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN.getType())){
  60 + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
  61 + List<Long> managerUserIds = processDefinition.getManagerUserIds();
  62 + FlowableUtils.setAuthenticatedUserId(Convert.toLong(managerUserIds.get(0)));
  63 + return;
  64 + }
  65 + // 1.1.3 来自主流程管理员
  66 + if (startUserSetting.getEmptyHandleType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())){
  67 + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(parent.getProcessDefinitionId());
  68 + List<Long> managerUserIds = processDefinition.getManagerUserIds();
  69 + FlowableUtils.setAuthenticatedUserId(Convert.toLong(managerUserIds.get(0)));
  70 + return;
  71 + }
  72 + }
  73 + // 1.2 使用表单值,并兜底字符串转Long失败时使用主流程发起人
  74 + try {
  75 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(formField));
  76 + } catch (Exception e) {
  77 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
  78 + }
  79 + }
  80 + // 2. 当发起人来源为主流程发起人时,并兜底startUserSetting为空时
  81 + if (startUserSetting == null ||
  82 + startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER.getType())) {
  83 + ExecutionEntity parent = (ExecutionEntity) execution.getParent();
  84 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
  85 + }
  86 + }
  87 +
  88 +}
0 \ No newline at end of file 89 \ No newline at end of file