mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	!1395 feat: 工作流支持审批人撤回
Merge pull request !1395 from Lesan/feature/bpm-审批人撤回
This commit is contained in:
		| @ -97,6 +97,9 @@ public class BpmModelMetaInfoVO { | |||||||
|     @Schema(description = "任务后置通知设置", example = "{}") |     @Schema(description = "任务后置通知设置", example = "{}") | ||||||
|     private HttpRequestSetting taskAfterTriggerSetting; |     private HttpRequestSetting taskAfterTriggerSetting; | ||||||
|  |  | ||||||
|  |     @Schema(description = "允许允许审批人撤回任务", example = "false") | ||||||
|  |     private Boolean allowWithdrawTask; | ||||||
|  |  | ||||||
|     @Schema(description = "流程 ID 规则") |     @Schema(description = "流程 ID 规则") | ||||||
|     @Data |     @Data | ||||||
|     @Valid |     @Valid | ||||||
|  | |||||||
| @ -219,6 +219,14 @@ public class BpmTaskController { | |||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @PutMapping("/withdraw") | ||||||
|  |     @Operation(summary = "撤回任务") | ||||||
|  |     @PreAuthorize("@ss.hasPermission('bpm:task:update')") | ||||||
|  |     public CommonResult<Boolean> withdrawTask(@RequestParam("taskId") String taskId) { | ||||||
|  |         taskService.withdrawTask(getLoginUserId(), taskId); | ||||||
|  |         return success(true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @GetMapping("/list-by-parent-task-id") |     @GetMapping("/list-by-parent-task-id") | ||||||
|     @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 |     @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 | ||||||
|     @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) |     @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) | ||||||
|  | |||||||
| @ -219,4 +219,9 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { | |||||||
|     @TableField(typeHandler = JacksonTypeHandler.class) |     @TableField(typeHandler = JacksonTypeHandler.class) | ||||||
|     private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; |     private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 是否允许审批人撤回任务 | ||||||
|  |      */ | ||||||
|  |     private Boolean allowWithdrawTask; | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -61,6 +61,10 @@ public interface ErrorCodeConstants { | |||||||
|     ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); |     ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); | ||||||
|     ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!"); |     ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!"); | ||||||
|     ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!"); |     ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!"); | ||||||
|  |     ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!"); | ||||||
|  |     ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!"); | ||||||
|  |     ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!"); | ||||||
|  |     ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!"); | ||||||
|  |  | ||||||
|     // ========== 动态表单模块 1-009-010-000 ========== |     // ========== 动态表单模块 1-009-010-000 ========== | ||||||
|     ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在"); |     ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在"); | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ public enum BpmReasonEnum { | |||||||
|     APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), |     APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), | ||||||
|     APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), |     APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), | ||||||
|     CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"), |     CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"), | ||||||
|  |     CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"), | ||||||
|     ; |     ; | ||||||
|  |  | ||||||
|     private final String reason; |     private final String reason; | ||||||
|  | |||||||
| @ -908,6 +908,48 @@ public class BpmnModelUtils { | |||||||
|         return nextFlowNodes; |         return nextFlowNodes; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 查找起始节点下一个用户任务列表列表 | ||||||
|  |      * @param source 起始节点 | ||||||
|  |      * @return 结果 | ||||||
|  |      */ | ||||||
|  |     public static List<UserTask> getNextUserTasks(FlowElement source) { | ||||||
|  |         return getNextUserTasks(source, null, null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 查找起始节点下一个用户任务列表列表 | ||||||
|  |      * @param source 起始节点 | ||||||
|  |      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||||||
|  |      * @param userTaskList 用户任务列表 | ||||||
|  |      * @return 结果 | ||||||
|  |      */ | ||||||
|  |     public static List<UserTask> getNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||||||
|  |         hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>()); | ||||||
|  |         userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>()); | ||||||
|  |         // 获取出口连线 | ||||||
|  |         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||||||
|  |         if (!sequenceFlows.isEmpty()) { | ||||||
|  |             for (SequenceFlow sequenceFlow : sequenceFlows) { | ||||||
|  |                 // 如果发现连线重复,说明循环了,跳过这个循环 | ||||||
|  |                 if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 // 添加已经走过的连线 | ||||||
|  |                 hasSequenceFlow.add(sequenceFlow.getId()); | ||||||
|  |                 FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement(); | ||||||
|  |                 if (targetFlowElement instanceof UserTask) { | ||||||
|  |                     // 若节点为用户任务,加入到结果列表中 | ||||||
|  |                     userTaskList.add((UserTask) targetFlowElement); | ||||||
|  |                 } else { | ||||||
|  |                     // 若节点非用户任务,继续递归查找下一个节点 | ||||||
|  |                     getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return userTaskList; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 处理排它网关 |      * 处理排它网关 | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -250,6 +250,14 @@ public interface BpmTaskService { | |||||||
|      */ |      */ | ||||||
|     void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); |     void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 撤回任务 | ||||||
|  |      * | ||||||
|  |      * @param userId 用户编号 | ||||||
|  |      * @param taskId 任务编号 | ||||||
|  |      */ | ||||||
|  |     void withdrawTask(Long userId, String taskId); | ||||||
|  |  | ||||||
|     // ========== Event 事件相关方法 ========== |     // ========== Event 事件相关方法 ========== | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -40,10 +40,9 @@ import jakarta.annotation.Resource; | |||||||
| import jakarta.validation.Valid; | import jakarta.validation.Valid; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| import org.flowable.bpmn.model.*; | import org.flowable.bpmn.model.*; | ||||||
| import org.flowable.engine.HistoryService; | import org.flowable.common.engine.api.FlowableException; | ||||||
| import org.flowable.engine.ManagementService; | import org.flowable.common.engine.api.FlowableObjectNotFoundException; | ||||||
| import org.flowable.engine.RuntimeService; | import org.flowable.engine.*; | ||||||
| import org.flowable.engine.TaskService; |  | ||||||
| import org.flowable.engine.history.HistoricActivityInstance; | import org.flowable.engine.history.HistoricActivityInstance; | ||||||
| import org.flowable.engine.runtime.ActivityInstance; | import org.flowable.engine.runtime.ActivityInstance; | ||||||
| import org.flowable.engine.runtime.Execution; | import org.flowable.engine.runtime.Execution; | ||||||
| @ -62,6 +61,7 @@ import org.springframework.transaction.support.TransactionSynchronization; | |||||||
| import org.springframework.transaction.support.TransactionSynchronizationManager; | import org.springframework.transaction.support.TransactionSynchronizationManager; | ||||||
|  |  | ||||||
| import java.util.*; | import java.util.*; | ||||||
|  | import java.util.stream.Collectors; | ||||||
| import java.util.stream.Stream; | import java.util.stream.Stream; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
| @ -599,7 +599,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 校验选择的下一个节点的审批人,是否合法 |      * 校验选择的下一个节点的审批人,是否合法 | ||||||
|      * |      * <p> | ||||||
|      * 1. 是否有漏选:没有选择审批人 |      * 1. 是否有漏选:没有选择审批人 | ||||||
|      * 2. 是否有多选:非下一个节点 |      * 2. 是否有多选:非下一个节点 | ||||||
|      * |      * | ||||||
| @ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|                     approveUserSelectAssignees = new HashMap<>(); |                     approveUserSelectAssignees = new HashMap<>(); | ||||||
|                 } |                 } | ||||||
|                 approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); |                 approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); | ||||||
|                 Map<String,List<Long>> existingApproveUserSelectAssignees = (Map<String,List<Long>>) variables.get( |                 Map<String, List<Long>> existingApproveUserSelectAssignees = (Map<String, List<Long>>) variables.get( | ||||||
|                         BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); |                         BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); | ||||||
|                 if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) { |                 if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) { | ||||||
|                     approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees); |                     approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees); | ||||||
| @ -1177,6 +1177,70 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); |         processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @Transactional(rollbackFor = Exception.class) | ||||||
|  |     public void withdrawTask(Long userId, String taskId) { | ||||||
|  |         // 1.查询本人已办任务 | ||||||
|  |         HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery() | ||||||
|  |                 .taskId(taskId).taskAssignee(userId.toString()).finished().singleResult(); | ||||||
|  |         if (ObjectUtil.isNull(taskInstance)) { | ||||||
|  |             throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS); | ||||||
|  |         } | ||||||
|  |         // 2.校验流程是否结束 | ||||||
|  |         ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() | ||||||
|  |                 .processInstanceId(taskInstance.getProcessInstanceId()) | ||||||
|  |                 .active() | ||||||
|  |                 .singleResult(); | ||||||
|  |         if (ObjectUtil.isNull(processInstance)) { | ||||||
|  |             throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING); | ||||||
|  |         } | ||||||
|  |         // 3.判断此流程是否允许撤回 | ||||||
|  |         BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService | ||||||
|  |                 .getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); | ||||||
|  |         if (ObjectUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) { | ||||||
|  |             throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW); | ||||||
|  |         } | ||||||
|  |         // 4.判断此任务下一节点是否满足撤回 | ||||||
|  |         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId()); | ||||||
|  |         UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey()); | ||||||
|  |         List<UserTask> nextUserTaskList = BpmnModelUtils.getNextUserTasks(userTask); | ||||||
|  |         List<String> nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).toList(); | ||||||
|  |         if (CollUtil.isEmpty(nextUserTaskKeys)) { | ||||||
|  |             throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); | ||||||
|  |         } | ||||||
|  |         long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery() | ||||||
|  |                 .processInstanceId(processInstance.getProcessInstanceId()) | ||||||
|  |                 .taskDefinitionKeys(nextUserTaskKeys) | ||||||
|  |                 .taskCreatedAfter(taskInstance.getEndTime()) // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以 | ||||||
|  |                 .finished() | ||||||
|  |                 .count(); | ||||||
|  |         if (nextUserTaskFinishedCount > 0) { | ||||||
|  |             throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); | ||||||
|  |         } | ||||||
|  |         // 5.获取需要撤回的运行任务 | ||||||
|  |         List<Task> runningTaskList = taskService.createTaskQuery() | ||||||
|  |                 .processInstanceId(processInstance.getProcessInstanceId()) | ||||||
|  |                 .taskDefinitionKeys(nextUserTaskKeys) | ||||||
|  |                 .active().list(); | ||||||
|  |         if (CollUtil.isEmpty(runningTaskList)) { | ||||||
|  |             throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); | ||||||
|  |         } | ||||||
|  |         List<String> withdrawExecutionIds = new ArrayList<>(); | ||||||
|  |         for (Task task : runningTaskList) { | ||||||
|  |             // 标记撤回任务为取消 | ||||||
|  |             // TODO @芋艿:是否需要添加被撤回状态? | ||||||
|  |             taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(), | ||||||
|  |                     BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回")); | ||||||
|  |             updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason()); | ||||||
|  |             withdrawExecutionIds.add(task.getExecutionId()); | ||||||
|  |         } | ||||||
|  |         // 6.执行撤回操作 | ||||||
|  |         runtimeService.createChangeActivityStateBuilder() | ||||||
|  |                 .processInstanceId(processInstance.getProcessInstanceId()) | ||||||
|  |                 .moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey()) | ||||||
|  |                 .changeState(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 校验任务是否能被减签 |      * 校验任务是否能被减签 | ||||||
|      * |      * | ||||||
| @ -1223,7 +1287,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 2. 任务前置通知 |         // 2. 任务前置通知 | ||||||
|         if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ |         if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) { | ||||||
|             BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); |             BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); | ||||||
|             BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, |             BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, | ||||||
|                     setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); |                     setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); | ||||||
| @ -1456,7 +1520,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 任务后置通知 |         // 任务后置通知 | ||||||
|         if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ |         if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) { | ||||||
|             BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); |             BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); | ||||||
|             BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, |             BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, | ||||||
|                     setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); |                     setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 芋道源码
					芋道源码