diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index 57f4d393f3..5ba75c0524 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -59,7 +59,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav // 第二步,获取任务的所有处理人 @SuppressWarnings("unchecked") - Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class); + Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class); if (assigneeUserIds == null) { assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); if (CollUtil.isEmpty(assigneeUserIds)) { diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/DeleteExecutionCommand.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/DeleteExecutionCommand.java new file mode 100644 index 0000000000..b26f24b75f --- /dev/null +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/DeleteExecutionCommand.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; + +/** + * @Author whc + * @Date 2025/5/21 11:30 + */ +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; + +public class DeleteExecutionCommand implements Command { + + private final String executionId; + private final String reason; + + public DeleteExecutionCommand(String executionId, String reason) { + this.executionId = executionId; + this.reason = reason; + } + + @Override + public Void execute(CommandContext commandContext) { + ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext); + ExecutionEntity execution = executionEntityManager.findById(executionId); + if (execution != null) { + executionEntityManager.deleteExecutionAndRelatedData(execution, reason, false); + } + return null; + } +} + diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 1cccf18f04..adc5741c50 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -755,6 +755,48 @@ public class BpmnModelUtils { return userTaskList; } + /** + * 获取当前元素后可能执行到的所有userTask + * + * @param startElement 起始元素 + * @param bpmnModel 流程模型 + * @return 当前元素后可能执行到的所有userTask,不包含自身 + */ + public static Set findReachableUserTasks(FlowElement startElement, BpmnModel bpmnModel) { + Set userTasks = new HashSet<>(); + Set visited = new HashSet<>(); + + if (startElement instanceof FlowNode) { + List outgoing = ((FlowNode) startElement).getOutgoingFlows(); + for (SequenceFlow flow : outgoing) { + FlowElement target = bpmnModel.getMainProcess().getFlowElement(flow.getTargetRef()); + dfs(target, bpmnModel, userTasks, visited); + } + } + return userTasks; + } + + private static void dfs(FlowElement current, BpmnModel model, Set userTasks, Set visited) { + if (current == null || visited.contains(current.getId())) { + return; + } + + visited.add(current.getId()); + + if (current instanceof UserTask) { + userTasks.add((UserTask) current); + } + + if (current instanceof FlowNode) { + List outgoingFlows = ((FlowNode) current).getOutgoingFlows(); + for (SequenceFlow flow : outgoingFlows) { + String targetRef = flow.getTargetRef(); + FlowElement targetElement = model.getMainProcess().getFlowElement(targetRef); + dfs(targetElement, model, userTasks, visited); + } + } + + } // ========== BPMN 流程预测相关的方法 ========== /** diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 1164f4da72..163d10bfa2 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.DeleteExecutionCommand; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; @@ -646,6 +647,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (approveUserSelectAssignees == null) { approveUserSelectAssignees = new HashMap<>(); } + // 当多实例任务根据'审批人自选'策略多次选择时,避免审批人被后一次选择覆盖 + List nodeHadAssignees = approveUserSelectAssignees.get(nextFlowNode.getId()); + if (nodeHadAssignees != null){ + assignees = Stream.concat(nodeHadAssignees.stream(), assignees.stream()) + .distinct() + .toList(); + } approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); Map> existingApproveUserSelectAssignees = (Map>) variables.get( BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); @@ -906,10 +914,47 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 4. 执行驳回 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因: // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944 + // 选择一个主 execution + String mainExecutionId = runExecutionIds.get(0); + // 删除其他 execution 解决多个execution会在移动的目标节点创建出多个任务的问题 + for (int i = 1; i < runExecutionIds.size(); i++) { + String redundantId = runExecutionIds.get(i); + managementService.executeCommand(new DeleteExecutionCommand(redundantId, "退回清理冗余路径")); + } runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) - .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) + .moveExecutionToActivityId(mainExecutionId, reqVO.getTargetTaskDefinitionKey()) .changeState(); + // 5. 清除'审批人自选'策略相关数据 + // 移除退回后不会自动通过的节点的审批人数据 + // 根据自动去重设置执行相关操作 + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(currentTask.getProcessDefinitionId()); + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(currentTask.getProcessDefinitionId()); + ProcessInstance processInstance = processInstanceService.getProcessInstance(currentTask.getProcessInstanceId()); + if (BpmAutoApproveTypeEnum.NONE.getType().equals(processDefinitionInfo.getAutoApprovalType())) { + // 不自动通过,清除 targetElement 后所有节点的审批人数据 + Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance); + if (ObjectUtil.isNotEmpty(approveUserSelectAssignees)) { + Set reachableUserTasks = findReachableUserTasks(targetElement, bpmnModel); + for (UserTask reachableUserTask : reachableUserTasks) { + approveUserSelectAssignees.remove(reachableUserTask.getId()); + } + runtimeService.setVariable(processInstance.getProcessInstanceId(),BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,approveUserSelectAssignees); + } + } else if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) { + // TODO 优化:去重规则为连续审批的节点自动通过,看退回到目标节点后会自动通过走到哪里,将走到的位置之后的节点自选审批人数据清空 + // 暂且清除 targetElement 后所有节点的审批人数据 + // 自动通过时若候选人策略为'审批人自选',会因为缺失自选审批人报错,需要重新选择审批人手动审批 + Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance); + if (ObjectUtil.isNotEmpty(approveUserSelectAssignees)) { + Set reachableUserTasks = findReachableUserTasks(targetElement, bpmnModel); + for (UserTask reachableUserTask : reachableUserTasks) { + approveUserSelectAssignees.remove(reachableUserTask.getId()); + } + runtimeService.setVariable(processInstance.getProcessInstanceId(),BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,approveUserSelectAssignees); + } + } + // 若为自动通过,还会走到当前节点,不清除节点自选审批人数据 } @Override