Commit b13f71424b2fe3b5bbfa8bee979d4208f5a56779

Authored by kehaiyou
1 parent 9630f236

feat: 工作流-》流程实例的任务节点回退

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 }
... ...