仿钉钉流程设计- 加签拒绝处理

This commit is contained in:
jason 2024-06-19 17:06:48 +08:00
parent 633a7c50ae
commit 4d49952c52
9 changed files with 85 additions and 44 deletions

View File

@ -13,10 +13,8 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum BpmFieldPermissionEnum { public enum BpmFieldPermissionEnum {
// TODO @jason这个顺序要不要改下和页面保持一致只读1编辑2隐藏3 READ(1, "只读"),
// @芋艿 我看钉钉页面的顺序 可编辑 只读 隐藏 WRITE(2, "可编辑"),
WRITE(1, "可编辑"),
READ(2, "只读"),
NONE(3, "隐藏"); NONE(3, "隐藏");
/** /**

View File

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

View File

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

View File

@ -1,7 +1,5 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -11,12 +9,10 @@ import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
@ -39,7 +35,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
.add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_CREATED)
.add(FlowableEngineEventType.TASK_ASSIGNED) .add(FlowableEngineEventType.TASK_ASSIGNED)
// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时已经记录了 task status 为通过所以不需要监听了 // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时已经记录了 task status 为通过所以不需要监听了
// .add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED)
.add(FlowableEngineEventType.ACTIVITY_CANCELLED) .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
.build(); .build();
@ -59,18 +54,18 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Override @Override
protected void activityCancelled(FlowableActivityCancelledEvent event) { protected void activityCancelled(FlowableActivityCancelledEvent event) {
List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); // @芋艿 这里是不是就可以不要了 取消的任务状态在rejectTask 里面做了 如果在 updateTaskStatusWhenCanceled 里面修改会报错
if (CollUtil.isEmpty(activityList)) { // List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId());
log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); // if (CollUtil.isEmpty(activityList)) {
return; // log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());
// return;
// }
// // 遍历处理
// activityList.forEach(activity -> {
// if (StrUtil.isEmpty(activity.getTaskId())) {
// return;
// }
// taskService.updateTaskStatusWhenCanceled(activity.getTaskId());
// });
} }
// 遍历处理
activityList.forEach(activity -> {
if (StrUtil.isEmpty(activity.getTaskId())) {
return;
}
taskService.updateTaskStatusWhenCanceled(activity.getTaskId());
});
}
} }

View File

@ -81,7 +81,8 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe
BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction);
if (userTaskTimeoutAction != null) { if (userTaskTimeoutAction != null) {
// 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ???
List<Task> taskList = bpmTaskService.getTaskListByProcessInstanceIdAndAssigned(processInstanceId, null, taskDefKey); List<Task> taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true,
null, taskDefKey);
taskList.forEach(task -> { taskList.forEach(task -> {
// 自动提醒 // 自动提醒
if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) {

View File

@ -136,11 +136,12 @@ public interface BpmProcessInstanceService {
/** /**
* 更新 ProcessInstance 为不通过 * 更新 ProcessInstance 为不通过
* *
* @param id 流程编号 * @param processInstance 流程实例
* @param currentActivityId 当前的活动编号 * @param activityIds 当前未完成活动节点 Id
* @param endId 结束节点 Id
* @param reason 理由例如说审批不通过时需要传递该值 * @param reason 理由例如说审批不通过时需要传递该值
*/ */
void updateProcessInstanceReject(String id, String currentActivityId, String reason); void updateProcessInstanceReject(ProcessInstance processInstance, List<String> activityIds, String endId, String reason);
/** /**
* 当流程结束时候更新 ProcessInstance 为通过 * 当流程结束时候更新 ProcessInstance 为通过

View File

@ -129,14 +129,15 @@ public interface BpmTaskService {
*/ */
Task getTask(String id); Task getTask(String id);
// TODO @jasonjason这个貌似可以去掉了
/** /**
* 根据条件查询已经分配的用户任务列表 * 根据条件查询正在进行中的任务
*
* @param processInstanceId 流程实例编号不允许为空 * @param processInstanceId 流程实例编号不允许为空
* @param assigned 是否分配了审批人
* @param executionId execution Id * @param executionId execution Id
* @param taskDefineKey 任务定义 Key * @param taskDefineKey 任务定义 Key
*/ */
List<Task> getTaskListByProcessInstanceIdAndAssigned(String processInstanceId, String executionId, String taskDefineKey); List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String executionId, String taskDefineKey);
/** /**
* 获取当前任务的可回退的 UserTask 集合 * 获取当前任务的可回退的 UserTask 集合

View File

@ -1,11 +1,9 @@
package cn.iocoder.yudao.module.bpm.service.task; 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.ArrayUtil; import cn.hutool.core.util.*;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
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;
@ -28,6 +26,7 @@ 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.BpmnModel; import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService; import org.flowable.engine.HistoryService;
@ -54,6 +53,8 @@ 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 实现类
@ -326,18 +327,40 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (instance == null) { if (instance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS); throw exception(PROCESS_INSTANCE_NOT_EXISTS);
} }
// 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.1 如果是被加签任务且是后加签 更新加签任务状态为取消
if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) {
List<Task> childTaskList = getTaskListByParentTaskId(task.getId());
updateTaskStatusWhenCanceled(childTaskList, reqVO.getReason());
}
// 3.2 如果是加签的任务
if (StrUtil.isNotEmpty(task.getParentTaskId())) {
Task signTask = validateTaskExist(task.getParentTaskId());
// 3.2.1 更新被加签的任务为不通过
if (BpmTaskSignTypeEnum.BEFORE.getType().equals(signTask.getScopeType())) {
updateTaskStatusAndReason(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus(), AUTO_REJECT_BY_ADD_SIGN_REJECT.getReason());
} else if (BpmTaskSignTypeEnum.AFTER.getType().equals(signTask.getScopeType())) {
updateTaskStatus(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus());
// 后加签 不添加拒绝意见因为会把原来的意见覆盖.
}
// 3.2.2 添加评论
taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(),
BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(REJECT_BY_ADD_SIGN_TASK_REJECT));
// 3.2.3 更新还在进行中的加签任务状态为取消
List<Task> addSignTaskList = getTaskListByParentTaskId(task.getParentTaskId());
updateTaskStatusWhenCanceled(CollectionUtils.filterList(addSignTaskList, item -> !item.getId().equals(task.getId())),
reqVO.getReason());
}
// 4.1 驳回到指定的任务节点
BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement);
// 3.2 类型为驳回到指定的任务节点
if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement);
Assert.notNull(returnTaskId, "回退的节点不能为空"); Assert.notNull(returnTaskId, "回退的节点不能为空");
@ -346,9 +369,25 @@ public class BpmTaskServiceImpl implements BpmTaskService {
returnTask(userId, returnReq); returnTask(userId, returnReq);
return; return;
} }
// 3.3 其他情况 终止流程
processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), // 4.2.1 更新其它正在运行的任务状态为取消需要过滤掉当前任务和被加签的任务
task.getTaskDefinitionKey(), reqVO.getReason()); 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 终止流程
List<String> activityIds = convertList(taskList, Task::getTaskDefinitionKey);
EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
Assert.notNull(endEvent, "结束节点不能未空");
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));
});
} }
/** /**
@ -399,6 +438,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
@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) {
@ -450,10 +490,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
@Override @Override
public List<Task> getTaskListByProcessInstanceIdAndAssigned(String processInstanceId, String executionId, String defineKey) { public List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String executionId, String defineKey) {
Assert.notNull(processInstanceId, "processInstanceId 不能为空"); Assert.notNull(processInstanceId, "processInstanceId 不能为空");
TaskQuery taskQuery = taskService.createTaskQuery().taskAssigned().processInstanceId(processInstanceId).active() TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active()
.includeTaskLocalVariables(); .includeTaskLocalVariables();
if (BooleanUtil.isTrue(assigned)) {
taskQuery.taskAssigned();
}
if (StrUtil.isNotEmpty(executionId)) { if (StrUtil.isNotEmpty(executionId)) {
taskQuery.executionId(executionId); taskQuery.executionId(executionId);
} }