houzhongjian
2024-08-02 4a47e4b93f62b5e636ac0e76f3e4ee98e2b83154
提交 | 用户 | 时间
e7c126 1 package com.iailab.module.bpm.service.task;
H 2
3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ArrayUtil;
5 import cn.hutool.core.util.IdUtil;
6 import cn.hutool.core.util.ObjectUtil;
7 import cn.hutool.core.util.StrUtil;
4a47e4 8 import com.iailab.framework.common.pojo.CommonResult;
e7c126 9 import com.iailab.framework.common.pojo.PageResult;
H 10 import com.iailab.framework.common.util.date.DateUtils;
11 import com.iailab.framework.common.util.number.NumberUtils;
12 import com.iailab.framework.common.util.object.PageUtils;
13 import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
14 import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
15 import com.iailab.framework.web.core.util.WebFrameworkUtils;
16 import com.iailab.module.bpm.controller.admin.task.vo.task.*;
17 import com.iailab.module.bpm.convert.task.BpmTaskConvert;
18 import com.iailab.module.bpm.enums.task.BpmCommentTypeEnum;
19 import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum;
20 import com.iailab.module.bpm.enums.task.BpmTaskSignTypeEnum;
21 import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum;
22 import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants;
23 import com.iailab.module.bpm.service.definition.BpmModelService;
24 import com.iailab.module.bpm.service.message.BpmMessageService;
25 import com.iailab.module.system.api.user.AdminUserApi;
26 import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
27 import lombok.extern.slf4j.Slf4j;
28 import org.flowable.bpmn.model.BpmnModel;
29 import org.flowable.bpmn.model.FlowElement;
30 import org.flowable.bpmn.model.UserTask;
31 import org.flowable.engine.HistoryService;
32 import org.flowable.engine.ManagementService;
33 import org.flowable.engine.RuntimeService;
34 import org.flowable.engine.TaskService;
35 import org.flowable.engine.runtime.ProcessInstance;
36 import org.flowable.task.api.DelegationState;
37 import org.flowable.task.api.Task;
38 import org.flowable.task.api.TaskQuery;
39 import org.flowable.task.api.history.HistoricTaskInstance;
40 import org.flowable.task.api.history.HistoricTaskInstanceQuery;
41 import org.flowable.task.service.impl.persistence.entity.TaskEntity;
42 import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
43 import org.springframework.stereotype.Service;
44 import org.springframework.transaction.annotation.Transactional;
45 import org.springframework.transaction.support.TransactionSynchronization;
46 import org.springframework.transaction.support.TransactionSynchronizationManager;
47 import org.springframework.util.Assert;
48
49 import javax.annotation.Resource;
50 import javax.validation.Valid;
51 import java.util.*;
52 import java.util.stream.Stream;
53
54 import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
55 import static com.iailab.framework.common.util.collection.CollectionUtils.*;
56 import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
57
58 /**
59  * 流程任务实例 Service 实现类
60  *
61  * @author iailab
62  * @author jason
63  */
64 @Slf4j
65 @Service
66 public class BpmTaskServiceImpl implements BpmTaskService {
67
68     @Resource
69     private TaskService taskService;
70     @Resource
71     private HistoryService historyService;
72     @Resource
73     private RuntimeService runtimeService;
74     @Resource
75     private ManagementService managementService;
76
77     @Resource
78     private BpmProcessInstanceService processInstanceService;
79     @Resource
80     private BpmProcessInstanceCopyService processInstanceCopyService;
81     @Resource
82     private BpmModelService bpmModelService;
83     @Resource
84     private BpmMessageService messageService;
85
86     @Resource
87     private AdminUserApi adminUserApi;
88
89     @Override
90     public PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) {
91         TaskQuery taskQuery = taskService.createTaskQuery()
92                 .taskAssignee(String.valueOf(userId)) // 分配给自己
93                 .active()
94                 .includeProcessVariables()
95                 .orderByTaskCreateTime().desc(); // 创建时间倒序
96         if (StrUtil.isNotBlank(pageVO.getName())) {
97             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
98         }
99         if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
100             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
101             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
102         }
103         long count = taskQuery.count();
104         if (count == 0) {
105             return PageResult.empty();
106         }
107         List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
108         return new PageResult<>(tasks, count);
109     }
110
111     @Override
112     public PageResult<HistoricTaskInstance> getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) {
113         HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
114                 .finished() // 已完成
115                 .taskAssignee(String.valueOf(userId)) // 分配给自己
116                 .includeTaskLocalVariables()
117                 .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
118         if (StrUtil.isNotBlank(pageVO.getName())) {
119             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
120         }
121         if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
122             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
123             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
124         }
125         // 执行查询
126         long count = taskQuery.count();
127         if (count == 0) {
128             return PageResult.empty();
129         }
130         List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
131         return new PageResult<>(tasks, count);
132     }
133
134     @Override
135     public PageResult<HistoricTaskInstance> getTaskPage(Long userId, BpmTaskPageReqVO pageVO) {
136         HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
137                 .includeTaskLocalVariables()
138                 .taskTenantId(FlowableUtils.getTenantId())
139                 .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
140         if (StrUtil.isNotBlank(pageVO.getName())) {
141             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
142         }
143         if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
144             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
145             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
146         }
147         // 执行查询
148         long count = taskQuery.count();
149         if (count == 0) {
150             return PageResult.empty();
151         }
152         List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
153         return new PageResult<>(tasks, count);
154     }
155
156     @Override
157     public List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds) {
158         if (CollUtil.isEmpty(processInstanceIds)) {
159             return Collections.emptyList();
160         }
161         return taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).list();
162     }
163
164     @Override
165     public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) {
166         List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
167                 .includeTaskLocalVariables()
168                 .processInstanceId(processInstanceId)
169                 .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
170                 .list();
171         if (CollUtil.isEmpty(tasks)) {
172             return Collections.emptyList();
173         }
174         return tasks;
175     }
176
177     @Override
178     @Transactional(rollbackFor = Exception.class)
179     public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) {
180         // 1.1 校验任务存在
181         Task task = validateTask(userId, reqVO.getId());
182         // 1.2 校验流程实例存在
183         ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
184         if (instance == null) {
185             throw exception(PROCESS_INSTANCE_NOT_EXISTS);
186         }
187
188         // 2. 抄送用户
189         if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
190             processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId());
191         }
192
193         // 情况一:被委派的任务,不调用 complete 去完成任务
194         if (DelegationState.PENDING.equals(task.getDelegationState())) {
195             approveDelegateTask(reqVO, task);
196             return;
197         }
198
199         // 情况二:审批有【后】加签的任务
200         if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) {
201             approveAfterSignTask(task, reqVO);
202             return;
203         }
204
205         // 情况三:审批普通的任务。大多数情况下,都是这样
206         // 3.1 更新 task 状态、原因
207         updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason());
208         // 3.2 添加评论
209         taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
210                 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
211         // 3.3 调用 BPM complete 去完成任务
212         // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
213         if (CollUtil.isNotEmpty(reqVO.getVariables())) {
214             Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
215             taskService.complete(task.getId(), variables, true);
216         } else {
217             taskService.complete(task.getId());
218         }
219
220         // 【加签专属】处理加签任务
221         handleParentTaskIfSign(task.getParentTaskId());
222     }
223
224     /**
225      * 审批通过存在“后加签”的任务。
226      * <p>
227      * 注意:该任务不能马上完成,需要一个中间状态(APPROVING),并激活剩余所有子任务(PROCESS)为可审批处理
228      * 如果马上完成,则会触发下一个任务,甚至如果没有下一个任务则流程实例就直接结束了!
229      *
230      * @param task  当前任务
231      * @param reqVO 前端请求参数
232      */
233     private void approveAfterSignTask(Task task, BpmTaskApproveReqVO reqVO) {
234         // 更新父 task 状态 + 原因
235         updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVING.getStatus(), reqVO.getReason());
236
237         // 2. 激活子任务
238         List<Task> childrenTaskList = getTaskListByParentTaskId(task.getId());
239         for (Task childrenTask : childrenTaskList) {
240             taskService.resolveTask(childrenTask.getId());
241             // 更新子 task 状态
242             updateTaskStatus(childrenTask.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
243         }
244     }
245
246     /**
247      * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务:
248      *
249      * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批
250      * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批
251      *
252      * @param parentTaskId 父任务编号
253      */
254     private void handleParentTaskIfSign(String parentTaskId) {
255         if (StrUtil.isBlank(parentTaskId)) {
256             return;
257         }
258         // 1.1 判断是否还有子任务。如果没有,就不处理
259         Long childrenTaskCount = getTaskCountByParentTaskId(parentTaskId);
260         if (childrenTaskCount > 0) {
261             return;
262         }
263         // 1.2 只处理加签的父任务
264         Task parentTask = validateTaskExist(parentTaskId);
265         String scopeType = parentTask.getScopeType();
266         if (BpmTaskSignTypeEnum.of(scopeType) == null) {
267             return;
268         }
269
270         // 2. 子任务已处理完成,清空 scopeType 字段,修改 parentTask 信息,方便后续可以继续向前后向后加签
271         TaskEntityImpl parentTaskImpl = (TaskEntityImpl) parentTask;
272         parentTaskImpl.setScopeType(null);
273         taskService.saveTask(parentTaskImpl);
274
275         // 3.1 情况一:处理向【向前】加签
276         if (BpmTaskSignTypeEnum.BEFORE.getType().equals(scopeType)) {
277             // 3.1.1 owner 重新赋值给父任务的 assignee,这样它就可以被审批
278             taskService.resolveTask(parentTaskId);
279             // 3.1.2 更新流程任务 status
280             updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus());
281         // 3.2 情况二:处理向【向后】加签
282         } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) {
283             // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成
284             // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题
285             Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
286             if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) {
287                 return;
288             }
289             // 3.2.2 完成自己(因为它已经没有子任务,所以也可以完成)
290             updateTaskStatus(parentTaskId, BpmTaskStatusEnum.APPROVE.getStatus());
291             taskService.complete(parentTaskId);
292         }
293
294         // 4. 递归处理父任务
295         handleParentTaskIfSign(parentTask.getParentTaskId());
296     }
297
298     /**
299      * 审批被委派的任务
300      *
301      * @param reqVO 前端请求参数,包含当前任务ID,审批意见等
302      * @param task  当前被审批的任务
303      */
304     private void approveDelegateTask(BpmTaskApproveReqVO reqVO, Task task) {
305         // 1. 添加审批意见
306         AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId()).getCheckedData();
307         AdminUserRespDTO ownerUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); // 发起委托的用户
308         Assert.notNull(ownerUser, "委派任务找不到原审批人,需要检查数据");
309         taskService.addComment(reqVO.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_END.getType(),
310                 BpmCommentTypeEnum.DELEGATE_END.formatComment(currentUser.getNickname(), ownerUser.getNickname(), reqVO.getReason()));
311
312         // 2.1 调用 resolveTask 完成任务。
313         // 底层调用 TaskHelper.changeTaskAssignee(task, task.getOwner()):将 owner 设置为 assignee
314         taskService.resolveTask(task.getId());
315         // 2.2 更新 task 状态 + 原因
316         updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus(), reqVO.getReason());
317     }
318
319     @Override
320     @Transactional(rollbackFor = Exception.class)
321     public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) {
322         // 1.1 校验任务存在
323         Task task = validateTask(userId, reqVO.getId());
324         // 1.2 校验流程实例存在
325         ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
326         if (instance == null) {
327             throw exception(PROCESS_INSTANCE_NOT_EXISTS);
328         }
329
330         // 2.1 更新流程实例为不通过
331         updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason());
332         // 2.2 添加评论
333         taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
334                 BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
335
336         // 3. 更新流程实例,审批不通过!
337         processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason());
338     }
339
340     /**
341      * 更新流程任务的 status 状态
342      *
343      * @param id    任务编号
344      * @param status 状态
345      */
346     private void updateTaskStatus(String id, Integer status) {
347         taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status);
348     }
349
350     /**
351      * 更新流程任务的 status 状态、reason 理由
352      *
353      * @param id 任务编号
354      * @param status 状态
355      * @param reason 理由(审批通过、审批不通过的理由)
356      */
357     private void updateTaskStatusAndReason(String id, Integer status, String reason) {
358         updateTaskStatus(id, status);
359         taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason);
360     }
361
362     /**
363      * 校验任务是否存在,并且是否是分配给自己的任务
364      *
365      * @param userId 用户 id
366      * @param taskId task id
367      */
368     private Task validateTask(Long userId, String taskId) {
369         Task task = validateTaskExist(taskId);
370         if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
371             throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
372         }
373         return task;
374     }
375
376     @Override
377     public void updateTaskStatusWhenCreated(Task task) {
378         Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
379         if (status != null) {
380             log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status);
381             return;
382         }
383         updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
384     }
385
386     @Override
387     public void updateTaskStatusWhenCanceled(String taskId) {
388         Task task = getTask(taskId);
389         // 1. 可能只是活动,不是任务,所以查询不到
390         if (task == null) {
391             log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId);
392             return;
393         }
394
395         // 2. 更新 task 状态 + 原因
396         Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
397         if (BpmTaskStatusEnum.isEndStatus(status)) {
398             log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status);
399             return;
400         }
401         updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason());
402         // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由
403     }
404
405     @Override
406     public void updateTaskExtAssign(Task task) {
407         // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
408         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
409
410             @Override
411             public void afterCommit() {
412                 if (StrUtil.isEmpty(task.getAssignee())) {
413                     return;
414                 }
415                 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
416                 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData();
417                 messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
418             }
419
420         });
421     }
422
423     private Task validateTaskExist(String id) {
424         Task task = getTask(id);
425         if (task == null) {
426             throw exception(TASK_NOT_EXISTS);
427         }
428         return task;
429     }
430
431     @Override
432     public Task getTask(String id) {
433         return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult();
434     }
435
436     private HistoricTaskInstance getHistoricTask(String id) {
437         return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult();
438     }
439
440     @Override
441     public List<UserTask> getUserTaskListByReturn(String id) {
442         // 1.1 校验当前任务 task 存在
443         Task task = validateTaskExist(id);
444         // 1.2 根据流程定义获取流程模型信息
445         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
446         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
447         if (source == null) {
448             throw exception(TASK_NOT_EXISTS);
449         }
450
451         // 2.1 查询该任务的前置任务节点的 key 集合
452         List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null);
453         if (CollUtil.isEmpty(previousUserList)) {
454             return Collections.emptyList();
455         }
456         // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
457         previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
458         return previousUserList;
459     }
460
461     @Override
462     @Transactional(rollbackFor = Exception.class)
463     public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) {
464         // 1.1 当前任务 task
465         Task task = validateTask(userId, reqVO.getId());
466         if (task.isSuspended()) {
467             throw exception(TASK_IS_PENDING);
468         }
469         // 1.2 校验源头和目标节点的关系,并返回目标元素
470         FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),
471                 reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
472
473         // 2. 调用 Flowable 框架的回退逻辑
474         returnTask(task, targetElement, reqVO);
475     }
476
477     /**
478      * 回退流程节点时,校验目标任务节点是否可回退
479      *
480      * @param sourceKey           当前任务节点 Key
481      * @param targetKey           目标任务节点 key
482      * @param processDefinitionId 当前流程定义 ID
483      * @return 目标任务节点元素
484      */
485     private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
486         // 1.1 获取流程模型信息
487         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
488         // 1.3 获取当前任务节点元素
489         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
490         // 1.3 获取跳转的节点元素
491         FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey);
492         if (target == null) {
493             throw exception(TASK_TARGET_NODE_NOT_EXISTS);
494         }
495
496         // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
497         if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
498             throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
499         }
500         return target;
501     }
502
503     /**
504      * 执行回退逻辑
505      *
506      * @param currentTask   当前回退的任务
507      * @param targetElement 需要回退到的目标任务
508      * @param reqVO         前端参数封装
509      */
510     public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
511         // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
512         // 1.1 获取所有正常进行的任务节点 Key
513         List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
514         List<String> runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey);
515         // 1.2 通过 targetElement 的出口连线,计算在 runTaskKeyList 有哪些 key 需要被撤回
516         // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F
517         List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
518         List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
519
520         // 2. 给当前要被回退的 task 数组,设置回退意见
521         taskList.forEach(task -> {
522             // 需要排除掉,不需要设置回退意见的任务
523             if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
524                 return;
525             }
526             // 2.1 添加评论
527             taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
528                     BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
529             // 2.2 更新 task 状态 + 原因
530             updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
531         });
532
533         // 3. 执行驳回
534         runtimeService.createChangeActivityStateBuilder()
535                 .processInstanceId(currentTask.getProcessInstanceId())
536                 .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
537                         reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
538                 .changeState();
539     }
540
541     @Override
542     @Transactional(rollbackFor = Exception.class)
543     public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) {
544         String taskId = reqVO.getId();
545         // 1.1 校验任务
546         Task task = validateTask(userId, reqVO.getId());
547         if (task.getAssignee().equals(reqVO.getDelegateUserId().toString())) { // 校验当前审批人和被委派人不是同一人
548             throw exception(TASK_DELEGATE_FAIL_USER_REPEAT);
549         }
550         // 1.2 校验目标用户存在
551         AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId()).getCheckedData();
552         if (delegateUser == null) {
553             throw exception(TASK_DELEGATE_FAIL_USER_NOT_EXISTS);
554         }
555
556         // 2. 添加委托意见
557         AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData();
558         taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_START.getType(),
559                 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason()));
560
561         // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
562         taskService.setOwner(taskId, task.getAssignee());
563         // 3.2 执行委派,将任务委派给 delegateUser
564         taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
565         // 3.3 更新 task 状态。
566         // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用
567         updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus());
568     }
569
570     @Override
571     public void transferTask(Long userId, BpmTaskTransferReqVO reqVO) {
572         String taskId = reqVO.getId();
573         // 1.1 校验任务
574         Task task = validateTask(userId, reqVO.getId());
575         if (task.getAssignee().equals(reqVO.getAssigneeUserId().toString())) { // 校验当前审批人和被转派人不是同一人
576             throw exception(TASK_TRANSFER_FAIL_USER_REPEAT);
577         }
578         // 1.2 校验目标用户存在
579         AdminUserRespDTO assigneeUser = adminUserApi.getUser(reqVO.getAssigneeUserId()).getCheckedData();
580         if (assigneeUser == null) {
581             throw exception(TASK_TRANSFER_FAIL_USER_NOT_EXISTS);
582         }
583
584         // 2. 添加委托意见
585         AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData();
586         taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.TRANSFER.getType(),
587                 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason()));
588
589         // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
590         taskService.setOwner(taskId, task.getAssignee());
591         // 3.2 执行转派(审批人),将任务转派给 assigneeUser
592         // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!!
593         taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());
594     }
595
596     @Override
597     @Transactional(rollbackFor = Exception.class)
598     public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) {
599         // 1. 获取和校验任务
600         TaskEntityImpl taskEntity = validateTaskCanCreateSign(userId, reqVO);
601         List<AdminUserRespDTO> userList = adminUserApi.getUserList(reqVO.getUserIds()).getCheckedData();
602         if (CollUtil.isEmpty(userList)) {
603             throw exception(TASK_SIGN_CREATE_USER_NOT_EXIST);
604         }
605
606         // 2. 处理当前任务
607         // 2.1 开启计数功能,主要用于为了让表 ACT_RU_TASK 中的 SUB_TASK_COUNT_ 字段记录下总共有多少子任务,后续可能有用
608         taskEntity.setCountEnabled(true);
609         // 2.2 向前加签,设置 owner,置空 assign。等子任务都完成后,再调用 resolveTask 重新将 owner 设置为 assign
610         // 原因是:不能和向前加签的子任务一起审批,需要等前面的子任务都完成才能审批
611         if (reqVO.getType().equals(BpmTaskSignTypeEnum.BEFORE.getType())) {
612             taskEntity.setOwner(taskEntity.getAssignee());
613             taskEntity.setAssignee(null);
614         }
615         // 2.4 记录加签方式,完成任务时需要用到判断
616         taskEntity.setScopeType(reqVO.getType());
617         // 2.5 保存当前任务修改后的值
618         taskService.saveTask(taskEntity);
619         // 2.6 更新 task 状态为 WAIT,只有在向前加签的时候
620         if (reqVO.getType().equals(BpmTaskSignTypeEnum.BEFORE.getType())) {
621             updateTaskStatus(taskEntity.getId(), BpmTaskStatusEnum.WAIT.getStatus());
622         }
623
624         // 3. 创建加签任务
625         createSignTaskList(convertList(reqVO.getUserIds(), String::valueOf), taskEntity);
626
627         // 4. 记录加签的评论到 task 任务
628         AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData();
629         String comment = StrUtil.format(BpmCommentTypeEnum.ADD_SIGN.getComment(),
630                 currentUser.getNickname(), BpmTaskSignTypeEnum.nameOfType(reqVO.getType()),
631                 String.join(",", convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason());
632         taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(), BpmCommentTypeEnum.ADD_SIGN.getType(), comment);
633     }
634
635     /**
636      * 校验任务是否可以加签,主要校验加签类型是否一致:
637      * <p>
638      * 1. 如果存在“向前加签”的任务,则不能“向后加签”
639      * 2. 如果存在“向后加签”的任务,则不能“向前加签”
640      *
641      * @param userId 当前用户 ID
642      * @param reqVO  请求参数,包含任务 ID 和加签类型
643      * @return 当前任务
644      */
645     private TaskEntityImpl validateTaskCanCreateSign(Long userId, BpmTaskSignCreateReqVO reqVO) {
646         TaskEntityImpl taskEntity = (TaskEntityImpl) validateTask(userId, reqVO.getId());
647         // 向前加签和向后加签不能同时存在
648         if (taskEntity.getScopeType() != null
649                 && ObjectUtil.notEqual(taskEntity.getScopeType(), reqVO.getType())) {
650             throw exception(TASK_SIGN_CREATE_TYPE_ERROR,
651                     BpmTaskSignTypeEnum.nameOfType(taskEntity.getScopeType()), BpmTaskSignTypeEnum.nameOfType(reqVO.getType()));
652         }
653
654         // 同一个 key 的任务,审批人不重复
655         List<Task> taskList = taskService.createTaskQuery().processInstanceId(taskEntity.getProcessInstanceId())
656                 .taskDefinitionKey(taskEntity.getTaskDefinitionKey()).list();
657         List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner
658                 Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
659         if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) {
660             List<AdminUserRespDTO> userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData();
661             throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname)));
662         }
663         return taskEntity;
664     }
665
666     /**
667      * 创建加签子任务
668      *
669      * @param userIds 被加签的用户 ID
670      * @param taskEntity        被加签的任务
671      */
672     private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) {
673         if (CollUtil.isEmpty(userIds)) {
674             return;
675         }
676         // 创建加签人的新任务,全部基于 taskEntity 为父任务来创建
677         for (String addSignId : userIds) {
678             if (StrUtil.isBlank(addSignId)) {
679                 continue;
680             }
681             createSignTask(taskEntity, addSignId);
682         }
683     }
684
685     /**
686      * 创建加签子任务
687      *
688      * @param parentTask 父任务
689      * @param assignee   子任务的执行人
690      */
691     private void createSignTask(TaskEntityImpl parentTask, String assignee) {
692         // 1. 生成子任务
693         TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID());
694         BpmTaskConvert.INSTANCE.copyTo(parentTask, task);
695
696         // 2.1 向前加签,设置审批人
697         if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) {
698             task.setAssignee(assignee);
699         // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
700         } else {
701             task.setOwner(assignee);
702         }
703         // 2.3 保存子任务
704         taskService.saveTask(task);
705
706         // 3. 向后前签,设置子任务的状态为 WAIT,因为需要等父任务审批完
707         if (BpmTaskSignTypeEnum.AFTER.getType().equals(parentTask.getScopeType())) {
708             updateTaskStatus(task.getId(), BpmTaskStatusEnum.WAIT.getStatus());
709         }
710     }
711
712     @Override
713     @Transactional(rollbackFor = Exception.class)
714     public void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO) {
715         // 1.1 校验 task 可以被减签
716         Task task = validateTaskCanSignDelete(reqVO.getId());
717         // 1.2 校验取消人存在
718         AdminUserRespDTO cancelUser = null;
719         if (StrUtil.isNotBlank(task.getAssignee())) {
720             cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee())).getCheckedData();
721         }
722         if (cancelUser == null && StrUtil.isNotBlank(task.getOwner())) {
723             cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData();
724         }
725         Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误");
726
727         // 2.1 获得子任务列表,包括子任务的子任务
728         List<Task> childTaskList = getAllChildTaskList(task);
729         childTaskList.add(task);
730         // 2.2 更新子任务为已取消
731         String cancelReason = StrUtil.format("任务被取消,原因:由于[{}]操作[减签],", cancelUser.getNickname());
732         childTaskList.forEach(childTask -> updateTaskStatusAndReason(childTask.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), cancelReason));
733         // 2.3 删除任务和所有子任务
734         taskService.deleteTasks(convertList(childTaskList, Task::getId));
735
736         // 3. 记录日志到父任务中。先记录日志是因为,通过 handleParentTask 方法之后,任务可能被完成了,并且不存在了,会报异常,所以先记录
737         AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData();
738         taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), BpmCommentTypeEnum.SUB_SIGN.getType(),
739                 StrUtil.format(BpmCommentTypeEnum.SUB_SIGN.getComment(), user.getNickname(), cancelUser.getNickname()));
740
741         // 4. 处理当前任务的父任务
742         handleParentTaskIfSign(task.getParentTaskId());
743     }
744
745     /**
746      * 校验任务是否能被减签
747      *
748      * @param id 任务编号
749      * @return 任务信息
750      */
751     private Task validateTaskCanSignDelete(String id) {
752         Task task = validateTaskExist(id);
753         if (task.getParentTaskId() == null) {
754             throw exception(TASK_SIGN_DELETE_NO_PARENT);
755         }
756         Task parentTask = getTask(task.getParentTaskId());
757         if (parentTask == null) {
758             throw exception(TASK_SIGN_DELETE_NO_PARENT);
759         }
760         if (BpmTaskSignTypeEnum.of(parentTask.getScopeType()) == null) {
761             throw exception(TASK_SIGN_DELETE_NO_PARENT);
762         }
763         return task;
764     }
765
766     /**
767      * 获得所有子任务列表
768      *
769      * @param parentTask 父任务
770      * @return 所有子任务列表
771      */
772     private List<Task> getAllChildTaskList(Task parentTask) {
773         List<Task> result = new ArrayList<>();
774         // 1. 递归获取子级
775         Stack<Task> stack = new Stack<>();
776         stack.push(parentTask);
777         // 2. 递归遍历
778         for (int i = 0; i < Short.MAX_VALUE; i++) {
779             if (stack.isEmpty()) {
780                 break;
781             }
782             // 2.1 获取子任务们
783             Task task = stack.pop();
784             List<Task> childTaskList = getTaskListByParentTaskId(task.getId());
785             // 2.2 如果非空,则添加到 stack 进一步递归
786             if (CollUtil.isNotEmpty(childTaskList)) {
787                 stack.addAll(childTaskList);
788                 result.addAll(childTaskList);
789             }
790         }
791         return result;
792     }
793
794     @Override
795     public List<Task> getTaskListByParentTaskId(String parentTaskId) {
796         String tableName = managementService.getTableName(TaskEntity.class);
797         // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询
798         String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
799         return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list();
800     }
801
802     /**
803      * 获取子任务个数
804      *
805      * @param parentTaskId 父任务 ID
806      * @return 剩余子任务个数
807      */
808     private Long getTaskCountByParentTaskId(String parentTaskId) {
809         String tableName = managementService.getTableName(TaskEntity.class);
810         String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}";
811         return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
812     }
813
814     @Override
815     public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) {
816         if (CollUtil.isEmpty(taskIds)) {
817             return Collections.emptyMap();
818         }
819         List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list();
820         return convertMap(tasks, Task::getId, Task::getName);
821     }
822
823 }