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

Merge pull request !1395 from Lesan/feature/bpm-审批人撤回
This commit is contained in:
芋道源码
2025-08-02 07:28:14 +00:00
committed by Gitee
8 changed files with 151 additions and 16 deletions

View File

@ -97,6 +97,9 @@ public class BpmModelMetaInfoVO {
@Schema(description = "任务后置通知设置", example = "{}")
private HttpRequestSetting taskAfterTriggerSetting;
@Schema(description = "允许允许审批人撤回任务", example = "false")
private Boolean allowWithdrawTask;
@Schema(description = "流程 ID 规则")
@Data
@Valid

View File

@ -219,6 +219,14 @@ public class BpmTaskController {
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")
@Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
@Parameter(name = "parentTaskId", description = "父级任务编号", required = true)

View File

@ -219,4 +219,9 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting;
/**
* 是否允许审批人撤回任务
*/
private Boolean allowWithdrawTask;
}

View File

@ -61,6 +61,10 @@ public interface ErrorCodeConstants {
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_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 ==========
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");

View File

@ -35,6 +35,7 @@ public enum BpmReasonEnum {
APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"),
;
private final String reason;

View File

@ -908,6 +908,48 @@ public class BpmnModelUtils {
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;
}
/**
* 处理排它网关
*

View File

@ -250,6 +250,14 @@ public interface BpmTaskService {
*/
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
/**
* 撤回任务
*
* @param userId 用户编号
* @param taskId 任务编号
*/
void withdrawTask(Long userId, String taskId);
// ========== Event 事件相关方法 ==========
/**

View File

@ -40,10 +40,9 @@ import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
@ -62,6 +61,7 @@ import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -599,7 +599,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/**
* 校验选择的下一个节点的审批人,是否合法
*
* <p>
* 1. 是否有漏选:没有选择审批人
* 2. 是否有多选:非下一个节点
*
@ -1177,6 +1177,70 @@ public class BpmTaskServiceImpl implements BpmTaskService {
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();
}
/**
* 校验任务是否能被减签
*