【代码优化】工作流:task 审批不通过时,针对加签的处理

This commit is contained in:
YunaiV 2024-08-11 13:00:01 +08:00
parent 5bafc0ce57
commit 521cc3deb4
8 changed files with 66 additions and 57 deletions

View File

@ -75,8 +75,4 @@ public interface ErrorCodeConstants {
// ========== BPM 流程表达式 1-009-014-000 ========== // ========== BPM 流程表达式 1-009-014-000 ==========
ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在");
// ========== BPM 仿钉钉流程设计器 1-009-015-000 ==========
// TODO @芋艿这个错误码需要关注下
ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程");
} }

View File

@ -22,8 +22,6 @@ public enum BpmCommentTypeEnum {
TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"), TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"),
ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"), ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"),
SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"),
// TODO @芋艿这个枚举状态需要关注下
REJECT_BY_ADD_SIGN_TASK_REJECT("10", "不通过","系统自动不通过,原因是:加签任务不通过")
; ;
/** /**

View File

@ -22,8 +22,6 @@ public enum BpmDeleteReasonEnum {
// ========== 流程任务的独有原因 ========== // ========== 流程任务的独有原因 ==========
CANCEL_BY_SYSTEM("系统自动取消"), // 场景非常多比如说1多任务审批已经满足条件无需审批该任务2流程实例被取消无需审批该任务等等 CANCEL_BY_SYSTEM("系统自动取消"), // 场景非常多比如说1多任务审批已经满足条件无需审批该任务2流程实例被取消无需审批该任务等等
// TODO @芋艿这个枚举状态需要关注下
AUTO_REJECT_BY_ADD_SIGN_REJECT("系统自动拒绝,原因:加签任务被拒绝") // 加签任务审批不通过导致任务不通过
; ;
private final String reason; private final String reason;

View File

@ -59,8 +59,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Override @Override
protected void activityCancelled(FlowableActivityCancelledEvent event) { protected void activityCancelled(FlowableActivityCancelledEvent event) {
// TODO @jason如果用户主动取消可能需要考虑这个 // TODO @jason如果用户主动取消可能需要考虑这个
// TODO @芋艿 如果在 rejectTask 处理了 这里又要查询一次感觉有点多余 主动取消是不是也要处理一下要不然有加签的任务也会报错
// @芋艿 这里是不是就可以不要了 取消的任务状态在rejectTask 里面做了 如果在 updateTaskStatusWhenCanceled 里面修改会报错
List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId());
if (CollUtil.isEmpty(activityList)) { if (CollUtil.isEmpty(activityList)) {
log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());

View File

@ -10,6 +10,7 @@ import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -141,7 +142,7 @@ public interface BpmProcessInstanceService {
* @param endId 结束节点 Id * @param endId 结束节点 Id
* @param reason 理由例如说审批不通过时需要传递该值 * @param reason 理由例如说审批不通过时需要传递该值
*/ */
void updateProcessInstanceReject(ProcessInstance processInstance, List<String> activityIds, String endId, String reason); void updateProcessInstanceReject(ProcessInstance processInstance, Collection<String> activityIds, String endId, String reason);
/** /**
* 当流程结束时候更新 ProcessInstance 为通过 * 当流程结束时候更新 ProcessInstance 为通过

View File

@ -133,9 +133,9 @@ public interface BpmTaskService {
* 根据条件查询正在进行中的任务 * 根据条件查询正在进行中的任务
* *
* @param processInstanceId 流程实例编号不允许为空 * @param processInstanceId 流程实例编号不允许为空
* @param assigned 是否分配了审批人 * @param assigned 是否分配了审批人允许空
* @param executionId execution Id * @param executionId execution Id允许空
* @param taskDefineKey 任务定义 Key * @param taskDefineKey 任务定义 Key允许空
*/ */
List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId,
Boolean assigned, Boolean assigned,

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils;
@ -53,8 +52,6 @@ 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;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum.REJECT_BY_ADD_SIGN_TASK_REJECT;
import static cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum.AUTO_REJECT_BY_ADD_SIGN_REJECT;
/** /**
* 流程任务实例 Service 实现类 * 流程任务实例 Service 实现类
@ -329,35 +326,41 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (instance == null) { if (instance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS); throw exception(PROCESS_INSTANCE_NOT_EXISTS);
} }
// 2. 处理当前任务
// 2.1 更新流程任务为不通过 // 2.1 更新流程任务为不通过
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason());
// 2.2 添加评论 // 2.2 添加评论
taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
// 3.1 如果是被加签任务且是后加签 更新加签任务状态为取消 // 3. 处理其他进行中的任务
if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) { // 3.1 如果当前任务时被加签的则加它的根任务也标记成未通过
List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); // 疑问为什么要标记未通过呢
updateTaskStatusWhenCanceled(childTaskList, reqVO.getReason()); // 回答例如说 A 任务被向前加签除 B 任务时B 任务被审批不通过此时 A 会被取消 yudao-ui-admin-vue3 不展示已取消的任务导致展示不出审批不通过的细节
if (task.getParentTaskId() != null) {
String rootParentId = getTaskRootParentId(task);
updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(),
BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
} }
// 3.2 如果是加签的任务 // 3.2 其它未结束的任务直接取消
if (StrUtil.isNotEmpty(task.getParentTaskId())) { // 疑问为什么不通过 updateTaskStatusWhenCanceled 监听取消而是直接提前调用呢
Task signTask = validateTaskExist(task.getParentTaskId()); // 回答详细见 updateTaskStatusWhenCanceled 的方法加签的场景
// 3.2.1 更新被加签的任务为不通过 List<Task> taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), null, null, null);
if (BpmTaskSignTypeEnum.BEFORE.getType().equals(signTask.getScopeType())) { taskList.forEach(otherTask -> {
updateTaskStatusAndReason(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus(), AUTO_REJECT_BY_ADD_SIGN_REJECT.getReason()); if (!otherTask.getId().equals(task.getId())) { // 不需要处理当前任务
} else if (BpmTaskSignTypeEnum.AFTER.getType().equals(signTask.getScopeType())) { return;
updateTaskStatus(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus());
// 后加签 不添加拒绝意见因为会把原来的意见覆盖.
} }
// 3.2.2 添加评论 Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) {
BpmCommentTypeEnum.REJECT.getType(), REJECT_BY_ADD_SIGN_TASK_REJECT.getComment()); return;
// 3.2.3 更新还在进行中的加签任务状态为取消 }
List<Task> addSignTaskList = getTaskListByParentTaskId(task.getParentTaskId()); updateTaskStatusWhenCanceled(otherTask.getId());
updateTaskStatusWhenCanceled(CollectionUtils.filterList(addSignTaskList, item -> !item.getId().equals(task.getId())), });
reqVO.getReason()); taskList.stream().filter(otherTask -> !otherTask.getId().equals(task.getId())) // 需要排除当前任务
} .forEach(otherTask -> updateTaskStatusWhenCanceled(otherTask.getId()));
// 4.1 驳回到指定的任务节点 // 4.1 驳回到指定的任务节点
BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
@ -372,26 +375,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return; return;
} }
// 4.2.1 更新其它正在运行的任务状态为取消需要过滤掉当前任务和被加签的任务 // 4.2 终止流程
// TODO @jason如果过滤掉被加签的任务这些任务被对应的审批人看到是啥状态哈 @芋艿 为不通过状态
List<Task> taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), false, null, null);
updateTaskStatusWhenCanceled(
CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())),
reqVO.getReason());
// 4.2.2 终止流程
Set<String> activityIds = convertSet(taskList, Task::getTaskDefinitionKey); Set<String> activityIds = convertSet(taskList, Task::getTaskDefinitionKey);
EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
Assert.notNull(endEvent, "结束节点不能未空"); Assert.notNull(endEvent, "结束节点不能未空");
processInstanceService.updateProcessInstanceReject(instance, CollUtil.newArrayList(activityIds), endEvent.getId(), reqVO.getReason()); processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason());
}
private void updateTaskStatusWhenCanceled(List<Task> taskList, String reason) {
taskList.forEach(task -> {
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason());
taskService.addComment(task.getId(), task.getProcessInstanceId(),
BpmCommentTypeEnum.CANCEL.getType(), BpmCommentTypeEnum.CANCEL.formatComment(reason));
});
} }
/** /**
@ -440,9 +428,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
} }
/**
* 重要补充说明该方法目前主要有两个情况会调用到
*
* 1. 或签场景 + 审批通过一个或签有多个审批时如果 A 审批通过其它或签 BC 等任务会被 Flowable 自动删除此时需要通过该方法更新状态为已取消
* 2. 审批不通过 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时对于加签的任务不会被 Flowable 删除此时需要通过该方法更新状态为已取消
*/
@Override @Override
public void updateTaskStatusWhenCanceled(String taskId) { public void updateTaskStatusWhenCanceled(String taskId) {
// @芋艿这里是不是可以不要了要不然 updateTaskStatusAndReason 会报错 task 已经删除了
Task task = getTask(taskId); Task task = getTask(taskId);
// 1. 可能只是活动不是任务所以查询不到 // 1. 可能只是活动不是任务所以查询不到
if (task == null) { if (task == null) {
@ -500,6 +493,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
.includeTaskLocalVariables(); .includeTaskLocalVariables();
if (BooleanUtil.isTrue(assigned)) { if (BooleanUtil.isTrue(assigned)) {
taskQuery.taskAssigned(); taskQuery.taskAssigned();
} else if (BooleanUtil.isFalse(assigned)) {
taskQuery.taskUnassigned();
} }
if (StrUtil.isNotEmpty(executionId)) { if (StrUtil.isNotEmpty(executionId)) {
taskQuery.executionId(executionId); taskQuery.executionId(executionId);
@ -888,6 +883,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
} }
/**
* 获得任务根任务的父任务编号
*
* @param task 任务
* @return 根任务的父任务编号
*/
private String getTaskRootParentId(Task task) {
if (task == null || task.getParentTaskId() == null) {
return null;
}
for (int i = 0; i < Short.MAX_VALUE; i++) {
Task parentTask = getTask(task.getParentTaskId());
if (parentTask == null) {
return null;
}
if (parentTask.getParentTaskId() == null) {
return parentTask.getId();
}
task = parentTask;
}
throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId()));
}
@Override @Override
public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) { public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) {
if (CollUtil.isEmpty(taskIds)) { if (CollUtil.isEmpty(taskIds)) {