Commit b13f71424b2fe3b5bbfa8bee979d4208f5a56779
1 parent
9630f236
feat: 工作流-》流程实例的任务节点回退
Showing
10 changed files
with
563 additions
and
40 deletions
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/ModelUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.flowable.core.util; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ObjectUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 5 | +import org.flowable.bpmn.converter.BpmnXMLConverter; | |
| 6 | +import org.flowable.bpmn.model.Process; | |
| 7 | +import org.flowable.bpmn.model.*; | |
| 8 | +import org.flowable.common.engine.impl.util.io.StringStreamSource; | |
| 9 | + | |
| 10 | +import java.util.*; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 流程模型转操作工具类 | |
| 14 | + */ | |
| 15 | +public class ModelUtils { | |
| 16 | + | |
| 17 | + /** | |
| 18 | + * 根据节点,获取入口连线 | |
| 19 | + * | |
| 20 | + * @param source 起始节点 | |
| 21 | + * @return 入口连线列表 | |
| 22 | + */ | |
| 23 | + public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) { | |
| 24 | + List<SequenceFlow> sequenceFlows = new ArrayList<>(); | |
| 25 | + if (source instanceof FlowNode) { | |
| 26 | + sequenceFlows = ((FlowNode) source).getIncomingFlows(); | |
| 27 | + } | |
| 28 | + return sequenceFlows; | |
| 29 | + } | |
| 30 | + | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 根据节点,获取出口连线 | |
| 34 | + * | |
| 35 | + * @param source 起始节点 | |
| 36 | + * @return 出口连线列表 | |
| 37 | + */ | |
| 38 | + public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) { | |
| 39 | + List<SequenceFlow> sequenceFlows = new ArrayList<>(); | |
| 40 | + if (source instanceof FlowNode) { | |
| 41 | + sequenceFlows = ((FlowNode) source).getOutgoingFlows(); | |
| 42 | + } | |
| 43 | + return sequenceFlows; | |
| 44 | + } | |
| 45 | + | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 获取流程元素信息 | |
| 49 | + * | |
| 50 | + * @param model bpmnModel对象 | |
| 51 | + * @param flowElementId 元素ID | |
| 52 | + * @return 元素信息 | |
| 53 | + */ | |
| 54 | + public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { | |
| 55 | + Process process = model.getMainProcess(); | |
| 56 | + return process.getFlowElement(flowElementId); | |
| 57 | + } | |
| 58 | + | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * 找到 source 节点之前的所有用户任务节点 | |
| 62 | + * | |
| 63 | + * @param source 起始节点 | |
| 64 | + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | |
| 65 | + * @param userTaskList 已找到的用户任务节点 | |
| 66 | + * @return | |
| 67 | + */ | |
| 68 | + public static List<UserTask> getPreUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | |
| 69 | + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | |
| 70 | + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | |
| 71 | + | |
| 72 | + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | |
| 73 | + if (source instanceof StartEvent && source.getSubProcess() != null) { | |
| 74 | + userTaskList = getPreUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList); | |
| 75 | + } | |
| 76 | + | |
| 77 | + // 根据类型,获取入口连线 | |
| 78 | + List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | |
| 79 | + | |
| 80 | + if (sequenceFlows != null) { | |
| 81 | + // 循环找到目标元素 | |
| 82 | + for (SequenceFlow sequenceFlow : sequenceFlows) { | |
| 83 | + // 如果发现连线重复,说明循环了,跳过这个循环 | |
| 84 | + if (hasSequenceFlow.contains(sequenceFlow.getId())) { | |
| 85 | + continue; | |
| 86 | + } | |
| 87 | + // 添加已经走过的连线 | |
| 88 | + hasSequenceFlow.add(sequenceFlow.getId()); | |
| 89 | + // 类型为用户节点,则新增父级节点 | |
| 90 | + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { | |
| 91 | + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); | |
| 92 | + } | |
| 93 | + // 类型为子流程,则添加子流程开始节点出口处相连的节点 | |
| 94 | + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { | |
| 95 | + // 获取子流程用户任务节点 | |
| 96 | + List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); | |
| 97 | + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | |
| 98 | + if (childUserTaskList != null && childUserTaskList.size() > 0) { | |
| 99 | + userTaskList.addAll(childUserTaskList); | |
| 100 | + } | |
| 101 | + } | |
| 102 | + // 继续迭代 | |
| 103 | + userTaskList = getPreUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); | |
| 104 | + } | |
| 105 | + } | |
| 106 | + return userTaskList; | |
| 107 | + } | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * 迭代获取子流程用户任务节点 | |
| 111 | + * | |
| 112 | + * @param source 起始节点 | |
| 113 | + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | |
| 114 | + * @param userTaskList 需要撤回的用户任务列表 | |
| 115 | + * @return | |
| 116 | + */ | |
| 117 | + public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | |
| 118 | + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | |
| 119 | + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | |
| 120 | + | |
| 121 | + // 根据类型,获取出口连线 | |
| 122 | + List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | |
| 123 | + | |
| 124 | + if (sequenceFlows != null) { | |
| 125 | + // 循环找到目标元素 | |
| 126 | + for (SequenceFlow sequenceFlow : sequenceFlows) { | |
| 127 | + // 如果发现连线重复,说明循环了,跳过这个循环 | |
| 128 | + if (hasSequenceFlow.contains(sequenceFlow.getId())) { | |
| 129 | + continue; | |
| 130 | + } | |
| 131 | + // 添加已经走过的连线 | |
| 132 | + hasSequenceFlow.add(sequenceFlow.getId()); | |
| 133 | + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | |
| 134 | + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { | |
| 135 | + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | |
| 136 | + continue; | |
| 137 | + } | |
| 138 | + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | |
| 139 | + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | |
| 140 | + List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); | |
| 141 | + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | |
| 142 | + if (childUserTaskList != null && childUserTaskList.size() > 0) { | |
| 143 | + userTaskList.addAll(childUserTaskList); | |
| 144 | + continue; | |
| 145 | + } | |
| 146 | + } | |
| 147 | + // 继续迭代 | |
| 148 | + userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); | |
| 149 | + } | |
| 150 | + } | |
| 151 | + return userTaskList; | |
| 152 | + } | |
| 153 | + | |
| 154 | + | |
| 155 | + /** | |
| 156 | + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 | |
| 157 | + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 | |
| 158 | + * | |
| 159 | + * @param source 起始节点 | |
| 160 | + * @param target 目标节点 | |
| 161 | + * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 | |
| 162 | + * @return 结果 | |
| 163 | + */ | |
| 164 | + public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { | |
| 165 | + visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; | |
| 166 | + //不能是开始事件和子流程 | |
| 167 | + if (source instanceof StartEvent && isInEventSubprocess(source)) { | |
| 168 | + return false; | |
| 169 | + } | |
| 170 | + | |
| 171 | + // 根据类型,获取入口连线 | |
| 172 | + List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | |
| 173 | + if (sequenceFlows != null && sequenceFlows.size() > 0) { | |
| 174 | + // 循环找到目标元素 | |
| 175 | + for (SequenceFlow sequenceFlow : sequenceFlows) { | |
| 176 | + // 如果发现连线重复,说明循环了,跳过这个循环 | |
| 177 | + if (visitedElements.contains(sequenceFlow.getId())) { | |
| 178 | + continue; | |
| 179 | + } | |
| 180 | + // 添加已经走过的连线 | |
| 181 | + visitedElements.add(sequenceFlow.getId()); | |
| 182 | + FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); | |
| 183 | + // 这条线路存在目标节点,这条线路完成,进入下个线路 | |
| 184 | + if (target.getId().equals(sourceFlowElement.getId())) { | |
| 185 | + continue; | |
| 186 | + } | |
| 187 | + // 如果目标节点为并行网关,则不继续 | |
| 188 | + if (sourceFlowElement instanceof ParallelGateway) { | |
| 189 | + return false; | |
| 190 | + } | |
| 191 | + // 否则就继续迭代 | |
| 192 | + boolean isSequential = isSequentialReachable(sourceFlowElement, target, visitedElements); | |
| 193 | + if (!isSequential) { | |
| 194 | + return false; | |
| 195 | + } | |
| 196 | + } | |
| 197 | + } | |
| 198 | + return true; | |
| 199 | + } | |
| 200 | + | |
| 201 | + /** | |
| 202 | + * 判断当前节点是否属于不同的子流程 | |
| 203 | + * | |
| 204 | + * @param flowElement 被判断的节点 | |
| 205 | + * @return true表示属于子流程 | |
| 206 | + */ | |
| 207 | + protected static boolean isInEventSubprocess(FlowElement flowElement) { | |
| 208 | + FlowElementsContainer flowElementsContainer = flowElement.getParentContainer(); | |
| 209 | + while (flowElementsContainer != null) { | |
| 210 | + if (flowElementsContainer instanceof EventSubProcess) { | |
| 211 | + return true; | |
| 212 | + } | |
| 213 | + | |
| 214 | + if (flowElementsContainer instanceof FlowElement) { | |
| 215 | + flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); | |
| 216 | + } else { | |
| 217 | + flowElementsContainer = null; | |
| 218 | + } | |
| 219 | + } | |
| 220 | + return false; | |
| 221 | + } | |
| 222 | + | |
| 223 | + | |
| 224 | + /** | |
| 225 | + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 | |
| 226 | + * | |
| 227 | + * @param source 起始节点 | |
| 228 | + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 | |
| 229 | + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | |
| 230 | + * @param userTaskList 需要撤回的用户任务列表 | |
| 231 | + * @return | |
| 232 | + */ | |
| 233 | + public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | |
| 234 | + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | |
| 235 | + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | |
| 236 | + | |
| 237 | + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | |
| 238 | + if (source instanceof StartEvent && source.getSubProcess() != null) { | |
| 239 | + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); | |
| 240 | + } | |
| 241 | + | |
| 242 | + // 根据类型,获取出口连线 | |
| 243 | + List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | |
| 244 | + | |
| 245 | + if (sequenceFlows != null) { | |
| 246 | + // 循环找到目标元素 | |
| 247 | + for (SequenceFlow sequenceFlow : sequenceFlows) { | |
| 248 | + // 如果发现连线重复,说明循环了,跳过这个循环 | |
| 249 | + if (hasSequenceFlow.contains(sequenceFlow.getId())) { | |
| 250 | + continue; | |
| 251 | + } | |
| 252 | + // 添加已经走过的连线 | |
| 253 | + hasSequenceFlow.add(sequenceFlow.getId()); | |
| 254 | + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | |
| 255 | + if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { | |
| 256 | + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | |
| 257 | + continue; | |
| 258 | + } | |
| 259 | + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | |
| 260 | + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | |
| 261 | + List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); | |
| 262 | + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | |
| 263 | + if (childUserTaskList != null && childUserTaskList.size() > 0) { | |
| 264 | + userTaskList.addAll(childUserTaskList); | |
| 265 | + continue; | |
| 266 | + } | |
| 267 | + } | |
| 268 | + // 继续迭代 | |
| 269 | + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); | |
| 270 | + } | |
| 271 | + } | |
| 272 | + return userTaskList; | |
| 273 | + } | |
| 274 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
| ... | ... | @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; |
| 4 | 4 | |
| 5 | 5 | /** |
| 6 | 6 | * Bpm 错误码枚举类 |
| 7 | - * | |
| 7 | + * <p> | |
| 8 | 8 | * bpm 系统,使用 1-009-000-000 段 |
| 9 | 9 | */ |
| 10 | 10 | public interface ErrorCodeConstants { |
| ... | ... | @@ -45,7 +45,11 @@ public interface ErrorCodeConstants { |
| 45 | 45 | // ========== 流程任务 1-009-005-000 ========== |
| 46 | 46 | ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1_009_005_000, "审批任务失败,原因:该任务不处于未审批"); |
| 47 | 47 | ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "审批任务失败,原因:该任务的审批人不是你"); |
| 48 | - | |
| 48 | + ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); | |
| 49 | + ErrorCode TASK_IS_NOT_ACTIVITY = new ErrorCode(1_009_005_003, "当前任务不属于活动状态,不能操作"); | |
| 50 | + ErrorCode TASK_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_004, " 当前节点相对于目标节点,不属于串行关系,无法回退"); | |
| 51 | + ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_005, " 目标节点不存在"); | |
| 52 | + ErrorCode TASK_ROLLBACK_FAIL = new ErrorCode(1_009_005_006, "回退任务失败,选择回退的节点没有需要回滚的任务!请重新选择其他任务节点"); | |
| 49 | 53 | // ========== 流程任务分配规则 1-009-006-000 ========== |
| 50 | 54 | ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则"); |
| 51 | 55 | ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在"); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
| ... | ... | @@ -75,4 +75,19 @@ public class BpmTaskController { |
| 75 | 75 | return success(true); |
| 76 | 76 | } |
| 77 | 77 | |
| 78 | + @GetMapping("/get-return-list") | |
| 79 | + @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") | |
| 80 | + @PreAuthorize("@ss.hasPermission('bpm:task:rollback')") | |
| 81 | + public CommonResult<List<BpmTaskRollbackRespVO>> getReturnList(String taskId) { | |
| 82 | + return success(taskService.findReturnTaskList(taskId)); | |
| 83 | + } | |
| 84 | + | |
| 85 | + @PutMapping("/rollback") | |
| 86 | + @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") | |
| 87 | + @PreAuthorize("@ss.hasPermission('bpm:task:rollback')") | |
| 88 | + public CommonResult<Boolean> getReturnList(@Valid @RequestBody BpmTaskRollbackReqVO reqVO) { | |
| 89 | + taskService.taskReturn(reqVO); | |
| 90 | + return success(true); | |
| 91 | + } | |
| 92 | + | |
| 78 | 93 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRollbackReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import javax.validation.constraints.NotEmpty; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - 回退流程任务的 Request VO") | |
| 9 | +@Data | |
| 10 | +public class BpmTaskRollbackReqVO { | |
| 11 | + | |
| 12 | + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 13 | + @NotEmpty(message = "任务编号不能为空") | |
| 14 | + private String id; | |
| 15 | + | |
| 16 | + @Schema(description = "回退到的任务Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 17 | + @NotEmpty(message = "回退到的任务Key不能为空") | |
| 18 | + private String targetDefinitionKey; | |
| 19 | + | |
| 20 | + @Schema(description = "回退意见") | |
| 21 | + private String reason; | |
| 22 | + | |
| 23 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRollbackRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * | |
| 8 | + */ | |
| 9 | +@Schema(description = "管理后台 - 流程任务的 可回退的节点 Response VO") | |
| 10 | +@Data | |
| 11 | +public class BpmTaskRollbackRespVO { | |
| 12 | + | |
| 13 | + @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") | |
| 14 | + private String taskDefinitionKey; | |
| 15 | + | |
| 16 | + @Schema(description = "任务名词", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批") | |
| 17 | + private String name; | |
| 18 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
| ... | ... | @@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRollbackRespVO; | |
| 9 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO; |
| 10 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; |
| 11 | 12 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 12 | 13 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 13 | 14 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 15 | +import org.flowable.bpmn.model.FlowElement; | |
| 14 | 16 | import org.flowable.common.engine.impl.db.SuspensionState; |
| 15 | 17 | import org.flowable.engine.history.HistoricProcessInstance; |
| 16 | 18 | import org.flowable.engine.runtime.ProcessInstance; |
| ... | ... | @@ -55,8 +57,8 @@ public interface BpmTaskConvert { |
| 55 | 57 | } |
| 56 | 58 | |
| 57 | 59 | default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks, |
| 58 | - Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap, | |
| 59 | - Map<Long, AdminUserRespDTO> userMap) { | |
| 60 | + Map<String, BpmTaskExtDO> bpmTaskExtDOMap, Map<String, HistoricProcessInstance> historicProcessInstanceMap, | |
| 61 | + Map<Long, AdminUserRespDTO> userMap) { | |
| 60 | 62 | return CollectionUtils.convertList(tasks, task -> { |
| 61 | 63 | BpmTaskDonePageItemRespVO respVO = convert2(task); |
| 62 | 64 | BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId()); |
| ... | ... | @@ -82,8 +84,8 @@ public interface BpmTaskConvert { |
| 82 | 84 | BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser); |
| 83 | 85 | |
| 84 | 86 | default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, |
| 85 | - Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance, | |
| 86 | - Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { | |
| 87 | + Map<String, BpmTaskExtDO> bpmTaskExtDOMap, HistoricProcessInstance processInstance, | |
| 88 | + Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { | |
| 87 | 89 | return CollectionUtils.convertList(tasks, task -> { |
| 88 | 90 | BpmTaskRespVO respVO = convert3(task); |
| 89 | 91 | BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId()); |
| ... | ... | @@ -113,29 +115,35 @@ public interface BpmTaskConvert { |
| 113 | 115 | void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to); |
| 114 | 116 | |
| 115 | 117 | @Mappings({@Mapping(source = "processInstance.id", target = "id"), |
| 116 | - @Mapping(source = "processInstance.name", target = "name"), | |
| 117 | - @Mapping(source = "processInstance.startUserId", target = "startUserId"), | |
| 118 | - @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), | |
| 119 | - @Mapping(source = "startUser.nickname", target = "startUserNickname")}) | |
| 118 | + @Mapping(source = "processInstance.name", target = "name"), | |
| 119 | + @Mapping(source = "processInstance.startUserId", target = "startUserId"), | |
| 120 | + @Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"), | |
| 121 | + @Mapping(source = "startUser.nickname", target = "startUserNickname")}) | |
| 120 | 122 | BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance, |
| 121 | - AdminUserRespDTO startUser); | |
| 123 | + AdminUserRespDTO startUser); | |
| 122 | 124 | |
| 123 | 125 | default BpmTaskExtDO convert2TaskExt(Task task) { |
| 124 | 126 | BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId()) |
| 125 | - .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName()) | |
| 126 | - .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId()); | |
| 127 | + .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName()) | |
| 128 | + .setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId()); | |
| 127 | 129 | taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime())); |
| 128 | 130 | return taskExtDO; |
| 129 | 131 | } |
| 130 | 132 | |
| 131 | 133 | default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, |
| 132 | - Task task) { | |
| 134 | + Task task) { | |
| 133 | 135 | BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO(); |
| 134 | 136 | reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId()) |
| 135 | - .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId()) | |
| 136 | - .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName()) | |
| 137 | - .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())); | |
| 137 | + .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId()) | |
| 138 | + .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName()) | |
| 139 | + .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())); | |
| 138 | 140 | return reqDTO; |
| 139 | 141 | } |
| 140 | 142 | |
| 143 | + @Mapping(source = "taskDefinitionKey", target = "id") | |
| 144 | + default List<BpmTaskRollbackRespVO> convertList(List<FlowElement> elementList) { | |
| 145 | + return CollectionUtils.convertList(elementList, element -> new BpmTaskRollbackRespVO() | |
| 146 | + .setName(element.getName()) | |
| 147 | + .setTaskDefinitionKey(element.getId())); | |
| 148 | + } | |
| 141 | 149 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
| ... | ... | @@ -74,4 +74,12 @@ public interface BpmModelService { |
| 74 | 74 | */ |
| 75 | 75 | BpmnModel getBpmnModel(String id); |
| 76 | 76 | |
| 77 | + /** | |
| 78 | + * 获得流程定义编号对应的 BPMN Model | |
| 79 | + * | |
| 80 | + * @param processDefinitionId 流程定义编号 | |
| 81 | + * @return BPMN Model | |
| 82 | + */ | |
| 83 | + BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); | |
| 84 | + | |
| 77 | 85 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
| ... | ... | @@ -233,6 +233,11 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 233 | 233 | return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true); |
| 234 | 234 | } |
| 235 | 235 | |
| 236 | + @Override | |
| 237 | + public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { | |
| 238 | + return repositoryService.getBpmnModel(processDefinitionId); | |
| 239 | + } | |
| 240 | + | |
| 236 | 241 | private void checkKeyNCName(String key) { |
| 237 | 242 | if (!ValidationUtils.isXmlNCName(key)) { |
| 238 | 243 | throw exception(MODEL_KEY_VALID); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 6 | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| ... | ... | @@ -128,4 +127,17 @@ public interface BpmTaskService { |
| 128 | 127 | */ |
| 129 | 128 | void updateTaskExtAssign(Task task); |
| 130 | 129 | |
| 130 | + /** | |
| 131 | + * 获取当前人物的可回退的流程集合 | |
| 132 | + * @param taskId 当前的任务ID | |
| 133 | + * @return 可以回退的节点列表 | |
| 134 | + */ | |
| 135 | + List<BpmTaskRollbackRespVO> findReturnTaskList(String taskId); | |
| 136 | + | |
| 137 | + /** | |
| 138 | + * 将任务回退到指定的 targetDefinitionKey 位置 | |
| 139 | + * @param reqVO 回退的任务key和当前所在的任务ID | |
| 140 | + */ | |
| 141 | + void taskReturn(BpmTaskRollbackReqVO reqVO); | |
| 142 | + | |
| 131 | 143 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| ... | ... | @@ -8,21 +8,33 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 10 | 10 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 11 | +import cn.iocoder.yudao.framework.flowable.core.util.ModelUtils; | |
| 11 | 12 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 12 | 13 | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; |
| 13 | 14 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; |
| 14 | 15 | import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper; |
| 15 | 16 | import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; |
| 16 | 17 | import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; |
| 18 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | |
| 17 | 19 | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; |
| 18 | 20 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| 19 | 21 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 20 | 22 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 21 | 23 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 22 | 24 | import lombok.extern.slf4j.Slf4j; |
| 25 | +import org.flowable.bpmn.constants.BpmnXMLConstants; | |
| 26 | +import org.flowable.bpmn.model.BpmnModel; | |
| 27 | +import org.flowable.bpmn.model.FlowElement; | |
| 28 | +import org.flowable.bpmn.model.UserTask; | |
| 29 | +import org.flowable.common.engine.api.FlowableException; | |
| 30 | +import org.flowable.common.engine.api.FlowableObjectNotFoundException; | |
| 23 | 31 | import org.flowable.engine.HistoryService; |
| 32 | +import org.flowable.engine.RepositoryService; | |
| 33 | +import org.flowable.engine.RuntimeService; | |
| 24 | 34 | import org.flowable.engine.TaskService; |
| 35 | +import org.flowable.engine.history.HistoricActivityInstance; | |
| 25 | 36 | import org.flowable.engine.history.HistoricProcessInstance; |
| 37 | +import org.flowable.engine.repository.ProcessDefinition; | |
| 26 | 38 | import org.flowable.engine.runtime.ProcessInstance; |
| 27 | 39 | import org.flowable.task.api.Task; |
| 28 | 40 | import org.flowable.task.api.TaskQuery; |
| ... | ... | @@ -39,8 +51,7 @@ import java.time.LocalDateTime; |
| 39 | 51 | import java.util.*; |
| 40 | 52 | |
| 41 | 53 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 42 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 43 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 54 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | |
| 44 | 55 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; |
| 45 | 56 | |
| 46 | 57 | /** |
| ... | ... | @@ -68,12 +79,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 68 | 79 | private BpmTaskExtMapper taskExtMapper; |
| 69 | 80 | @Resource |
| 70 | 81 | private BpmMessageService messageService; |
| 82 | + @Resource | |
| 83 | + private BpmModelService bpmModelService; | |
| 84 | + @Resource | |
| 85 | + private RuntimeService runtimeService; | |
| 86 | + | |
| 71 | 87 | |
| 72 | 88 | @Override |
| 73 | 89 | public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { |
| 74 | 90 | // 查询待办任务 |
| 75 | 91 | TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己 |
| 76 | - .orderByTaskCreateTime().desc(); // 创建时间倒序 | |
| 92 | + .orderByTaskCreateTime().desc(); // 创建时间倒序 | |
| 77 | 93 | if (StrUtil.isNotBlank(pageVO.getName())) { |
| 78 | 94 | taskQuery.taskNameLike("%" + pageVO.getName() + "%"); |
| 79 | 95 | } |
| ... | ... | @@ -91,21 +107,21 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 91 | 107 | |
| 92 | 108 | // 获得 ProcessInstance Map |
| 93 | 109 | Map<String, ProcessInstance> processInstanceMap = |
| 94 | - processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); | |
| 110 | + processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); | |
| 95 | 111 | // 获得 User Map |
| 96 | 112 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| 97 | - convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | |
| 113 | + convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | |
| 98 | 114 | // 拼接结果 |
| 99 | 115 | return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap), |
| 100 | - taskQuery.count()); | |
| 116 | + taskQuery.count()); | |
| 101 | 117 | } |
| 102 | 118 | |
| 103 | 119 | @Override |
| 104 | 120 | public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) { |
| 105 | 121 | // 查询已办任务 |
| 106 | 122 | HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成 |
| 107 | - .taskAssignee(String.valueOf(userId)) // 分配给自己 | |
| 108 | - .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 | |
| 123 | + .taskAssignee(String.valueOf(userId)) // 分配给自己 | |
| 124 | + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 | |
| 109 | 125 | if (StrUtil.isNotBlank(pageVO.getName())) { |
| 110 | 126 | taskQuery.taskNameLike("%" + pageVO.getName() + "%"); |
| 111 | 127 | } |
| ... | ... | @@ -123,19 +139,19 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 123 | 139 | |
| 124 | 140 | // 获得 TaskExtDO Map |
| 125 | 141 | List<BpmTaskExtDO> bpmTaskExtDOs = |
| 126 | - taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); | |
| 142 | + taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); | |
| 127 | 143 | Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId); |
| 128 | 144 | // 获得 ProcessInstance Map |
| 129 | 145 | Map<String, HistoricProcessInstance> historicProcessInstanceMap = |
| 130 | - processInstanceService.getHistoricProcessInstanceMap( | |
| 131 | - convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); | |
| 146 | + processInstanceService.getHistoricProcessInstanceMap( | |
| 147 | + convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); | |
| 132 | 148 | // 获得 User Map |
| 133 | 149 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| 134 | - convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | |
| 150 | + convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); | |
| 135 | 151 | // 拼接结果 |
| 136 | 152 | return new PageResult<>( |
| 137 | - BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), | |
| 138 | - taskQuery.count()); | |
| 153 | + BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), | |
| 154 | + taskQuery.count()); | |
| 139 | 155 | } |
| 140 | 156 | |
| 141 | 157 | @Override |
| ... | ... | @@ -189,8 +205,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 189 | 205 | |
| 190 | 206 | // 更新任务拓展表为通过 |
| 191 | 207 | taskExtMapper.updateByTaskId( |
| 192 | - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) | |
| 193 | - .setReason(reqVO.getReason())); | |
| 208 | + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) | |
| 209 | + .setReason(reqVO.getReason())); | |
| 194 | 210 | } |
| 195 | 211 | |
| 196 | 212 | @Override |
| ... | ... | @@ -208,8 +224,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 208 | 224 | |
| 209 | 225 | // 更新任务拓展表为不通过 |
| 210 | 226 | taskExtMapper.updateByTaskId( |
| 211 | - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) | |
| 212 | - .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); | |
| 227 | + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) | |
| 228 | + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); | |
| 213 | 229 | } |
| 214 | 230 | |
| 215 | 231 | @Override |
| ... | ... | @@ -245,7 +261,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 245 | 261 | @Override |
| 246 | 262 | public void createTaskExt(Task task) { |
| 247 | 263 | BpmTaskExtDO taskExtDO = |
| 248 | - BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); | |
| 264 | + BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); | |
| 249 | 265 | taskExtMapper.insert(taskExtDO); |
| 250 | 266 | } |
| 251 | 267 | |
| ... | ... | @@ -293,17 +309,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 293 | 309 | @Override |
| 294 | 310 | public void updateTaskExtAssign(Task task) { |
| 295 | 311 | BpmTaskExtDO taskExtDO = |
| 296 | - new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); | |
| 312 | + new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); | |
| 297 | 313 | taskExtMapper.updateByTaskId(taskExtDO); |
| 298 | 314 | // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 |
| 299 | 315 | TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { |
| 300 | 316 | @Override |
| 301 | 317 | public void afterCommit() { |
| 302 | 318 | ProcessInstance processInstance = |
| 303 | - processInstanceService.getProcessInstance(task.getProcessInstanceId()); | |
| 319 | + processInstanceService.getProcessInstance(task.getProcessInstanceId()); | |
| 304 | 320 | AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); |
| 305 | 321 | messageService.sendMessageWhenTaskAssigned( |
| 306 | - BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | |
| 322 | + BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | |
| 307 | 323 | } |
| 308 | 324 | }); |
| 309 | 325 | } |
| ... | ... | @@ -316,4 +332,144 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 316 | 332 | return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult(); |
| 317 | 333 | } |
| 318 | 334 | |
| 335 | + @Override | |
| 336 | + public List<BpmTaskRollbackRespVO> findReturnTaskList(String taskId) { | |
| 337 | + // 当前任务 task | |
| 338 | + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); | |
| 339 | + // 根据流程定义获取流程模型信息 | |
| 340 | + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 341 | + // 查询该任务的前置任务节点的key集合 | |
| 342 | + Set<String> historyTaksDefinitionKeySet = getPreTaskByCurrentTask(task, bpmnModel); | |
| 343 | + if (CollUtil.isEmpty(historyTaksDefinitionKeySet)) { | |
| 344 | + return Collections.emptyList(); | |
| 345 | + } | |
| 346 | + // 获取当前任务节点元素 | |
| 347 | + FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 348 | + List<FlowElement> elementList = new ArrayList<>(); | |
| 349 | + for (String activityId : historyTaksDefinitionKeySet) { | |
| 350 | + FlowElement target = ModelUtils.getFlowElementById(bpmnModel, activityId); | |
| 351 | + //不支持串行和子流程 | |
| 352 | + boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>()); | |
| 353 | + if (isSequential) { | |
| 354 | + elementList.add(target); | |
| 355 | + } | |
| 356 | + } | |
| 357 | + return BpmTaskConvert.INSTANCE.convertList(elementList); | |
| 358 | + } | |
| 359 | + | |
| 360 | + /** | |
| 361 | + * 查询当前流程实例符合条件可回退的 taskDefinitionKey 集合 | |
| 362 | + * | |
| 363 | + * @param task 当前任务 | |
| 364 | + * @param bpmnModel 当前流程定义对应的流程模型 | |
| 365 | + * @return 符合条件的已去重的 taskDefinitionKey集合 | |
| 366 | + */ | |
| 367 | + private Set<String> getPreTaskByCurrentTask(Task task, BpmnModel bpmnModel) { | |
| 368 | + // 获取当前任务节点元素 | |
| 369 | + FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 370 | + //拿到当前任务节点的前置节点集合 | |
| 371 | + List<UserTask> preUserTaskList = ModelUtils.getPreUserTaskList(source, null, null); | |
| 372 | + //需要保证这些节点都是在该source节点之前的 | |
| 373 | + if (CollUtil.isNotEmpty(preUserTaskList)) { | |
| 374 | + return convertSet(preUserTaskList, UserTask::getId); | |
| 375 | + } | |
| 376 | + return Collections.emptySet(); | |
| 377 | + } | |
| 378 | + | |
| 379 | + @Transactional(rollbackFor = Exception.class) | |
| 380 | + @Override | |
| 381 | + public void taskReturn(BpmTaskRollbackReqVO reqVO) { | |
| 382 | + // 当前任务 task | |
| 383 | + Task task = validateRollback(reqVO.getId()); | |
| 384 | + // 校验源头和目标节点的关系,并返回目标元素 | |
| 385 | + FlowElement targetElement = validateRollbackProcessDefinition(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId()); | |
| 386 | + //调用flowable框架的回退逻辑 | |
| 387 | + this.handlerRollback(task, targetElement, reqVO); | |
| 388 | + // 更新任务扩展表 | |
| 389 | + taskExtMapper.updateByTaskId( | |
| 390 | + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult()) | |
| 391 | + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); | |
| 392 | + } | |
| 393 | + | |
| 394 | + /** | |
| 395 | + * 校验当前流程是否可以操作回退 | |
| 396 | + * | |
| 397 | + * @param taskId 当前任务ID | |
| 398 | + * @return | |
| 399 | + */ | |
| 400 | + private Task validateRollback(String taskId) { | |
| 401 | + // 当前任务 task | |
| 402 | + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); | |
| 403 | + if (null == task) { | |
| 404 | + throw exception(TASK_NOT_EXISTS); | |
| 405 | + } | |
| 406 | + if (task.isSuspended()) { | |
| 407 | + throw exception(TASK_IS_NOT_ACTIVITY); | |
| 408 | + } | |
| 409 | + return task; | |
| 410 | + } | |
| 411 | + | |
| 412 | + /** | |
| 413 | + * 回退流程节点时,校验源头节点和目标节点的关系 | |
| 414 | + * | |
| 415 | + * @param sourceKey 当前任务节点Key | |
| 416 | + * @param targetKey 目标任务节点key | |
| 417 | + * @param processDefinitionId 当前流程定义ID | |
| 418 | + * @return 目标元素 | |
| 419 | + */ | |
| 420 | + private FlowElement validateRollbackProcessDefinition(String sourceKey, String targetKey, String processDefinitionId) { | |
| 421 | + // 获取流程模型信息 | |
| 422 | + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 423 | + // 获取当前任务节点元素 | |
| 424 | + FlowElement source = ModelUtils.getFlowElementById(bpmnModel, sourceKey); | |
| 425 | + // 获取跳转的节点元素 | |
| 426 | + FlowElement target = ModelUtils.getFlowElementById(bpmnModel, targetKey); | |
| 427 | + if (null == target) { | |
| 428 | + throw exception(TASK_TARGET_NODE_NOT_EXISTS); | |
| 429 | + } | |
| 430 | + // 从当前节点向前扫描,判断当前节点与目标节点是否属于串行,若目标节点是在并行网关上或非同一路线上,不可跳转 | |
| 431 | + boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>()); | |
| 432 | + if (!isSequential) { | |
| 433 | + throw exception(TASK_SOURCE_TARGET_ERROR); | |
| 434 | + } | |
| 435 | + return target; | |
| 436 | + } | |
| 437 | + | |
| 438 | + /** | |
| 439 | + * 处理回退逻辑 | |
| 440 | + * | |
| 441 | + * @param task 当前回退的任务 | |
| 442 | + * @param targetElement 需要回退到的目标任务 | |
| 443 | + * @param reqVO 前端参数封装 | |
| 444 | + */ | |
| 445 | + public void handlerRollback(Task task, FlowElement targetElement, BpmTaskRollbackReqVO reqVO) { | |
| 446 | + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 | |
| 447 | + List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); | |
| 448 | + List<String> runTaskKeyList = convertList(runTaskList, Task::getTaskDefinitionKey); | |
| 449 | + // 通过 targetElement 的出口连线,结合 runTaskList 比对,获取需要撤回的任务 key | |
| 450 | + List<UserTask> rollbackUserTaskList = ModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); | |
| 451 | + // 需退回任务 key | |
| 452 | + List<String> rollbackTaskDefinitionKeyList = convertList(rollbackUserTaskList, UserTask::getId); | |
| 453 | + | |
| 454 | + // 通过 key 和 runTaskList 中的key对比,拿到任务ID,设置设置回退意见 | |
| 455 | + List<String> currentTaskIds = new ArrayList<>(); | |
| 456 | + rollbackTaskDefinitionKeyList.forEach(currentId -> runTaskList.forEach(runTask -> { | |
| 457 | + if (currentId.equals(runTask.getTaskDefinitionKey())) { | |
| 458 | + currentTaskIds.add(runTask.getId()); | |
| 459 | + } | |
| 460 | + })); | |
| 461 | + if (CollUtil.isEmpty(currentTaskIds)) { | |
| 462 | + throw exception(TASK_ROLLBACK_FAIL); | |
| 463 | + } | |
| 464 | + // 设置回退意见 | |
| 465 | + for (String currentTaskId : currentTaskIds) { | |
| 466 | + taskService.addComment(currentTaskId, task.getProcessInstanceId(), BpmProcessInstanceResultEnum.BACK.getResult().toString(), reqVO.getReason()); | |
| 467 | + } | |
| 468 | + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetKey 跳转到的节点(1) | |
| 469 | + runtimeService.createChangeActivityStateBuilder() | |
| 470 | + .processInstanceId(task.getProcessInstanceId()) | |
| 471 | + .moveActivityIdsToSingleActivityId(rollbackTaskDefinitionKeyList, reqVO.getTargetDefinitionKey()) | |
| 472 | + .changeState(); | |
| 473 | + } | |
| 474 | + | |
| 319 | 475 | } | ... | ... |