mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-29 01:17:27 +08:00 
			
		
		
		
	feat: 工作流-》流程实例的任务节点回退
This commit is contained in:
		| @ -0,0 +1,274 @@ | ||||
| package cn.iocoder.yudao.framework.flowable.core.util; | ||||
|  | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import org.flowable.bpmn.converter.BpmnXMLConverter; | ||||
| import org.flowable.bpmn.model.Process; | ||||
| import org.flowable.bpmn.model.*; | ||||
| import org.flowable.common.engine.impl.util.io.StringStreamSource; | ||||
|  | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * 流程模型转操作工具类 | ||||
|  */ | ||||
| public class ModelUtils { | ||||
|  | ||||
|     /** | ||||
|      * 根据节点,获取入口连线 | ||||
|      * | ||||
|      * @param source 起始节点 | ||||
|      * @return 入口连线列表 | ||||
|      */ | ||||
|     public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) { | ||||
|         List<SequenceFlow> sequenceFlows = new ArrayList<>(); | ||||
|         if (source instanceof FlowNode) { | ||||
|             sequenceFlows = ((FlowNode) source).getIncomingFlows(); | ||||
|         } | ||||
|         return sequenceFlows; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 根据节点,获取出口连线 | ||||
|      * | ||||
|      * @param source 起始节点 | ||||
|      * @return 出口连线列表 | ||||
|      */ | ||||
|     public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) { | ||||
|         List<SequenceFlow> sequenceFlows = new ArrayList<>(); | ||||
|         if (source instanceof FlowNode) { | ||||
|             sequenceFlows = ((FlowNode) source).getOutgoingFlows(); | ||||
|         } | ||||
|         return sequenceFlows; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 获取流程元素信息 | ||||
|      * | ||||
|      * @param model         bpmnModel对象 | ||||
|      * @param flowElementId 元素ID | ||||
|      * @return 元素信息 | ||||
|      */ | ||||
|     public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { | ||||
|         Process process = model.getMainProcess(); | ||||
|         return process.getFlowElement(flowElementId); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 找到 source 节点之前的所有用户任务节点 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @param userTaskList    已找到的用户任务节点 | ||||
|      * @return | ||||
|      */ | ||||
|     public static List<UserTask> getPreUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||
|         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||||
|         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||||
|  | ||||
|         // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | ||||
|         if (source instanceof StartEvent && source.getSubProcess() != null) { | ||||
|             userTaskList = getPreUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|  | ||||
|         // 根据类型,获取入口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | ||||
|  | ||||
|         if (sequenceFlows != null) { | ||||
|             // 循环找到目标元素 | ||||
|             for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|                 // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|                 if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 添加已经走过的连线 | ||||
|                 hasSequenceFlow.add(sequenceFlow.getId()); | ||||
|                 // 类型为用户节点,则新增父级节点 | ||||
|                 if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { | ||||
|                     userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); | ||||
|                 } | ||||
|                 // 类型为子流程,则添加子流程开始节点出口处相连的节点 | ||||
|                 if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { | ||||
|                     // 获取子流程用户任务节点 | ||||
|                     List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); | ||||
|                     // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||||
|                     if (childUserTaskList != null && childUserTaskList.size() > 0) { | ||||
|                         userTaskList.addAll(childUserTaskList); | ||||
|                     } | ||||
|                 } | ||||
|                 // 继续迭代 | ||||
|                 userTaskList = getPreUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); | ||||
|             } | ||||
|         } | ||||
|         return userTaskList; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 迭代获取子流程用户任务节点 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @param userTaskList    需要撤回的用户任务列表 | ||||
|      * @return | ||||
|      */ | ||||
|     public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||
|         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||||
|         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||||
|  | ||||
|         // 根据类型,获取出口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||||
|  | ||||
|         if (sequenceFlows != null) { | ||||
|             // 循环找到目标元素 | ||||
|             for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|                 // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|                 if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 添加已经走过的连线 | ||||
|                 hasSequenceFlow.add(sequenceFlow.getId()); | ||||
|                 // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | ||||
|                 if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { | ||||
|                     userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | ||||
|                 if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | ||||
|                     List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); | ||||
|                     // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||||
|                     if (childUserTaskList != null && childUserTaskList.size() > 0) { | ||||
|                         userTaskList.addAll(childUserTaskList); | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|                 // 继续迭代 | ||||
|                 userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); | ||||
|             } | ||||
|         } | ||||
|         return userTaskList; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 | ||||
|      * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param target          目标节点 | ||||
|      * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { | ||||
|         visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; | ||||
|         //不能是开始事件和子流程 | ||||
|         if (source instanceof StartEvent && isInEventSubprocess(source)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // 根据类型,获取入口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | ||||
|         if (sequenceFlows != null && sequenceFlows.size() > 0) { | ||||
|             // 循环找到目标元素 | ||||
|             for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|                 // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|                 if (visitedElements.contains(sequenceFlow.getId())) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 添加已经走过的连线 | ||||
|                 visitedElements.add(sequenceFlow.getId()); | ||||
|                 FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); | ||||
|                 // 这条线路存在目标节点,这条线路完成,进入下个线路 | ||||
|                 if (target.getId().equals(sourceFlowElement.getId())) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 如果目标节点为并行网关,则不继续 | ||||
|                 if (sourceFlowElement instanceof ParallelGateway) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 // 否则就继续迭代 | ||||
|                 boolean isSequential = isSequentialReachable(sourceFlowElement, target, visitedElements); | ||||
|                 if (!isSequential) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断当前节点是否属于不同的子流程 | ||||
|      * | ||||
|      * @param flowElement 被判断的节点 | ||||
|      * @return true表示属于子流程 | ||||
|      */ | ||||
|     protected static boolean isInEventSubprocess(FlowElement flowElement) { | ||||
|         FlowElementsContainer flowElementsContainer = flowElement.getParentContainer(); | ||||
|         while (flowElementsContainer != null) { | ||||
|             if (flowElementsContainer instanceof EventSubProcess) { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if (flowElementsContainer instanceof FlowElement) { | ||||
|                 flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); | ||||
|             } else { | ||||
|                 flowElementsContainer = null; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 | ||||
|      * | ||||
|      * @param source          起始节点 | ||||
|      * @param runTaskKeyList  正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 | ||||
|      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||
|      * @param userTaskList    需要撤回的用户任务列表 | ||||
|      * @return | ||||
|      */ | ||||
|     public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||
|         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||||
|         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||||
|  | ||||
|         // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | ||||
|         if (source instanceof StartEvent && source.getSubProcess() != null) { | ||||
|             userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); | ||||
|         } | ||||
|  | ||||
|         // 根据类型,获取出口连线 | ||||
|         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||||
|  | ||||
|         if (sequenceFlows != null) { | ||||
|             // 循环找到目标元素 | ||||
|             for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||
|                 // 如果发现连线重复,说明循环了,跳过这个循环 | ||||
|                 if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 添加已经走过的连线 | ||||
|                 hasSequenceFlow.add(sequenceFlow.getId()); | ||||
|                 // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | ||||
|                 if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { | ||||
|                     userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | ||||
|                 if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | ||||
|                     List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); | ||||
|                     // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||||
|                     if (childUserTaskList != null && childUserTaskList.size() > 0) { | ||||
|                         userTaskList.addAll(childUserTaskList); | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|                 // 继续迭代 | ||||
|                 userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); | ||||
|             } | ||||
|         } | ||||
|         return userTaskList; | ||||
|     } | ||||
| } | ||||
| @ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; | ||||
|  | ||||
| /** | ||||
|  * Bpm 错误码枚举类 | ||||
|  * | ||||
|  * <p> | ||||
|  * bpm 系统,使用 1-009-000-000 段 | ||||
|  */ | ||||
| public interface ErrorCodeConstants { | ||||
| @ -45,7 +45,11 @@ public interface ErrorCodeConstants { | ||||
|     // ========== 流程任务 1-009-005-000 ========== | ||||
|     ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1_009_005_000, "审批任务失败,原因:该任务不处于未审批"); | ||||
|     ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "审批任务失败,原因:该任务的审批人不是你"); | ||||
|  | ||||
|     ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); | ||||
|     ErrorCode TASK_IS_NOT_ACTIVITY = new ErrorCode(1_009_005_003, "当前任务不属于活动状态,不能操作"); | ||||
|     ErrorCode TASK_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_004, " 当前节点相对于目标节点,不属于串行关系,无法回退"); | ||||
|     ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_005, " 目标节点不存在"); | ||||
|     ErrorCode TASK_ROLLBACK_FAIL = new ErrorCode(1_009_005_006, "回退任务失败,选择回退的节点没有需要回滚的任务!请重新选择其他任务节点"); | ||||
|     // ========== 流程任务分配规则 1-009-006-000 ========== | ||||
|     ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则"); | ||||
|     ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在"); | ||||
|  | ||||
| @ -75,4 +75,19 @@ public class BpmTaskController { | ||||
|         return success(true); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/get-return-list") | ||||
|     @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") | ||||
|     @PreAuthorize("@ss.hasPermission('bpm:task:rollback')") | ||||
|     public CommonResult<List<BpmTaskRollbackRespVO>> getReturnList(String taskId) { | ||||
|         return success(taskService.findReturnTaskList(taskId)); | ||||
|     } | ||||
|  | ||||
|     @PutMapping("/rollback") | ||||
|     @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") | ||||
|     @PreAuthorize("@ss.hasPermission('bpm:task:rollback')") | ||||
|     public CommonResult<Boolean> getReturnList(@Valid @RequestBody BpmTaskRollbackReqVO reqVO) { | ||||
|         taskService.taskReturn(reqVO); | ||||
|         return success(true); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,23 @@ | ||||
| package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import javax.validation.constraints.NotEmpty; | ||||
|  | ||||
| @Schema(description = "管理后台 - 回退流程任务的 Request VO") | ||||
| @Data | ||||
| public class BpmTaskRollbackReqVO { | ||||
|  | ||||
|     @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||||
|     @NotEmpty(message = "任务编号不能为空") | ||||
|     private String id; | ||||
|  | ||||
|     @Schema(description = "回退到的任务Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||||
|     @NotEmpty(message = "回退到的任务Key不能为空") | ||||
|     private String targetDefinitionKey; | ||||
|  | ||||
|     @Schema(description = "回退意见") | ||||
|     private String reason; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * | ||||
|  */ | ||||
| @Schema(description = "管理后台 - 流程任务的 可回退的节点 Response VO") | ||||
| @Data | ||||
| public class BpmTaskRollbackRespVO { | ||||
|  | ||||
|     @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") | ||||
|     private String taskDefinitionKey; | ||||
|  | ||||
|     @Schema(description = "任务名词", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批") | ||||
|     private String name; | ||||
| } | ||||
| @ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.util.date.DateUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRollbackRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO; | ||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; | ||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | ||||
| import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | ||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||
| import org.flowable.bpmn.model.FlowElement; | ||||
| import org.flowable.common.engine.impl.db.SuspensionState; | ||||
| import org.flowable.engine.history.HistoricProcessInstance; | ||||
| import org.flowable.engine.runtime.ProcessInstance; | ||||
| @ -55,8 +57,8 @@ public interface BpmTaskConvert { | ||||
|     } | ||||
|  | ||||
|     default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks, | ||||
|         Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap, | ||||
|         Map<Long, AdminUserRespDTO> userMap) { | ||||
|                                                          Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap, | ||||
|                                                          Map<Long, AdminUserRespDTO> userMap) { | ||||
|         return CollectionUtils.convertList(tasks, task -> { | ||||
|             BpmTaskDonePageItemRespVO respVO = convert2(task); | ||||
|             BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId()); | ||||
| @ -82,8 +84,8 @@ public interface BpmTaskConvert { | ||||
|     BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser); | ||||
|  | ||||
|     default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, | ||||
|         Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance, | ||||
|         Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { | ||||
|                                              Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance, | ||||
|                                              Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { | ||||
|         return CollectionUtils.convertList(tasks, task -> { | ||||
|             BpmTaskRespVO respVO = convert3(task); | ||||
|             BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId()); | ||||
| @ -113,29 +115,35 @@ public interface BpmTaskConvert { | ||||
|     void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to); | ||||
|  | ||||
|     @Mappings({@Mapping(source = "processInstance.id", target = "id"), | ||||
|         @Mapping(source = "processInstance.name", target = "name"), | ||||
|         @Mapping(source = "processInstance.startUserId", target = "startUserId"), | ||||
|         @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), | ||||
|         @Mapping(source = "startUser.nickname", target = "startUserNickname")}) | ||||
|             @Mapping(source = "processInstance.name", target = "name"), | ||||
|             @Mapping(source = "processInstance.startUserId", target = "startUserId"), | ||||
|             @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), | ||||
|             @Mapping(source = "startUser.nickname", target = "startUserNickname")}) | ||||
|     BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance, | ||||
|         AdminUserRespDTO startUser); | ||||
|                                                       AdminUserRespDTO startUser); | ||||
|  | ||||
|     default BpmTaskExtDO convert2TaskExt(Task task) { | ||||
|         BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId()) | ||||
|             .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName()) | ||||
|             .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId()); | ||||
|                 .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName()) | ||||
|                 .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId()); | ||||
|         taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime())); | ||||
|         return taskExtDO; | ||||
|     } | ||||
|  | ||||
|     default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, | ||||
|         Task task) { | ||||
|                                                         Task task) { | ||||
|         BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO(); | ||||
|         reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId()) | ||||
|             .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId()) | ||||
|             .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName()) | ||||
|             .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())); | ||||
|                 .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId()) | ||||
|                 .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName()) | ||||
|                 .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())); | ||||
|         return reqDTO; | ||||
|     } | ||||
|  | ||||
|     @Mapping(source = "taskDefinitionKey", target = "id") | ||||
|     default List<BpmTaskRollbackRespVO> convertList(List<FlowElement> elementList) { | ||||
|         return CollectionUtils.convertList(elementList, element -> new BpmTaskRollbackRespVO() | ||||
|                 .setName(element.getName()) | ||||
|                 .setTaskDefinitionKey(element.getId())); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -74,4 +74,12 @@ public interface BpmModelService { | ||||
|      */ | ||||
|     BpmnModel getBpmnModel(String id); | ||||
|  | ||||
|     /** | ||||
|      * 获得流程定义编号对应的 BPMN Model | ||||
|      * | ||||
|      * @param processDefinitionId 流程定义编号 | ||||
|      * @return BPMN Model | ||||
|      */ | ||||
|     BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -233,6 +233,11 @@ public class BpmModelServiceImpl implements BpmModelService { | ||||
|         return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { | ||||
|         return repositoryService.getBpmnModel(processDefinitionId); | ||||
|     } | ||||
|  | ||||
|     private void checkKeyNCName(String key) { | ||||
|         if (!ValidationUtils.isXmlNCName(key)) { | ||||
|             throw exception(MODEL_KEY_VALID); | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| package cn.iocoder.yudao.module.bpm.service.task; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | ||||
| @ -128,4 +127,17 @@ public interface BpmTaskService { | ||||
|      */ | ||||
|     void updateTaskExtAssign(Task task); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前人物的可回退的流程集合 | ||||
|      * @param taskId 当前的任务ID | ||||
|      * @return 可以回退的节点列表 | ||||
|      */ | ||||
|     List<BpmTaskRollbackRespVO> findReturnTaskList(String taskId); | ||||
|  | ||||
|     /** | ||||
|      * 将任务回退到指定的 targetDefinitionKey 位置 | ||||
|      * @param reqVO 回退的任务key和当前所在的任务ID | ||||
|      */ | ||||
|     void taskReturn(BpmTaskRollbackReqVO reqVO); | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -8,21 +8,33 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.date.DateUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.PageUtils; | ||||
| import cn.iocoder.yudao.framework.flowable.core.util.ModelUtils; | ||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | ||||
| import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | ||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; | ||||
| import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper; | ||||
| import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; | ||||
| import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; | ||||
| import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | ||||
| import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; | ||||
| import cn.iocoder.yudao.module.system.api.dept.DeptApi; | ||||
| import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | ||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.flowable.bpmn.constants.BpmnXMLConstants; | ||||
| import org.flowable.bpmn.model.BpmnModel; | ||||
| import org.flowable.bpmn.model.FlowElement; | ||||
| import org.flowable.bpmn.model.UserTask; | ||||
| import org.flowable.common.engine.api.FlowableException; | ||||
| import org.flowable.common.engine.api.FlowableObjectNotFoundException; | ||||
| import org.flowable.engine.HistoryService; | ||||
| import org.flowable.engine.RepositoryService; | ||||
| import org.flowable.engine.RuntimeService; | ||||
| import org.flowable.engine.TaskService; | ||||
| import org.flowable.engine.history.HistoricActivityInstance; | ||||
| import org.flowable.engine.history.HistoricProcessInstance; | ||||
| import org.flowable.engine.repository.ProcessDefinition; | ||||
| import org.flowable.engine.runtime.ProcessInstance; | ||||
| import org.flowable.task.api.Task; | ||||
| import org.flowable.task.api.TaskQuery; | ||||
| @ -39,8 +51,7 @@ import java.time.LocalDateTime; | ||||
| import java.util.*; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | ||||
| import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; | ||||
|  | ||||
| /** | ||||
| @ -68,12 +79,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|     private BpmTaskExtMapper taskExtMapper; | ||||
|     @Resource | ||||
|     private BpmMessageService messageService; | ||||
|     @Resource | ||||
|     private BpmModelService bpmModelService; | ||||
|     @Resource | ||||
|     private RuntimeService runtimeService; | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { | ||||
|         // 查询待办任务 | ||||
|         TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己 | ||||
|             .orderByTaskCreateTime().desc(); // 创建时间倒序 | ||||
|                 .orderByTaskCreateTime().desc(); // 创建时间倒序 | ||||
|         if (StrUtil.isNotBlank(pageVO.getName())) { | ||||
|             taskQuery.taskNameLike("%" + pageVO.getName() + "%"); | ||||
|         } | ||||
| @ -91,21 +107,21 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|  | ||||
|         // 获得 ProcessInstance Map | ||||
|         Map<String, ProcessInstance> processInstanceMap = | ||||
|             processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); | ||||
|                 processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); | ||||
|         // 获得 User Map | ||||
|         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | ||||
|             convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | ||||
|                 convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | ||||
|         // 拼接结果 | ||||
|         return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap), | ||||
|             taskQuery.count()); | ||||
|                 taskQuery.count()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) { | ||||
|         // 查询已办任务 | ||||
|         HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成 | ||||
|             .taskAssignee(String.valueOf(userId)) // 分配给自己 | ||||
|             .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 | ||||
|                 .taskAssignee(String.valueOf(userId)) // 分配给自己 | ||||
|                 .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 | ||||
|         if (StrUtil.isNotBlank(pageVO.getName())) { | ||||
|             taskQuery.taskNameLike("%" + pageVO.getName() + "%"); | ||||
|         } | ||||
| @ -123,19 +139,19 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|  | ||||
|         // 获得 TaskExtDO Map | ||||
|         List<BpmTaskExtDO> bpmTaskExtDOs = | ||||
|             taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); | ||||
|                 taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); | ||||
|         Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId); | ||||
|         // 获得 ProcessInstance Map | ||||
|         Map<String, HistoricProcessInstance> historicProcessInstanceMap = | ||||
|             processInstanceService.getHistoricProcessInstanceMap( | ||||
|                 convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); | ||||
|                 processInstanceService.getHistoricProcessInstanceMap( | ||||
|                         convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); | ||||
|         // 获得 User Map | ||||
|         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | ||||
|             convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | ||||
|                 convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | ||||
|         // 拼接结果 | ||||
|         return new PageResult<>( | ||||
|             BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), | ||||
|             taskQuery.count()); | ||||
|                 BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), | ||||
|                 taskQuery.count()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @ -189,8 +205,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|  | ||||
|         // 更新任务拓展表为通过 | ||||
|         taskExtMapper.updateByTaskId( | ||||
|             new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) | ||||
|                 .setReason(reqVO.getReason())); | ||||
|                 new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) | ||||
|                         .setReason(reqVO.getReason())); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @ -208,8 +224,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|  | ||||
|         // 更新任务拓展表为不通过 | ||||
|         taskExtMapper.updateByTaskId( | ||||
|             new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) | ||||
|                     .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); | ||||
|                 new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) | ||||
|                         .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @ -245,7 +261,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|     @Override | ||||
|     public void createTaskExt(Task task) { | ||||
|         BpmTaskExtDO taskExtDO = | ||||
|             BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); | ||||
|                 BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); | ||||
|         taskExtMapper.insert(taskExtDO); | ||||
|     } | ||||
|  | ||||
| @ -293,17 +309,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|     @Override | ||||
|     public void updateTaskExtAssign(Task task) { | ||||
|         BpmTaskExtDO taskExtDO = | ||||
|             new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); | ||||
|                 new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); | ||||
|         taskExtMapper.updateByTaskId(taskExtDO); | ||||
|         // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 | ||||
|         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | ||||
|             @Override | ||||
|             public void afterCommit() { | ||||
|                 ProcessInstance processInstance = | ||||
|                     processInstanceService.getProcessInstance(task.getProcessInstanceId()); | ||||
|                         processInstanceService.getProcessInstance(task.getProcessInstanceId()); | ||||
|                 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | ||||
|                 messageService.sendMessageWhenTaskAssigned( | ||||
|                     BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | ||||
|                         BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| @ -316,4 +332,144 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||||
|         return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<BpmTaskRollbackRespVO> findReturnTaskList(String taskId) { | ||||
|         // 当前任务 task | ||||
|         Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); | ||||
|         // 根据流程定义获取流程模型信息 | ||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | ||||
|         // 查询该任务的前置任务节点的key集合 | ||||
|         Set<String> historyTaksDefinitionKeySet = getPreTaskByCurrentTask(task, bpmnModel); | ||||
|         if (CollUtil.isEmpty(historyTaksDefinitionKeySet)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         // 获取当前任务节点元素 | ||||
|         FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | ||||
|         List<FlowElement> elementList = new ArrayList<>(); | ||||
|         for (String activityId : historyTaksDefinitionKeySet) { | ||||
|             FlowElement target = ModelUtils.getFlowElementById(bpmnModel, activityId); | ||||
|             //不支持串行和子流程 | ||||
|             boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>()); | ||||
|             if (isSequential) { | ||||
|                 elementList.add(target); | ||||
|             } | ||||
|         } | ||||
|         return BpmTaskConvert.INSTANCE.convertList(elementList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询当前流程实例符合条件可回退的 taskDefinitionKey 集合 | ||||
|      * | ||||
|      * @param task      当前任务 | ||||
|      * @param bpmnModel 当前流程定义对应的流程模型 | ||||
|      * @return 符合条件的已去重的 taskDefinitionKey集合 | ||||
|      */ | ||||
|     private Set<String> getPreTaskByCurrentTask(Task task, BpmnModel bpmnModel) { | ||||
|         // 获取当前任务节点元素 | ||||
|         FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | ||||
|         //拿到当前任务节点的前置节点集合 | ||||
|         List<UserTask> preUserTaskList = ModelUtils.getPreUserTaskList(source, null, null); | ||||
|         //需要保证这些节点都是在该source节点之前的 | ||||
|         if (CollUtil.isNotEmpty(preUserTaskList)) { | ||||
|             return convertSet(preUserTaskList, UserTask::getId); | ||||
|         } | ||||
|         return Collections.emptySet(); | ||||
|     } | ||||
|  | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     @Override | ||||
|     public void taskReturn(BpmTaskRollbackReqVO reqVO) { | ||||
|         // 当前任务 task | ||||
|         Task task = validateRollback(reqVO.getId()); | ||||
|         // 校验源头和目标节点的关系,并返回目标元素 | ||||
|         FlowElement targetElement = validateRollbackProcessDefinition(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId()); | ||||
|         //调用flowable框架的回退逻辑 | ||||
|         this.handlerRollback(task, targetElement, reqVO); | ||||
|         // 更新任务扩展表 | ||||
|         taskExtMapper.updateByTaskId( | ||||
|                 new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()) | ||||
|                         .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 校验当前流程是否可以操作回退 | ||||
|      * | ||||
|      * @param taskId 当前任务ID | ||||
|      * @return | ||||
|      */ | ||||
|     private Task validateRollback(String taskId) { | ||||
|         // 当前任务 task | ||||
|         Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); | ||||
|         if (null == task) { | ||||
|             throw exception(TASK_NOT_EXISTS); | ||||
|         } | ||||
|         if (task.isSuspended()) { | ||||
|             throw exception(TASK_IS_NOT_ACTIVITY); | ||||
|         } | ||||
|         return task; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 回退流程节点时,校验源头节点和目标节点的关系 | ||||
|      * | ||||
|      * @param sourceKey           当前任务节点Key | ||||
|      * @param targetKey           目标任务节点key | ||||
|      * @param processDefinitionId 当前流程定义ID | ||||
|      * @return 目标元素 | ||||
|      */ | ||||
|     private FlowElement validateRollbackProcessDefinition(String sourceKey, String targetKey, String processDefinitionId) { | ||||
|         // 获取流程模型信息 | ||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); | ||||
|         // 获取当前任务节点元素 | ||||
|         FlowElement source = ModelUtils.getFlowElementById(bpmnModel, sourceKey); | ||||
|         // 获取跳转的节点元素 | ||||
|         FlowElement target = ModelUtils.getFlowElementById(bpmnModel, targetKey); | ||||
|         if (null == target) { | ||||
|             throw exception(TASK_TARGET_NODE_NOT_EXISTS); | ||||
|         } | ||||
|         // 从当前节点向前扫描,判断当前节点与目标节点是否属于串行,若目标节点是在并行网关上或非同一路线上,不可跳转 | ||||
|         boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>()); | ||||
|         if (!isSequential) { | ||||
|             throw exception(TASK_SOURCE_TARGET_ERROR); | ||||
|         } | ||||
|         return target; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理回退逻辑 | ||||
|      * | ||||
|      * @param task          当前回退的任务 | ||||
|      * @param targetElement 需要回退到的目标任务 | ||||
|      * @param reqVO         前端参数封装 | ||||
|      */ | ||||
|     public void handlerRollback(Task task, FlowElement targetElement, BpmTaskRollbackReqVO reqVO) { | ||||
|         // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 | ||||
|         List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); | ||||
|         List<String> runTaskKeyList = convertList(runTaskList, Task::getTaskDefinitionKey); | ||||
|         // 通过 targetElement 的出口连线,结合 runTaskList 比对,获取需要撤回的任务 key | ||||
|         List<UserTask> rollbackUserTaskList = ModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); | ||||
|         // 需退回任务 key | ||||
|         List<String> rollbackTaskDefinitionKeyList = convertList(rollbackUserTaskList, UserTask::getId); | ||||
|  | ||||
|         // 通过 key 和 runTaskList 中的key对比,拿到任务ID,设置设置回退意见 | ||||
|         List<String> currentTaskIds = new ArrayList<>(); | ||||
|         rollbackTaskDefinitionKeyList.forEach(currentId -> runTaskList.forEach(runTask -> { | ||||
|             if (currentId.equals(runTask.getTaskDefinitionKey())) { | ||||
|                 currentTaskIds.add(runTask.getId()); | ||||
|             } | ||||
|         })); | ||||
|         if (CollUtil.isEmpty(currentTaskIds)) { | ||||
|             throw exception(TASK_ROLLBACK_FAIL); | ||||
|         } | ||||
|         // 设置回退意见 | ||||
|         for (String currentTaskId : currentTaskIds) { | ||||
|             taskService.addComment(currentTaskId, task.getProcessInstanceId(), BpmProcessInstanceResultEnum.BACK.getResult().toString(), reqVO.getReason()); | ||||
|         } | ||||
|         // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetKey 跳转到的节点(1) | ||||
|         runtimeService.createChangeActivityStateBuilder() | ||||
|                 .processInstanceId(task.getProcessInstanceId()) | ||||
|                 .moveActivityIdsToSingleActivityId(rollbackTaskDefinitionKeyList, reqVO.getTargetDefinitionKey()) | ||||
|                 .changeState(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 kehaiyou
					kehaiyou