Commit d2bf9945f7690760f7f881e2b406c0e41b6d6e85

Authored by LesanOuO
1 parent 176e8714

feat: 工作流支持审批人撤回

yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
... ... @@ -97,6 +97,9 @@ public class BpmModelMetaInfoVO {
97 97 @Schema(description = "任务后置通知设置", example = "{}")
98 98 private HttpRequestSetting taskAfterTriggerSetting;
99 99  
  100 + @Schema(description = "允许允许审批人撤回任务", example = "false")
  101 + private Boolean allowWithdrawTask;
  102 +
100 103 @Schema(description = "流程 ID 规则")
101 104 @Data
102 105 @Valid
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
... ... @@ -219,6 +219,14 @@ public class BpmTaskController {
219 219 return success(true);
220 220 }
221 221  
  222 + @PutMapping("/withdraw")
  223 + @Operation(summary = "撤回任务")
  224 + @PreAuthorize("@ss.hasPermission('bpm:task:update')")
  225 + public CommonResult<Boolean> withdrawTask(@RequestParam("taskId") String taskId) {
  226 + taskService.withdrawTask(getLoginUserId(), taskId);
  227 + return success(true);
  228 + }
  229 +
222 230 @GetMapping("/list-by-parent-task-id")
223 231 @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
224 232 @Parameter(name = "parentTaskId", description = "父级任务编号", required = true)
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
... ... @@ -219,4 +219,9 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
219 219 @TableField(typeHandler = JacksonTypeHandler.class)
220 220 private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting;
221 221  
  222 + /**
  223 + * 是否允许审批人撤回任务
  224 + */
  225 + private Boolean allowWithdrawTask;
  226 +
222 227 }
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
... ... @@ -61,6 +61,10 @@ public interface ErrorCodeConstants {
61 61 ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
62 62 ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
63 63 ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
  64 + ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!");
  65 + ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!");
  66 + ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!");
  67 + ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!");
64 68  
65 69 // ========== 动态表单模块 1-009-010-000 ==========
66 70 ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
... ... @@ -35,6 +35,7 @@ public enum BpmReasonEnum {
35 35 APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
36 36 APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
37 37 CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
  38 + CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"),
38 39 ;
39 40  
40 41 private final String reason;
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -909,6 +909,48 @@ public class BpmnModelUtils {
909 909 }
910 910  
911 911 /**
  912 + * 查找起始节点下一个用户任务列表列表
  913 + * @param source 起始节点
  914 + * @return 结果
  915 + */
  916 + public static List<UserTask> getNextUserTasks(FlowElement source) {
  917 + return getNextUserTasks(source, null, null);
  918 + }
  919 +
  920 + /**
  921 + * 查找起始节点下一个用户任务列表列表
  922 + * @param source 起始节点
  923 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
  924 + * @param userTaskList 用户任务列表
  925 + * @return 结果
  926 + */
  927 + public static List<UserTask> getNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
  928 + hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
  929 + userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
  930 + // 获取出口连线
  931 + List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
  932 + if (!sequenceFlows.isEmpty()) {
  933 + for (SequenceFlow sequenceFlow : sequenceFlows) {
  934 + // 如果发现连线重复,说明循环了,跳过这个循环
  935 + if (hasSequenceFlow.contains(sequenceFlow.getId())) {
  936 + continue;
  937 + }
  938 + // 添加已经走过的连线
  939 + hasSequenceFlow.add(sequenceFlow.getId());
  940 + FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
  941 + if (targetFlowElement instanceof UserTask) {
  942 + // 若节点为用户任务,加入到结果列表中
  943 + userTaskList.add((UserTask) targetFlowElement);
  944 + } else {
  945 + // 若节点非用户任务,继续递归查找下一个节点
  946 + getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
  947 + }
  948 + }
  949 + }
  950 + return userTaskList;
  951 + }
  952 +
  953 + /**
912 954 * 处理排它网关
913 955 *
914 956 * @param gateway 排他网关
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
... ... @@ -250,6 +250,14 @@ public interface BpmTaskService {
250 250 */
251 251 void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
252 252  
  253 + /**
  254 + * 撤回任务
  255 + *
  256 + * @param userId 用户编号
  257 + * @param taskId 任务编号
  258 + */
  259 + void withdrawTask(Long userId, String taskId);
  260 +
253 261 // ========== Event 事件相关方法 ==========
254 262  
255 263 /**
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -40,10 +40,9 @@ import jakarta.annotation.Resource;
40 40 import jakarta.validation.Valid;
41 41 import lombok.extern.slf4j.Slf4j;
42 42 import org.flowable.bpmn.model.*;
43   -import org.flowable.engine.HistoryService;
44   -import org.flowable.engine.ManagementService;
45   -import org.flowable.engine.RuntimeService;
46   -import org.flowable.engine.TaskService;
  43 +import org.flowable.common.engine.api.FlowableException;
  44 +import org.flowable.common.engine.api.FlowableObjectNotFoundException;
  45 +import org.flowable.engine.*;
47 46 import org.flowable.engine.history.HistoricActivityInstance;
48 47 import org.flowable.engine.runtime.ActivityInstance;
49 48 import org.flowable.engine.runtime.Execution;
... ... @@ -62,6 +61,7 @@ import org.springframework.transaction.support.TransactionSynchronization;
62 61 import org.springframework.transaction.support.TransactionSynchronizationManager;
63 62  
64 63 import java.util.*;
  64 +import java.util.stream.Collectors;
65 65 import java.util.stream.Stream;
66 66  
67 67 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -196,7 +196,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
196 196 /**
197 197 * 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
198 198 *
199   - * @param userId 用户编号
  199 + * @param userId 用户编号
200 200 * @param processInstanceId 流程编号
201 201 * @return 任务
202 202 */
... ... @@ -599,15 +599,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
599 599  
600 600 /**
601 601 * 校验选择的下一个节点的审批人,是否合法
602   - *
  602 + * <p>
603 603 * 1. 是否有漏选:没有选择审批人
604 604 * 2. 是否有多选:非下一个节点
605 605 *
606 606 * @param taskDefinitionKey 当前任务节点标识
607   - * @param variables 流程变量
608   - * @param bpmnModel 流程模型
609   - * @param nextAssignees 下一个节点审批人集合(参数)
610   - * @param processInstance 流程实例
  607 + * @param variables 流程变量
  608 + * @param bpmnModel 流程模型
  609 + * @param nextAssignees 下一个节点审批人集合(参数)
  610 + * @param processInstance 流程实例
611 611 */
612 612 @SuppressWarnings("unchecked")
613 613 private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
... ... @@ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
659 659 approveUserSelectAssignees = new HashMap<>();
660 660 }
661 661 approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
662   - Map<String,List<Long>> existingApproveUserSelectAssignees = (Map<String,List<Long>>) variables.get(
  662 + Map<String, List<Long>> existingApproveUserSelectAssignees = (Map<String, List<Long>>) variables.get(
663 663 BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
664 664 if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) {
665 665 approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees);
... ... @@ -1177,6 +1177,70 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1177 1177 processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
1178 1178 }
1179 1179  
  1180 + @Override
  1181 + @Transactional(rollbackFor = Exception.class)
  1182 + public void withdrawTask(Long userId, String taskId) {
  1183 + // 1.查询本人已办任务
  1184 + HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery()
  1185 + .taskId(taskId).taskAssignee(userId.toString()).finished().singleResult();
  1186 + if (ObjectUtil.isNull(taskInstance)) {
  1187 + throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS);
  1188 + }
  1189 + // 2.校验流程是否结束
  1190 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
  1191 + .processInstanceId(taskInstance.getProcessInstanceId())
  1192 + .active()
  1193 + .singleResult();
  1194 + if (ObjectUtil.isNull(processInstance)) {
  1195 + throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING);
  1196 + }
  1197 + // 3.判断此流程是否允许撤回
  1198 + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService
  1199 + .getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
  1200 + if (ObjectUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) {
  1201 + throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW);
  1202 + }
  1203 + // 4.判断此任务下一节点是否满足撤回
  1204 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId());
  1205 + UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey());
  1206 + List<UserTask> nextUserTaskList = BpmnModelUtils.getNextUserTasks(userTask);
  1207 + List<String> nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).toList();
  1208 + if (CollUtil.isEmpty(nextUserTaskKeys)) {
  1209 + throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
  1210 + }
  1211 + long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery()
  1212 + .processInstanceId(processInstance.getProcessInstanceId())
  1213 + .taskDefinitionKeys(nextUserTaskKeys)
  1214 + .taskCreatedAfter(taskInstance.getEndTime()) // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以
  1215 + .finished()
  1216 + .count();
  1217 + if (nextUserTaskFinishedCount > 0) {
  1218 + throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
  1219 + }
  1220 + // 5.获取需要撤回的运行任务
  1221 + List<Task> runningTaskList = taskService.createTaskQuery()
  1222 + .processInstanceId(processInstance.getProcessInstanceId())
  1223 + .taskDefinitionKeys(nextUserTaskKeys)
  1224 + .active().list();
  1225 + if (CollUtil.isEmpty(runningTaskList)) {
  1226 + throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
  1227 + }
  1228 + List<String> withdrawExecutionIds = new ArrayList<>();
  1229 + for (Task task : runningTaskList) {
  1230 + // 标记撤回任务为取消
  1231 + // TODO @芋艿:是否需要添加被撤回状态?
  1232 + taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(),
  1233 + BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回"));
  1234 + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason());
  1235 + withdrawExecutionIds.add(task.getExecutionId());
  1236 + }
  1237 + // 6.执行撤回操作
  1238 + runtimeService.createChangeActivityStateBuilder()
  1239 + .processInstanceId(processInstance.getProcessInstanceId())
  1240 + .moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey())
  1241 + .changeState();
  1242 + }
  1243 +
1180 1244 /**
1181 1245 * 校验任务是否能被减签
1182 1246 *
... ... @@ -1223,7 +1287,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1223 1287 }
1224 1288  
1225 1289 // 2. 任务前置通知
1226   - if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){
  1290 + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) {
1227 1291 BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
1228 1292 BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
1229 1293 setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
... ... @@ -1350,7 +1414,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1350 1414 .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus())
1351 1415 .finished();
1352 1416 if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType())
1353   - && sameAssigneeQuery.count() > 0) {
  1417 + && sameAssigneeQuery.count() > 0) {
1354 1418 getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
1355 1419 .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName()));
1356 1420 return;
... ... @@ -1362,7 +1426,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1362 1426 return;
1363 1427 }
1364 1428 List<String> sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点
1365   - BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
  1429 + BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
1366 1430 SequenceFlow::getSourceRef);
1367 1431 if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) {
1368 1432 getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
... ... @@ -1387,7 +1451,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1387 1451 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
1388 1452 if (userTaskElement.getId().equals(START_USER_NODE_ID)
1389 1453 && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
1390   - || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
  1454 + || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
1391 1455 && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
1392 1456 getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
1393 1457 .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
... ... @@ -1456,7 +1520,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1456 1520 }
1457 1521  
1458 1522 // 任务后置通知
1459   - if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){
  1523 + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) {
1460 1524 BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
1461 1525 BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
1462 1526 setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
... ...