浏览代码

!1262 feat: 子流程-多实例
Merge pull request !1262 from Lesan/feature/bpm-子流程

芋道源码 5 月之前
父节点
当前提交
35f0d689f1

+ 36 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * BPM 子流程多实例来源类型枚举
+ *
+ * @author Lesan
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmChildProcessMultiInstanceSourceTypeEnum implements ArrayValuable<Integer> {
+
+    FIXED_QUANTITY(1, "固定数量"),
+    DIGITAL_FORM(2, "数字表单"),
+    MULTI_FORM(3, "多项表单");
+
+    private final Integer type;
+    private final String name;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessMultiInstanceSourceTypeEnum::getType).toArray(Integer[]::new);
+
+    public static BpmChildProcessMultiInstanceSourceTypeEnum typeOf(Integer type) {
+        return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+    }
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+}

+ 31 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java

@@ -452,6 +452,9 @@ public class BpmSimpleModelNodeVO {
         @Schema(description = "超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
         @Schema(description = "超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
         private TimeoutSetting timeoutSetting;
         private TimeoutSetting timeoutSetting;
 
 
+        @Schema(description = "多实例设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+        private MultiInstanceSetting multiInstanceSetting;
+
         @Schema(description = "子流程发起人配置")
         @Schema(description = "子流程发起人配置")
         @Data
         @Data
         @Valid
         @Valid
@@ -490,5 +493,33 @@ public class BpmSimpleModelNodeVO {
 
 
         }
         }
 
 
+        @Schema(description = "多实例设置")
+        @Data
+        @Valid
+        public static class MultiInstanceSetting {
+
+            @Schema(description = "是否开启多实例", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+            @NotNull(message = "是否开启多实例不能为空")
+            private Boolean enable;
+
+            @Schema(description = "是否串行", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+            @NotNull(message = "是否串行不能为空")
+            private Boolean sequential;
+
+            @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+            @NotNull(message = "完成比例不能为空")
+            private Integer completeRatio;
+
+            @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+            @NotNull(message = "多实例来源类型不能为空")
+            @InEnum(BpmChildProcessMultiInstanceSourceTypeEnum.class)
+            private Integer sourceType;
+
+            @Schema(description = "多实例来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+            @NotNull(message = "多实例来源不能为空")
+            private String source;
+
+        }
+
     }
     }
 }
 }

+ 40 - 19
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java

@@ -1,15 +1,21 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
-import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import lombok.Setter;
 import lombok.Setter;
 import org.flowable.bpmn.model.Activity;
 import org.flowable.bpmn.model.Activity;
+import org.flowable.bpmn.model.CallActivity;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
 import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
 import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
 
 
+import java.util.List;
 import java.util.Set;
 import java.util.Set;
 
 
 /**
 /**
@@ -42,27 +48,42 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
      */
      */
     @Override
     @Override
     protected int resolveNrOfInstances(DelegateExecution execution) {
     protected int resolveNrOfInstances(DelegateExecution execution) {
-        // 第一步,设置 collectionVariable 和 CollectionVariable
-        // 从  execution.getVariable() 读取所有任务处理人的 key
-        super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
-        super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
-        // 从 execution.getVariable() 读取当前所有任务处理的人的 key
-        super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
+        if (execution.getCurrentFlowElement() instanceof UserTask) {
+            // 第一步,设置 collectionVariable 和 CollectionVariable
+            // 从  execution.getVariable() 读取所有任务处理人的 key
+            super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
+            super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
+            // 从 execution.getVariable() 读取当前所有任务处理的人的 key
+            super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
 
 
-        // 第二步,获取任务的所有处理人
-        @SuppressWarnings("unchecked")
-        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
-        if (assigneeUserIds == null) {
-            assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
-            if (CollUtil.isEmpty(assigneeUserIds)) {
-                // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
-                // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
-                // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
-                assigneeUserIds = SetUtils.asSet((Long) null);
+            // 第二步,获取任务的所有处理人
+            @SuppressWarnings("unchecked")
+            Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
+            if (assigneeUserIds == null) {
+                assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
+                if (CollUtil.isEmpty(assigneeUserIds)) {
+                    // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
+                    // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
+                    // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
+                    assigneeUserIds = SetUtils.asSet((Long) null);
+                }
+                execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
             }
             }
-            execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
+            return assigneeUserIds.size();
         }
         }
-        return assigneeUserIds.size();
+
+        if (execution.getCurrentFlowElement() instanceof CallActivity) {
+            FlowElement flowElement = execution.getCurrentFlowElement();
+            Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
+            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.DIGITAL_FORM.getType())) {
+                return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
+            }
+            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTI_FORM.getType())) {
+                return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
+            }
+        }
+
+        return super.resolveNrOfInstances(execution);
     }
     }
 
 
 }
 }

+ 41 - 20
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java

@@ -2,14 +2,20 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
 
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import lombok.Setter;
 import lombok.Setter;
 import org.flowable.bpmn.model.Activity;
 import org.flowable.bpmn.model.Activity;
+import org.flowable.bpmn.model.CallActivity;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
 import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
 import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
 
 
+import java.util.List;
 import java.util.Set;
 import java.util.Set;
 
 
 /**
 /**
@@ -35,28 +41,43 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
      */
      */
     @Override
     @Override
     protected int resolveNrOfInstances(DelegateExecution execution) {
     protected int resolveNrOfInstances(DelegateExecution execution) {
-        // 第一步,设置 collectionVariable 和 CollectionVariable
-        // 从  execution.getVariable() 读取所有任务处理人的 key
-        super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
-        super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
-        // 从 execution.getVariable() 读取当前所有任务处理的人的 key
-        super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
-
-        // 第二步,获取任务的所有处理人
-        // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
-        @SuppressWarnings("unchecked")
-        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
-        if (assigneeUserIds == null) {
-            assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
-            if (CollUtil.isEmpty(assigneeUserIds)) {
-                // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
-                // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
-                // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
-                assigneeUserIds = SetUtils.asSet((Long) null);
+        if (execution.getCurrentFlowElement() instanceof UserTask) {
+            // 第一步,设置 collectionVariable 和 CollectionVariable
+            // 从  execution.getVariable() 读取所有任务处理人的 key
+            super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
+            super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
+            // 从 execution.getVariable() 读取当前所有任务处理的人的 key
+            super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
+
+            // 第二步,获取任务的所有处理人
+            // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
+            @SuppressWarnings("unchecked")
+            Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
+            if (assigneeUserIds == null) {
+                assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
+                if (CollUtil.isEmpty(assigneeUserIds)) {
+                    // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
+                    // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
+                    // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
+                    assigneeUserIds = SetUtils.asSet((Long) null);
+                }
+                execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
+            }
+            return assigneeUserIds.size();
+        }
+
+        if (execution.getCurrentFlowElement() instanceof CallActivity) {
+            FlowElement flowElement = execution.getCurrentFlowElement();
+            Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
+            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.DIGITAL_FORM.getType())) {
+                return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
+            }
+            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTI_FORM.getType())) {
+                return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
             }
             }
-            execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
         }
         }
-        return assigneeUserIds.size();
+
+        return super.resolveNrOfInstances(execution);
     }
     }
 
 
 }
 }

+ 5 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java

@@ -68,6 +68,11 @@ public interface BpmnModelConstants {
      */
      */
     String USER_TASK_APPROVE_METHOD = "approveMethod";
     String USER_TASK_APPROVE_METHOD = "approveMethod";
 
 
+    /**
+     * BPMN Child Process 的扩展属性,用于标记多实例来源类型
+     */
+    String CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = "childProcessMultiInstanceSourceType";
+
     /**
     /**
      * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
      * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
      */
      */

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

@@ -158,6 +158,17 @@ public class BpmnModelUtils {
         return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
         return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
     }
     }
 
 
+    /**
+     * 解析子流程多实例来源类型
+     *
+     * @see BpmChildProcessMultiInstanceSourceTypeEnum
+     * @param element 任务节点
+     * @return 多实例来源类型
+     */
+    public static Integer parseMultiInstanceSourceType(FlowElement element) {
+        return NumberUtils.parseInt(parseExtensionElement(element, BpmnModelConstants.CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE));
+    }
+
     /**
     /**
      * 添加任务拒绝处理元素
      * 添加任务拒绝处理元素
      *
      *

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

@@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.B
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
 import cn.iocoder.yudao.module.bpm.enums.definition.*;
 import cn.iocoder.yudao.module.bpm.enums.definition.*;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 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.enums.BpmnVariableConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate;
@@ -84,7 +83,7 @@ public class SimpleModelUtils {
         traverseNodeToBuildFlowNode(startNode, process);
         traverseNodeToBuildFlowNode(startNode, process);
 
 
         // 3. 构建并添加节点之间的连线 Sequence Flow
         // 3. 构建并添加节点之间的连线 Sequence Flow
-        EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
+        EndEvent endEvent = getEndEvent(bpmnModel);
         traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
         traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
 
 
         // 4. 自动布局
         // 4. 自动布局
@@ -364,7 +363,7 @@ public class SimpleModelUtils {
             userTask.setName(node.getName());
             userTask.setName(node.getName());
 
 
             // 人工审批
             // 人工审批
-            addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
+            addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
             // 候选人策略为发起人自己
             // 候选人策略为发起人自己
             addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
             addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
             // 添加表单字段权限属性元素
             // 添加表单字段权限属性元素
@@ -415,25 +414,17 @@ public class SimpleModelUtils {
          */
          */
         private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
         private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
                                                                 BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) {
                                                                 BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) {
-            // 1.1 定时器边界事件
-            // TODO @lesan:一些 BoundaryEvent timeout 的,可以做一些基础的设置么?
-            BoundaryEvent boundaryEvent = new BoundaryEvent();
-            boundaryEvent.setId("Event-" + IdUtil.fastUUID());
-            boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
-            boundaryEvent.setAttachedToRef(userTask);
-            // 1.2 定义超时时间、最大提醒次数
-            TimerEventDefinition eventDefinition = new TimerEventDefinition();
-            eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
+            // 1. 创建 Timeout Boundary Event
+            String timeCycle = null;
             if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
             if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
                     timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
                     timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
-                eventDefinition.setTimeCycle(String.format("R%d/%s",
-                        timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
+                timeCycle = String.format("R%d/%s",
+                        timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration());
             }
             }
-            boundaryEvent.addEventDefinition(eventDefinition);
+            BoundaryEvent boundaryEvent = buildTimeoutBoundaryEvent(userTask, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT.getType(),
+                    timeoutHandler.getTimeDuration(), timeCycle, null);
 
 
-            // 2.1 添加定时器边界事件类型
-            addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT.getType());
-            // 2.2 添加超时执行动作元素
+            // 2 添加超时执行动作元素
             addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
             addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
             return boundaryEvent;
             return boundaryEvent;
         }
         }
@@ -516,7 +507,7 @@ public class SimpleModelUtils {
             BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
             BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
             Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
             Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
             // 添加审批方式的扩展属性
             // 添加审批方式的扩展属性
-            addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod);
+            addExtensionElement(userTask, USER_TASK_APPROVE_METHOD, approveMethod);
             if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
             if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
                 // 随机审批,不需要设置多实例属性
                 // 随机审批,不需要设置多实例属性
                 return;
                 return;
@@ -723,21 +714,14 @@ public class SimpleModelUtils {
 
 
             // 2. 添加接收任务的 Timer Boundary Event
             // 2. 添加接收任务的 Timer Boundary Event
             if (node.getDelaySetting() != null) {
             if (node.getDelaySetting() != null) {
-                // 2.1 定时器边界事件
-                // TODO @lesan:一些 BoundaryEvent timeout 的,可以做一些基础的设置么?
-                BoundaryEvent boundaryEvent = new BoundaryEvent();
-                boundaryEvent.setId("Event-" + IdUtil.fastUUID());
-                boundaryEvent.setCancelActivity(false);
-                boundaryEvent.setAttachedToRef(receiveTask);
-                // 2.2 定义超时时间
-                TimerEventDefinition eventDefinition = new TimerEventDefinition();
+                BoundaryEvent boundaryEvent = null;
                 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
                 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
-                    eventDefinition.setTimeDuration(node.getDelaySetting().getDelayTime());
+                    boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
+                            node.getDelaySetting().getDelayTime(), null, null);
                 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
                 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
-                    eventDefinition.setTimeDate(node.getDelaySetting().getDelayTime());
+                    boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
+                            null, null, node.getDelaySetting().getDelayTime());
                 }
                 }
-                boundaryEvent.addEventDefinition(eventDefinition);
-                addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType());
                 flowElements.add(boundaryEvent);
                 flowElements.add(boundaryEvent);
             }
             }
             return flowElements;
             return flowElements;
@@ -869,23 +853,35 @@ public class SimpleModelUtils {
             callActivity.setExecutionListeners(executionListeners);
             callActivity.setExecutionListeners(executionListeners);
 
 
             // 7. 超时设置
             // 7. 超时设置
-            if (childProcessSetting.getTimeoutSetting() != null) {
-                // TODO @lesan:一些 BoundaryEvent timeout 的,可以做一些基础的设置么?
-                BoundaryEvent boundaryEvent = new BoundaryEvent();
-                boundaryEvent.setId("Event-" + IdUtil.fastUUID());
-                boundaryEvent.setCancelActivity(false);
-                boundaryEvent.setAttachedToRef(callActivity);
-                TimerEventDefinition eventDefinition = new TimerEventDefinition();
+            if (childProcessSetting.getTimeoutSetting() != null && Boolean.TRUE.equals(childProcessSetting.getTimeoutSetting().getEnable())) {
+                BoundaryEvent boundaryEvent = null;
                 if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
                 if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
-                    eventDefinition.setTimeDuration(childProcessSetting.getTimeoutSetting().getTimeExpression());
+                    boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
+                            childProcessSetting.getTimeoutSetting().getTimeExpression(), null, null);
                 } else if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
                 } else if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
-                    eventDefinition.setTimeDate(childProcessSetting.getTimeoutSetting().getTimeExpression());
+                    boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT.getType(),
+                            null, null, childProcessSetting.getTimeoutSetting().getTimeExpression());
                 }
                 }
-                boundaryEvent.addEventDefinition(eventDefinition);
-                addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT.getType());
                 flowElements.add(boundaryEvent);
                 flowElements.add(boundaryEvent);
             }
             }
 
 
+            // 8. 多实例
+            if (childProcessSetting.getMultiInstanceSetting() != null && Boolean.TRUE.equals(childProcessSetting.getMultiInstanceSetting().getEnable())) {
+                MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
+                multiInstanceCharacteristics.setSequential(childProcessSetting.getMultiInstanceSetting().getSequential());
+                if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY.getType())) {
+                    multiInstanceCharacteristics.setLoopCardinality(childProcessSetting.getMultiInstanceSetting().getSource());
+                }
+                if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.DIGITAL_FORM.getType()) ||
+                        childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTI_FORM.getType())) {
+                    multiInstanceCharacteristics.setInputDataItem(childProcessSetting.getMultiInstanceSetting().getSource());
+                }
+                multiInstanceCharacteristics.setCompletionCondition(String.format("${ nrOfCompletedInstances/nrOfInstances >= %s}",
+                        String.format("%.2f", childProcessSetting.getMultiInstanceSetting().getCompleteRatio() / 100D)));
+                callActivity.setLoopCharacteristics(multiInstanceCharacteristics);
+                addExtensionElement(callActivity, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, childProcessSetting.getMultiInstanceSetting().getSourceType());
+            }
+
             // 添加节点类型
             // 添加节点类型
             addNodeType(node.getType(), callActivity);
             addNodeType(node.getType(), callActivity);
             flowElements.add(callActivity);
             flowElements.add(callActivity);
@@ -903,6 +899,33 @@ public class SimpleModelUtils {
         return id + "_join";
         return id + "_join";
     }
     }
 
 
+    private static BoundaryEvent buildTimeoutBoundaryEvent(Activity attachedToRef, Integer type,
+                                                           String timeDuration,
+                                                           String timeCycle,
+                                                           String timeDate) {
+        // 1.1 定时器边界事件
+        BoundaryEvent boundaryEvent = new BoundaryEvent();
+        boundaryEvent.setId("Event-" + IdUtil.fastUUID());
+        boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
+        boundaryEvent.setAttachedToRef(attachedToRef);
+        // 1.2 定义超时时间表达式
+        TimerEventDefinition eventDefinition = new TimerEventDefinition();
+        if (ObjUtil.isNotNull(timeDuration)) {
+            eventDefinition.setTimeDuration(timeDuration);
+        }
+        if (ObjUtil.isNotNull(timeDuration)) {
+            eventDefinition.setTimeCycle(timeCycle);
+        }
+        if (ObjUtil.isNotNull(timeDate)) {
+            eventDefinition.setTimeDate(timeDate);
+        }
+        boundaryEvent.addEventDefinition(eventDefinition);
+
+        // 2.1 添加定时器边界事件类型
+        addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, type);
+        return boundaryEvent;
+    }
+
     // ========== SIMPLE 流程预测相关的方法 ==========
     // ========== SIMPLE 流程预测相关的方法 ==========
 
 
     public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
     public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {

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

@@ -414,7 +414,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
             // 处理每个任务的 tasks 属性
             // 处理每个任务的 tasks 属性
             for (HistoricActivityInstance activity : taskActivities) {
             for (HistoricActivityInstance activity : taskActivities) {
                 HistoricTaskInstance task = taskMap.get(activity.getTaskId());
                 HistoricTaskInstance task = taskMap.get(activity.getTaskId());
-                // TODO @lesan:这里为啥 continue 哈? @芋艿:子流程的 activity 中 task 是null 下面的方法会报错;TODO @lesan:写个注释???
+                // ChildProcess 子流程节点仅存在于 activity 中,并且没有自身的 task ,需要跳过执行
                 if (task == null) {
                 if (task == null) {
                     continue;
                     continue;
                 }
                 }

+ 12 - 8
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.bpm.service.task.listener;
 package cn.iocoder.yudao.module.bpm.service.task.listener;
 
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
@@ -9,13 +10,14 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserEmpt
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserTypeEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
 import jakarta.annotation.Resource;
 import jakarta.annotation.Resource;
 import lombok.Setter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.delegate.ExecutionListener;
 import org.flowable.engine.delegate.ExecutionListener;
 import org.flowable.engine.impl.el.FixedValue;
 import org.flowable.engine.impl.el.FixedValue;
-import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
+import org.flowable.engine.runtime.ProcessInstance;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
 
 
 import java.util.List;
 import java.util.List;
@@ -37,30 +39,32 @@ public class BpmCallActivityListener implements ExecutionListener {
     @Resource
     @Resource
     private BpmProcessDefinitionService processDefinitionService;
     private BpmProcessDefinitionService processDefinitionService;
 
 
+    @Resource
+    private BpmProcessInstanceService processInstanceService;
+
     @Override
     @Override
     public void notify(DelegateExecution execution) {
     public void notify(DelegateExecution execution) {
         String expressionText = listenerConfig.getExpressionText();
         String expressionText = listenerConfig.getExpressionText();
         Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText);
         Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText);
         BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject(
         BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject(
                 expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class);
                 expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class);
+        ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getRootProcessInstanceId());
 
 
         // 1. 当发起人来源为主流程发起人时,并兜底 startUserSetting 为空时
         // 1. 当发起人来源为主流程发起人时,并兜底 startUserSetting 为空时
         if (startUserSetting == null
         if (startUserSetting == null
                 || startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER.getType())) {
                 || startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER.getType())) {
-            ExecutionEntity parent = (ExecutionEntity) execution.getParent();
-            FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
+            FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
             return;
             return;
         }
         }
 
 
         // 2. 当发起人来源为表单时
         // 2. 当发起人来源为表单时
         if (startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.FROM_FORM.getType())) {
         if (startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.FROM_FORM.getType())) {
-            ExecutionEntity parent = (ExecutionEntity) execution.getParent();
-            String formFieldValue = parent.getVariable(startUserSetting.getFormField(), String.class);
+            String formFieldValue = MapUtil.getStr(processInstance.getProcessVariables(), startUserSetting.getFormField());
             // 2.1 当表单值为空时
             // 2.1 当表单值为空时
             if (StrUtil.isEmpty(formFieldValue)) {
             if (StrUtil.isEmpty(formFieldValue)) {
                 // 2.1.1 来自主流程发起人
                 // 2.1.1 来自主流程发起人
                 if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())) {
                 if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())) {
-                    FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
+                    FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
                     return;
                     return;
                 }
                 }
                 // 2.1.2 来自子流程管理员
                 // 2.1.2 来自子流程管理员
@@ -72,7 +76,7 @@ public class BpmCallActivityListener implements ExecutionListener {
                 }
                 }
                 // 2.1.3 来自主流程管理员
                 // 2.1.3 来自主流程管理员
                 if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())) {
                 if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())) {
-                    BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(parent.getProcessDefinitionId());
+                    BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
                     List<Long> managerUserIds = processDefinition.getManagerUserIds();
                     List<Long> managerUserIds = processDefinition.getManagerUserIds();
                     FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0));
                     FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0));
                     return;
                     return;
@@ -84,7 +88,7 @@ public class BpmCallActivityListener implements ExecutionListener {
             } catch (Exception e) {
             } catch (Exception e) {
                 log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]",
                 log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]",
                         DELEGATE_EXPRESSION, formFieldValue);
                         DELEGATE_EXPRESSION, formFieldValue);
-                FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
+                FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
             }
             }
         }
         }
     }
     }