mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +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 错误码枚举类 |  * Bpm 错误码枚举类 | ||||||
|  * |  * <p> | ||||||
|  * bpm 系统,使用 1-009-000-000 段 |  * bpm 系统,使用 1-009-000-000 段 | ||||||
|  */ |  */ | ||||||
| public interface ErrorCodeConstants { | public interface ErrorCodeConstants { | ||||||
| @ -45,7 +45,11 @@ public interface ErrorCodeConstants { | |||||||
|     // ========== 流程任务 1-009-005-000 ========== |     // ========== 流程任务 1-009-005-000 ========== | ||||||
|     ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(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_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 ========== |     // ========== 流程任务分配规则 1-009-006-000 ========== | ||||||
|     ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(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, "流程任务分配规则不存在"); |     ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在"); | ||||||
|  | |||||||
| @ -75,4 +75,19 @@ public class BpmTaskController { | |||||||
|         return success(true); |         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.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.BpmTaskDonePageItemRespVO; | ||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | 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.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO; | ||||||
| import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; | 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.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | ||||||
| import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | ||||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | 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.common.engine.impl.db.SuspensionState; | ||||||
| import org.flowable.engine.history.HistoricProcessInstance; | import org.flowable.engine.history.HistoricProcessInstance; | ||||||
| import org.flowable.engine.runtime.ProcessInstance; | import org.flowable.engine.runtime.ProcessInstance; | ||||||
| @ -138,4 +140,10 @@ public interface BpmTaskConvert { | |||||||
|         return reqDTO; |         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); |     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); |         return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { | ||||||
|  |         return repositoryService.getBpmnModel(processDefinitionId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void checkKeyNCName(String key) { |     private void checkKeyNCName(String key) { | ||||||
|         if (!ValidationUtils.isXmlNCName(key)) { |         if (!ValidationUtils.isXmlNCName(key)) { | ||||||
|             throw exception(MODEL_KEY_VALID); |             throw exception(MODEL_KEY_VALID); | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.service.task; | 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.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | ||||||
| @ -128,4 +127,17 @@ public interface BpmTaskService { | |||||||
|      */ |      */ | ||||||
|     void updateTaskExtAssign(Task task); |     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.date.DateUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.object.PageUtils; | 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.controller.admin.task.vo.task.*; | ||||||
| import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | 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.dataobject.task.BpmTaskExtDO; | ||||||
| import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper; | 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.BpmProcessInstanceDeleteReasonEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; | 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.bpm.service.message.BpmMessageService; | ||||||
| import cn.iocoder.yudao.module.system.api.dept.DeptApi; | 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.dept.dto.DeptRespDTO; | ||||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||||
| import lombok.extern.slf4j.Slf4j; | 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.HistoryService; | ||||||
|  | import org.flowable.engine.RepositoryService; | ||||||
|  | import org.flowable.engine.RuntimeService; | ||||||
| import org.flowable.engine.TaskService; | import org.flowable.engine.TaskService; | ||||||
|  | import org.flowable.engine.history.HistoricActivityInstance; | ||||||
| import org.flowable.engine.history.HistoricProcessInstance; | import org.flowable.engine.history.HistoricProcessInstance; | ||||||
|  | import org.flowable.engine.repository.ProcessDefinition; | ||||||
| import org.flowable.engine.runtime.ProcessInstance; | import org.flowable.engine.runtime.ProcessInstance; | ||||||
| import org.flowable.task.api.Task; | import org.flowable.task.api.Task; | ||||||
| import org.flowable.task.api.TaskQuery; | import org.flowable.task.api.TaskQuery; | ||||||
| @ -39,8 +51,7 @@ import java.time.LocalDateTime; | |||||||
| import java.util.*; | import java.util.*; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 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.*; | ||||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |  | ||||||
| import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -68,6 +79,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|     private BpmTaskExtMapper taskExtMapper; |     private BpmTaskExtMapper taskExtMapper; | ||||||
|     @Resource |     @Resource | ||||||
|     private BpmMessageService messageService; |     private BpmMessageService messageService; | ||||||
|  |     @Resource | ||||||
|  |     private BpmModelService bpmModelService; | ||||||
|  |     @Resource | ||||||
|  |     private RuntimeService runtimeService; | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { |     public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { | ||||||
| @ -316,4 +332,144 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult(); |         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