diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java new file mode 100644 index 0000000000..f3cac8ba92 --- /dev/null +++ b/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 { + + 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; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 76a44fe3e1..c0d2a06672 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/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 = "{}") private TimeoutSetting timeoutSetting; + @Schema(description = "多实例设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + private MultiInstanceSetting multiInstanceSetting; + @Schema(description = "子流程发起人配置") @Data @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; + + } + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index d239dbe3ff..84c89fc97b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/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; import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 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.util.BpmnModelUtils; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; 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.impl.bpmn.behavior.AbstractBpmnActivityBehavior; import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import java.util.List; import java.util.Set; /** @@ -42,27 +48,42 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav */ @Override 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 assigneeUserIds = (Set) 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 assigneeUserIds = (Set) 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); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index b3a3a24f80..a3ba19c70e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/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.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.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; 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.impl.bpmn.behavior.AbstractBpmnActivityBehavior; import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import java.util.List; import java.util.Set; /** @@ -35,28 +41,43 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB */ @Override 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()); - // 第二步,获取任务的所有处理人 - // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人 - @SuppressWarnings("unchecked") - Set assigneeUserIds = (Set) 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.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人 + @SuppressWarnings("unchecked") + Set assigneeUserIds = (Set) 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); } - 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); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index cb857b7390..e416428c4e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/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"; + /** + * BPMN Child Process 的扩展属性,用于标记多实例来源类型 + */ + String CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = "childProcessMultiInstanceSourceType"; + /** * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 */ diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 27d923bdeb..83b9b44662 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/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)); } + /** + * 解析子流程多实例来源类型 + * + * @see BpmChildProcessMultiInstanceSourceTypeEnum + * @param element 任务节点 + * @return 多实例来源类型 + */ + public static Integer parseMultiInstanceSourceType(FlowElement element) { + return NumberUtils.parseInt(parseExtensionElement(element, BpmnModelConstants.CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE)); + } + /** * 添加任务拒绝处理元素 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index def9e8aac5..9a32f4cf1f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/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.enums.definition.*; 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.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate; @@ -84,7 +83,7 @@ public class SimpleModelUtils { traverseNodeToBuildFlowNode(startNode, process); // 3. 构建并添加节点之间的连线 Sequence Flow - EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); + EndEvent endEvent = getEndEvent(bpmnModel); traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); // 4. 自动布局 @@ -364,7 +363,7 @@ public class SimpleModelUtils { 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); // 添加表单字段权限属性元素 @@ -415,25 +414,17 @@ public class SimpleModelUtils { */ private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, 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()) && 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()); return boundaryEvent; } @@ -516,7 +507,7 @@ public class SimpleModelUtils { BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); // 添加审批方式的扩展属性 - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod); + addExtensionElement(userTask, USER_TASK_APPROVE_METHOD, approveMethod); if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { // 随机审批,不需要设置多实例属性 return; @@ -723,21 +714,14 @@ public class SimpleModelUtils { // 2. 添加接收任务的 Timer Boundary Event 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())) { - 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())) { - 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); } return flowElements; @@ -869,23 +853,35 @@ public class SimpleModelUtils { callActivity.setExecutionListeners(executionListeners); // 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())) { - 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())) { - 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); } + // 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); flowElements.add(callActivity); @@ -903,6 +899,33 @@ public class SimpleModelUtils { 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 流程预测相关的方法 ========== public static List simulateProcess(BpmSimpleModelNodeVO rootNode, Map variables) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index ed128f186e..c3566c8e95 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/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 属性 for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); - // TODO @lesan:这里为啥 continue 哈? @芋艿:子流程的 activity 中 task 是null 下面的方法会报错;TODO @lesan:写个注释??? + // ChildProcess 子流程节点仅存在于 activity 中,并且没有自身的 task ,需要跳过执行 if (task == null) { continue; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java index b454d33335..40313a9663 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java +++ b/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; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import jakarta.annotation.Resource; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; 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 java.util.List; @@ -37,30 +39,32 @@ public class BpmCallActivityListener implements ExecutionListener { @Resource private BpmProcessDefinitionService processDefinitionService; + @Resource + private BpmProcessInstanceService processInstanceService; + @Override public void notify(DelegateExecution execution) { String expressionText = listenerConfig.getExpressionText(); Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText); BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject( expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class); + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getRootProcessInstanceId()); // 1. 当发起人来源为主流程发起人时,并兜底 startUserSetting 为空时 if (startUserSetting == null || 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; } // 2. 当发起人来源为表单时 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 当表单值为空时 if (StrUtil.isEmpty(formFieldValue)) { // 2.1.1 来自主流程发起人 if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())) { - FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId())); + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId())); return; } // 2.1.2 来自子流程管理员 @@ -72,7 +76,7 @@ public class BpmCallActivityListener implements ExecutionListener { } // 2.1.3 来自主流程管理员 if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())) { - BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(parent.getProcessDefinitionId()); + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); List managerUserIds = processDefinition.getManagerUserIds(); FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0)); return; @@ -84,7 +88,7 @@ public class BpmCallActivityListener implements ExecutionListener { } catch (Exception e) { log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]", DELEGATE_EXPRESSION, formFieldValue); - FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId())); + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId())); } } }