【功能重构】工作流:重构流程不通过、取消的处理逻辑,完全转向 flowable 的 moveActivityIdsToSingleActivityId API

This commit is contained in:
YunaiV 2024-08-11 19:01:56 +08:00
parent 521cc3deb4
commit 32804d3e0b
8 changed files with 66 additions and 82 deletions

View File

@ -5,13 +5,13 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
/** /**
* 流程实例/任务的删除原因枚举 * 流程实例/任务的的处理原因枚举
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum BpmDeleteReasonEnum { public enum BpmReasonEnum {
// ========== 流程实例的独有原因 ========== // ========== 流程实例的独有原因 ==========
@ -36,10 +36,4 @@ public enum BpmDeleteReasonEnum {
return StrUtil.format(reason, args); return StrUtil.format(reason, args);
} }
// ========== 逻辑 ==========
public static boolean isRejectReason(String reason) {
return StrUtil.startWith(reason, "审批不通过任务,原因:");
}
} }

View File

@ -89,11 +89,6 @@ public interface BpmProcessInstanceConvert {
@Mapping(source = "from.id", target = "to.id", ignore = true) @Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, HistoricProcessInstance instance, Integer status) {
return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
.setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
}
default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {; default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {;
return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
.setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());

View File

@ -15,6 +15,14 @@ public class BpmConstants {
* @see ProcessInstance#getProcessVariables() * @see ProcessInstance#getProcessVariables()
*/ */
public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS";
/**
* 流程实例的变量 - 理由
*
* 例如说审批不通过的理由目前审核通过暂时不会记录
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON";
/** /**
* 流程实例的变量 - 发起用户选择的审批人 Map * 流程实例的变量 - 发起用户选择的审批人 Map
* *

View File

@ -6,7 +6,6 @@ import jakarta.annotation.Resource;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; 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.FlowableCancelledEvent;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,24 +20,18 @@ import java.util.Set;
@Component @Component
public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
@Resource
@Lazy
private BpmProcessInstanceService processInstanceService;
public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
.add(FlowableEngineEventType.PROCESS_CANCELLED)
.add(FlowableEngineEventType.PROCESS_COMPLETED) .add(FlowableEngineEventType.PROCESS_COMPLETED)
.build(); .build();
@Resource
@Lazy // 延迟加载避免循环依赖
private BpmProcessInstanceService processInstanceService;
public BpmProcessInstanceEventListener(){ public BpmProcessInstanceEventListener(){
super(PROCESS_INSTANCE_EVENTS); super(PROCESS_INSTANCE_EVENTS);
} }
@Override
protected void processCancelled(FlowableCancelledEvent event) {
processInstanceService.updateProcessInstanceWhenCancel(event);
}
@Override @Override
protected void processCompleted(FlowableEngineEntityEvent event) { protected void processCompleted(FlowableEngineEntityEvent event) {
processInstanceService.updateProcessInstanceWhenCompleted((ProcessInstance)event.getEntity()); processInstanceService.updateProcessInstanceWhenCompleted((ProcessInstance)event.getEntity());

View File

@ -6,11 +6,9 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
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;
@ -120,32 +118,16 @@ public interface BpmProcessInstanceService {
*/ */
void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO);
/**
* 更新 ProcessInstance 拓展记录为取消
*
* @param event 流程取消事件
*/
void updateProcessInstanceWhenCancel(FlowableCancelledEvent event);
/**
* 更新 ProcessInstance 为完成
*
* @param instance 流程任务
*/
void updateProcessInstanceWhenApprove(ProcessInstance instance);
/** /**
* 更新 ProcessInstance 为不通过 * 更新 ProcessInstance 为不通过
* *
* @param processInstance 流程实例 * @param processInstance 流程实例
* @param activityIds 当前未完成活动节点 Id
* @param endId 结束节点 Id
* @param reason 理由例如说审批不通过时需要传递该值 * @param reason 理由例如说审批不通过时需要传递该值
*/ */
void updateProcessInstanceReject(ProcessInstance processInstance, Collection<String> activityIds, String endId, String reason); void updateProcessInstanceReject(ProcessInstance processInstance, String reason);
/** /**
* 当流程结束时候更新 ProcessInstance 为通过 * 处理 ProcessInstance 完成审批通过不通过取消
* *
* @param instance 流程任务 * @param instance 流程任务
*/ */

View File

@ -90,8 +90,6 @@ public interface BpmTaskService {
*/ */
void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO);
/** /**
* 将流程任务分配给指定用户 * 将流程任务分配给指定用户
* *
@ -100,6 +98,13 @@ public interface BpmTaskService {
*/ */
void transferTask(Long userId, BpmTaskTransferReqVO reqVO); void transferTask(Long userId, BpmTaskTransferReqVO reqVO);
/**
* 将指定流程实例的进行中的流程任务移动到结束节点
*
* @param processInstanceId 流程编号
*/
void moveTaskToEnd(String processInstanceId);
/** /**
* 更新 Task 状态在创建时 * 更新 Task 状态在创建时
* *

View File

@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
@ -327,15 +327,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
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()));
// 2.3 如果当前任务时被加签的则加它的根任务也标记成未通过
// 3. 处理其他进行中的任务
// 3.1 如果当前任务时被加签的则加它的根任务也标记成未通过
// 疑问为什么要标记未通过呢 // 疑问为什么要标记未通过呢
// 回答例如说 A 任务被向前加签除 B 任务时B 任务被审批不通过此时 A 会被取消 yudao-ui-admin-vue3 不展示已取消的任务导致展示不出审批不通过的细节 // 回答例如说 A 任务被向前加签除 B 任务时B 任务被审批不通过此时 A 会被取消 yudao-ui-admin-vue3 不展示已取消的任务导致展示不出审批不通过的细节
if (task.getParentTaskId() != null) { if (task.getParentTaskId() != null) {
@ -345,41 +342,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
} }
// 3.2 其它未结束的任务直接取消
// 疑问为什么不通过 updateTaskStatusWhenCanceled 监听取消而是直接提前调用呢
// 回答详细见 updateTaskStatusWhenCanceled 的方法加签的场景
List<Task> taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), null, null, null);
taskList.forEach(otherTask -> {
if (!otherTask.getId().equals(task.getId())) { // 不需要处理当前任务
return;
}
Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) {
return;
}
updateTaskStatusWhenCanceled(otherTask.getId());
});
taskList.stream().filter(otherTask -> !otherTask.getId().equals(task.getId())) // 需要排除当前任务
.forEach(otherTask -> updateTaskStatusWhenCanceled(otherTask.getId()));
// 4.1 驳回到指定的任务节点 // 3. 根据不同的 RejectHandler 处理策略
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());
// 3.1 情况一驳回到指定的任务节点
BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement);
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, "回退的节点不能为空");
BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId) returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
.setReason(reqVO.getReason()); .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
returnTask(userId, returnReq);
return; return;
} }
// 3.2 情况二直接结束审批不通过
// 4.2 终止流程 processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason()); // 标记不通过
Set<String> activityIds = convertSet(taskList, Task::getTaskDefinitionKey); moveTaskToEnd(task.getProcessInstanceId()); // 结束流程
EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
Assert.notNull(endEvent, "结束节点不能未空");
processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason());
} }
/** /**
@ -449,7 +427,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status);
return; return;
} }
updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason());
// 补充说明由于 Task 被删除成 HistoricTask 无法通过 taskService.addComment 添加理由所以无法存储具体的取消理由 // 补充说明由于 Task 被删除成 HistoricTask 无法通过 taskService.addComment 添加理由所以无法存储具体的取消理由
} }
@ -665,6 +643,35 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());
} }
@Override
public void moveTaskToEnd(String processInstanceId) {
List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null, null);
if (CollUtil.isEmpty(taskList)) {
return;
}
// 1. 其它未结束的任务直接取消
// 疑问为什么不通过 updateTaskStatusWhenCanceled 监听取消而是直接提前调用呢
// 回答详细见 updateTaskStatusWhenCanceled 的方法加签的场景
taskList.forEach(task -> {
Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) {
return;
}
updateTaskStatusWhenCanceled(task.getId());
});
// 2. 终止流程
BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId());
List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey));
EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
Assert.notNull(endEvent, "结束节点不能未空");
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveActivityIdsToSingleActivityId(activityIds, endEvent.getId())
.changeState();
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) { public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) {