Commit 382684636819d059750d116b0a56af3d183d38e5

Authored by 芋道源码
Committed by Gitee
2 parents b420c5b8 921f99d3

!629 工作流-》流程实例的任务节点回退

Merge pull request !629 from Youkehai/master
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_PENDING = 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_RETURN_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,20 @@ public class BpmTaskController {
75 75 return success(true);
76 76 }
77 77  
  78 + @GetMapping("/get-return-list")
  79 + @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
  80 + @Parameter(name = "taskId", description = "当前任务ID", required = true)
  81 + @PreAuthorize("@ss.hasPermission('bpm:task:return')")
  82 + public CommonResult<List<BpmTaskSimpleRespVO>> getReturnList(@RequestParam("taskId") String taskId) {
  83 + return success(taskService.getReturnTaskList(taskId));
  84 + }
  85 +
  86 + @PutMapping("/return")
  87 + @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮")
  88 + @PreAuthorize("@ss.hasPermission('bpm:task:return')")
  89 + public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
  90 + taskService.returnTask(reqVO);
  91 + return success(true);
  92 + }
  93 +
78 94 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.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 BpmTaskReturnReqVO {
  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 = "回退意见", example = "")
  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/BpmTaskSimpleRespVO.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 BpmTaskSimpleRespVO {
  12 +
  13 + @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
  14 + private String definitionKey;
  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.BpmTaskSimpleRespVO;
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<BpmTaskSimpleRespVO> convertList(List<FlowElement> elementList) {
  145 + return CollectionUtils.convertList(elementList, element -> new BpmTaskSimpleRespVO()
  146 + .setName(element.getName())
  147 + .setDefinitionKey(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.*;
... ... @@ -23,7 +22,6 @@ public interface BpmTaskService {
23 22 *
24 23 * @param userId 用户编号
25 24 * @param pageReqVO 分页请求
26   - *
27 25 * @return 流程任务分页
28 26 */
29 27 PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageReqVO);
... ... @@ -33,7 +31,6 @@ public interface BpmTaskService {
33 31 *
34 32 * @param userId 用户编号
35 33 * @param pageReqVO 分页请求
36   - *
37 34 * @return 流程任务分页
38 35 */
39 36 PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO);
... ... @@ -42,19 +39,17 @@ public interface BpmTaskService {
42 39 * 获得流程任务 Map
43 40 *
44 41 * @param processInstanceIds 流程实例的编号数组
45   - *
46 42 * @return 流程任务 Map
47 43 */
48 44 default Map<String, List<Task>> getTaskMapByProcessInstanceIds(List<String> processInstanceIds) {
49 45 return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds),
50   - Task::getProcessInstanceId);
  46 + Task::getProcessInstanceId);
51 47 }
52 48  
53 49 /**
54 50 * 获得流程任务列表
55 51 *
56 52 * @param processInstanceIds 流程实例的编号数组
57   - *
58 53 * @return 流程任务列表
59 54 */
60 55 List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds);
... ... @@ -63,7 +58,6 @@ public interface BpmTaskService {
63 58 * 获得指令流程实例的流程任务列表,包括所有状态的
64 59 *
65 60 * @param processInstanceId 流程实例的编号
66   - *
67 61 * @return 流程任务列表
68 62 */
69 63 List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId);
... ... @@ -128,4 +122,19 @@ public interface BpmTaskService {
128 122 */
129 123 void updateTaskExtAssign(Task task);
130 124  
  125 + /**
  126 + * 获取当前任务的可回退的流程集合
  127 + *
  128 + * @param taskId 当前的任务ID
  129 + * @return 可以回退的节点列表
  130 + */
  131 + List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId);
  132 +
  133 + /**
  134 + * 将任务回退到指定的 targetDefinitionKey 位置
  135 + *
  136 + * @param reqVO 回退的任务key和当前所在的任务ID
  137 + */
  138 + void returnTask(BpmTaskReturnReqVO reqVO);
  139 +
131 140 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
1 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.collection.ListUtil;
5 4 import cn.hutool.core.util.ArrayUtil;
6 5 import cn.hutool.core.util.StrUtil;
7 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 7 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
9 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
10 9 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
  10 +import cn.iocoder.yudao.framework.flowable.core.util.ModelUtils;
11 11 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
12 12 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
13 13 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
14 14 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
15 15 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
16 16 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
  17 +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
17 18 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
18 19 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
19 20 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
20 21 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 23 import lombok.extern.slf4j.Slf4j;
  24 +import org.flowable.bpmn.model.BpmnModel;
  25 +import org.flowable.bpmn.model.FlowElement;
  26 +import org.flowable.bpmn.model.UserTask;
23 27 import org.flowable.engine.HistoryService;
  28 +import org.flowable.engine.RuntimeService;
24 29 import org.flowable.engine.TaskService;
25 30 import org.flowable.engine.history.HistoricProcessInstance;
26 31 import org.flowable.engine.runtime.ProcessInstance;
... ... @@ -39,8 +44,7 @@ import java.time.LocalDateTime;
39 44 import java.util.*;
40 45  
41 46 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;
  47 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
44 48 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
45 49  
46 50 /**
... ... @@ -68,12 +72,17 @@ public class BpmTaskServiceImpl implements BpmTaskService {
68 72 private BpmTaskExtMapper taskExtMapper;
69 73 @Resource
70 74 private BpmMessageService messageService;
  75 + @Resource
  76 + private BpmModelService bpmModelService;
  77 + @Resource
  78 + private RuntimeService runtimeService;
  79 +
71 80  
72 81 @Override
73 82 public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
74 83 // 查询待办任务
75 84 TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己
76   - .orderByTaskCreateTime().desc(); // 创建时间倒序
  85 + .orderByTaskCreateTime().desc(); // 创建时间倒序
77 86 if (StrUtil.isNotBlank(pageVO.getName())) {
78 87 taskQuery.taskNameLike("%" + pageVO.getName() + "%");
79 88 }
... ... @@ -91,21 +100,21 @@ public class BpmTaskServiceImpl implements BpmTaskService {
91 100  
92 101 // 获得 ProcessInstance Map
93 102 Map<String, ProcessInstance> processInstanceMap =
94   - processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
  103 + processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId));
95 104 // 获得 User Map
96 105 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
97   - convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
  106 + convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
98 107 // 拼接结果
99 108 return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap),
100   - taskQuery.count());
  109 + taskQuery.count());
101 110 }
102 111  
103 112 @Override
104 113 public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) {
105 114 // 查询已办任务
106 115 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
107   - .taskAssignee(String.valueOf(userId)) // 分配给自己
108   - .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
  116 + .taskAssignee(String.valueOf(userId)) // 分配给自己
  117 + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
109 118 if (StrUtil.isNotBlank(pageVO.getName())) {
110 119 taskQuery.taskNameLike("%" + pageVO.getName() + "%");
111 120 }
... ... @@ -123,19 +132,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
123 132  
124 133 // 获得 TaskExtDO Map
125 134 List<BpmTaskExtDO> bpmTaskExtDOs =
126   - taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
  135 + taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
127 136 Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
128 137 // 获得 ProcessInstance Map
129 138 Map<String, HistoricProcessInstance> historicProcessInstanceMap =
130   - processInstanceService.getHistoricProcessInstanceMap(
131   - convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
  139 + processInstanceService.getHistoricProcessInstanceMap(
  140 + convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
132 141 // 获得 User Map
133 142 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
134   - convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
  143 + convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
135 144 // 拼接结果
136 145 return new PageResult<>(
137   - BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
138   - taskQuery.count());
  146 + BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
  147 + taskQuery.count());
139 148 }
140 149  
141 150 @Override
... ... @@ -189,8 +198,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
189 198  
190 199 // 更新任务拓展表为通过
191 200 taskExtMapper.updateByTaskId(
192   - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
193   - .setReason(reqVO.getReason()));
  201 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
  202 + .setReason(reqVO.getReason()));
194 203 }
195 204  
196 205 @Override
... ... @@ -208,8 +217,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
208 217  
209 218 // 更新任务拓展表为不通过
210 219 taskExtMapper.updateByTaskId(
211   - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
212   - .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
  220 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
  221 + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
213 222 }
214 223  
215 224 @Override
... ... @@ -245,7 +254,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
245 254 @Override
246 255 public void createTaskExt(Task task) {
247 256 BpmTaskExtDO taskExtDO =
248   - BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
  257 + BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
249 258 taskExtMapper.insert(taskExtDO);
250 259 }
251 260  
... ... @@ -293,17 +302,17 @@ public class BpmTaskServiceImpl implements BpmTaskService {
293 302 @Override
294 303 public void updateTaskExtAssign(Task task) {
295 304 BpmTaskExtDO taskExtDO =
296   - new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
  305 + new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId());
297 306 taskExtMapper.updateByTaskId(taskExtDO);
298 307 // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
299 308 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
300 309 @Override
301 310 public void afterCommit() {
302 311 ProcessInstance processInstance =
303   - processInstanceService.getProcessInstance(task.getProcessInstanceId());
  312 + processInstanceService.getProcessInstance(task.getProcessInstanceId());
304 313 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
305 314 messageService.sendMessageWhenTaskAssigned(
306   - BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
  315 + BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
307 316 }
308 317 });
309 318 }
... ... @@ -316,4 +325,147 @@ public class BpmTaskServiceImpl implements BpmTaskService {
316 325 return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult();
317 326 }
318 327  
  328 + @Override
  329 + public List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId) {
  330 + // 当前任务 task
  331 + Task task = getTask(taskId);
  332 + if (null == task) {
  333 + throw exception(TASK_NOT_EXISTS);
  334 + }
  335 + // 根据流程定义获取流程模型信息
  336 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
  337 + // 查询该任务的前置任务节点的key集合
  338 + Set<String> historyTaksDefinitionKeySet = getPreTaskByCurrentTask(task, bpmnModel);
  339 + if (CollUtil.isEmpty(historyTaksDefinitionKeySet)) {
  340 + return Collections.emptyList();
  341 + }
  342 + // 获取当前任务节点元素
  343 + FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  344 + List<FlowElement> elementList = new ArrayList<>();
  345 + for (String activityId : historyTaksDefinitionKeySet) {
  346 + FlowElement target = ModelUtils.getFlowElementById(bpmnModel, activityId);
  347 + //非 串行和子流程则加入返回节点 elementList
  348 + boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
  349 + if (isSequential) {
  350 + elementList.add(target);
  351 + }
  352 + }
  353 + return BpmTaskConvert.INSTANCE.convertList(elementList);
  354 + }
  355 +
  356 + /**
  357 + * 查询当前流程实例符合条件可回退的 taskDefinitionKey 集合
  358 + *
  359 + * @param task 当前任务
  360 + * @param bpmnModel 当前流程定义对应的流程模型
  361 + * @return 符合条件的已去重的 taskDefinitionKey集合
  362 + */
  363 + private Set<String> getPreTaskByCurrentTask(Task task, BpmnModel bpmnModel) {
  364 + // 获取当前任务节点元素
  365 + FlowElement source = ModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  366 + //拿到当前任务节点的前置节点集合
  367 + List<UserTask> preUserTaskList = ModelUtils.getPreUserTaskList(source, null, null);
  368 + //需要保证这些节点都是在该source节点之前的
  369 + if (CollUtil.isNotEmpty(preUserTaskList)) {
  370 + return convertSet(preUserTaskList, UserTask::getId);
  371 + }
  372 + return Collections.emptySet();
  373 + }
  374 +
  375 + @Transactional(rollbackFor = Exception.class)
  376 + @Override
  377 + public void returnTask(BpmTaskReturnReqVO reqVO) {
  378 + // 当前任务 task
  379 + Task task = validateReturnTask(reqVO.getId());
  380 + // 校验源头和目标节点的关系,并返回目标元素
  381 + FlowElement targetElement = validateReturnProcessDefinition(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId());
  382 + //调用flowable框架的回退逻辑
  383 + this.handlerReturn(task, targetElement, reqVO);
  384 + // 更新任务扩展表
  385 + taskExtMapper.updateByTaskId(
  386 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.BACK.getResult())
  387 + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
  388 + }
  389 +
  390 + /**
  391 + * 校验当前流程是否可以操作回退
  392 + *
  393 + * @param taskId 当前任务ID
  394 + * @return
  395 + */
  396 + private Task validateReturnTask(String taskId) {
  397 + // 当前任务 task
  398 + Task task = getTask(taskId);
  399 + if (null == task) {
  400 + throw exception(TASK_NOT_EXISTS);
  401 + }
  402 + if (task.isSuspended()) {
  403 + throw exception(TASK_IS_PENDING);
  404 + }
  405 + return task;
  406 + }
  407 +
  408 + /**
  409 + * 回退流程节点时,校验源头节点和目标节点的关系
  410 + *
  411 + * @param sourceKey 当前任务节点Key
  412 + * @param targetKey 目标任务节点key
  413 + * @param processDefinitionId 当前流程定义ID
  414 + * @return 目标元素
  415 + */
  416 + private FlowElement validateReturnProcessDefinition(String sourceKey, String targetKey, String processDefinitionId) {
  417 + // 获取流程模型信息
  418 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
  419 + // 获取当前任务节点元素
  420 + FlowElement source = ModelUtils.getFlowElementById(bpmnModel, sourceKey);
  421 + // 获取跳转的节点元素
  422 + FlowElement target = ModelUtils.getFlowElementById(bpmnModel, targetKey);
  423 + if (null == target) {
  424 + throw exception(TASK_TARGET_NODE_NOT_EXISTS);
  425 + }
  426 + // 从当前节点向前扫描,判断当前节点与目标节点是否属于串行,若目标节点是在并行网关上或非同一路线上,不可跳转
  427 + boolean isSequential = ModelUtils.isSequentialReachable(source, target, new HashSet<>());
  428 + if (!isSequential) {
  429 + throw exception(TASK_SOURCE_TARGET_ERROR);
  430 + }
  431 + return target;
  432 + }
  433 +
  434 + /**
  435 + * 处理回退逻辑
  436 + *
  437 + * @param task 当前回退的任务
  438 + * @param targetElement 需要回退到的目标任务
  439 + * @param reqVO 前端参数封装
  440 + */
  441 + public void handlerReturn(Task task, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
  442 + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务
  443 + List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
  444 + List<String> runTaskKeyList = convertList(runTaskList, Task::getTaskDefinitionKey);
  445 + // 通过 targetElement 的出口连线,结合 runTaskList 比对,获取需要撤回的任务 key
  446 + List<UserTask> returnUserTaskList = ModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
  447 + // 需退回任务 key
  448 + List<String> returnTaskDefinitionKeyList = convertList(returnUserTaskList, UserTask::getId);
  449 +
  450 + // 通过 key 和 runTaskList 中的key对比,拿到任务ID,设置设置回退意见
  451 + List<String> currentTaskIds = new ArrayList<>();
  452 + returnTaskDefinitionKeyList.forEach(currentId -> runTaskList.forEach(runTask -> {
  453 + if (currentId.equals(runTask.getTaskDefinitionKey())) {
  454 + currentTaskIds.add(runTask.getId());
  455 + }
  456 + }));
  457 + if (CollUtil.isEmpty(currentTaskIds)) {
  458 + throw exception(TASK_RETURN_FAIL);
  459 + }
  460 + // 设置回退意见
  461 + for (String currentTaskId : currentTaskIds) {
  462 + taskService.addComment(currentTaskId, task.getProcessInstanceId(), BpmProcessInstanceResultEnum.BACK.getResult().toString(), reqVO.getReason());
  463 + }
  464 + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetKey 跳转到的节点(1)
  465 + runtimeService.createChangeActivityStateBuilder()
  466 + .processInstanceId(task.getProcessInstanceId())
  467 + .moveActivityIdsToSingleActivityId(returnTaskDefinitionKeyList, reqVO.getTargetDefinitionKey())
  468 + .changeState();
  469 + }
  470 +
319 471 }
... ...