Commit 521cc3deb4bd9296f623708c06034ea6848db1bc

Authored by YunaiV
1 parent 5bafc0ce

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

yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -75,8 +75,4 @@ public interface ErrorCodeConstants { @@ -75,8 +75,4 @@ public interface ErrorCodeConstants {
75 // ========== BPM 流程表达式 1-009-014-000 ========== 75 // ========== BPM 流程表达式 1-009-014-000 ==========
76 ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); 76 ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在");
77 77
78 - // ========== BPM 仿钉钉流程设计器 1-009-015-000 ==========  
79 - // TODO @芋艿:这个错误码,需要关注下  
80 - ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程");  
81 -  
82 } 78 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java
@@ -22,8 +22,6 @@ public enum BpmCommentTypeEnum { @@ -22,8 +22,6 @@ public enum BpmCommentTypeEnum {
22 TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"), 22 TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"),
23 ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"), 23 ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"),
24 SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), 24 SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"),
25 - // TODO @芋艿:这个枚举状态,需要关注下!  
26 - REJECT_BY_ADD_SIGN_TASK_REJECT("10", "不通过","系统自动不通过,原因是:加签任务不通过")  
27 ; 25 ;
28 26
29 /** 27 /**
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java
@@ -22,8 +22,6 @@ public enum BpmDeleteReasonEnum { @@ -22,8 +22,6 @@ public enum BpmDeleteReasonEnum {
22 // ========== 流程任务的独有原因 ========== 22 // ========== 流程任务的独有原因 ==========
23 23
24 CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 24 CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等
25 - // TODO @芋艿:这个枚举状态,需要关注下!  
26 - AUTO_REJECT_BY_ADD_SIGN_REJECT("系统自动拒绝,原因:加签任务被拒绝") // 加签任务审批不通过,导致任务不通过  
27 ; 25 ;
28 26
29 private final String reason; 27 private final String reason;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
@@ -59,8 +59,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -59,8 +59,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
59 @Override 59 @Override
60 protected void activityCancelled(FlowableActivityCancelledEvent event) { 60 protected void activityCancelled(FlowableActivityCancelledEvent event) {
61 // TODO @jason:如果用户主动取消,可能需要考虑这个 61 // TODO @jason:如果用户主动取消,可能需要考虑这个
62 - // TODO @芋艿 如果在 rejectTask 处理了。 这里又要查询一次。感觉有点多余。 主动取消是不是也要处理一下。要不然有加签的任务也会报错  
63 - // @芋艿。 这里是不是就可以不要了, 取消的任务状态,在rejectTask 里面做了, 如果在 updateTaskStatusWhenCanceled 里面修改会报错。  
64 List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); 62 List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId());
65 if (CollUtil.isEmpty(activityList)) { 63 if (CollUtil.isEmpty(activityList)) {
66 log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); 64 log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
@@ -10,6 +10,7 @@ import org.flowable.engine.delegate.event.FlowableCancelledEvent; @@ -10,6 +10,7 @@ import org.flowable.engine.delegate.event.FlowableCancelledEvent;
10 import org.flowable.engine.history.HistoricProcessInstance; 10 import org.flowable.engine.history.HistoricProcessInstance;
11 import org.flowable.engine.runtime.ProcessInstance; 11 import org.flowable.engine.runtime.ProcessInstance;
12 12
  13 +import java.util.Collection;
13 import java.util.List; 14 import java.util.List;
14 import java.util.Map; 15 import java.util.Map;
15 import java.util.Set; 16 import java.util.Set;
@@ -141,7 +142,7 @@ public interface BpmProcessInstanceService { @@ -141,7 +142,7 @@ public interface BpmProcessInstanceService {
141 * @param endId 结束节点 Id 142 * @param endId 结束节点 Id
142 * @param reason 理由。例如说,审批不通过时,需要传递该值 143 * @param reason 理由。例如说,审批不通过时,需要传递该值
143 */ 144 */
144 - void updateProcessInstanceReject(ProcessInstance processInstance, List<String> activityIds, String endId, String reason); 145 + void updateProcessInstanceReject(ProcessInstance processInstance, Collection<String> activityIds, String endId, String reason);
145 146
146 /** 147 /**
147 * 当流程结束时候,更新 ProcessInstance 为通过 148 * 当流程结束时候,更新 ProcessInstance 为通过
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1 -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; 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.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // TODO @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, List<String> activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } }  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; 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.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // TODO @芋艿 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(ProcessInstance processInstance, Collection<String> activityIds, String endId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(CollUtil.newArrayList(activityIds), endId) .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } }
3 \ No newline at end of file 3 \ No newline at end of file
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
@@ -133,9 +133,9 @@ public interface BpmTaskService { @@ -133,9 +133,9 @@ public interface BpmTaskService {
133 * 根据条件查询正在进行中的任务 133 * 根据条件查询正在进行中的任务
134 * 134 *
135 * @param processInstanceId 流程实例编号,不允许为空 135 * @param processInstanceId 流程实例编号,不允许为空
136 - * @param assigned 是否分配了审批人  
137 - * @param executionId execution Id  
138 - * @param taskDefineKey 任务定义 Key 136 + * @param assigned 是否分配了审批人,允许空
  137 + * @param executionId execution Id,允许空
  138 + * @param taskDefineKey 任务定义 Key,允许空
139 */ 139 */
140 List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, 140 List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId,
141 Boolean assigned, 141 Boolean assigned,
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.service.task; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.service.task;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.*; 4 import cn.hutool.core.util.*;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
7 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 6 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 import cn.iocoder.yudao.framework.common.util.object.PageUtils; 8 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
@@ -53,8 +52,6 @@ import java.util.stream.Stream; @@ -53,8 +52,6 @@ import java.util.stream.Stream;
53 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 52 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
54 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 53 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
55 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 54 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
56 -import static cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum.REJECT_BY_ADD_SIGN_TASK_REJECT;  
57 -import static cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum.AUTO_REJECT_BY_ADD_SIGN_REJECT;  
58 55
59 /** 56 /**
60 * 流程任务实例 Service 实现类 57 * 流程任务实例 Service 实现类
@@ -329,35 +326,41 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -329,35 +326,41 @@ public class BpmTaskServiceImpl implements BpmTaskService {
329 if (instance == null) { 326 if (instance == null) {
330 throw exception(PROCESS_INSTANCE_NOT_EXISTS); 327 throw exception(PROCESS_INSTANCE_NOT_EXISTS);
331 } 328 }
  329 +
  330 + // 2. 处理当前任务
332 // 2.1 更新流程任务为不通过 331 // 2.1 更新流程任务为不通过
333 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); 332 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason());
334 // 2.2 添加评论 333 // 2.2 添加评论
335 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), 334 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
336 BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); 335 BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
337 336
338 - // 3.1 如果是被加签任务且是后加签。 更新加签任务状态为取消  
339 - if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) {  
340 - List<Task> childTaskList = getTaskListByParentTaskId(task.getId());  
341 - updateTaskStatusWhenCanceled(childTaskList, reqVO.getReason());  
342 - }  
343 - // 3.2 如果是加签的任务  
344 - if (StrUtil.isNotEmpty(task.getParentTaskId())) {  
345 - Task signTask = validateTaskExist(task.getParentTaskId());  
346 - // 3.2.1 更新被加签的任务为不通过  
347 - if (BpmTaskSignTypeEnum.BEFORE.getType().equals(signTask.getScopeType())) {  
348 - updateTaskStatusAndReason(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus(), AUTO_REJECT_BY_ADD_SIGN_REJECT.getReason());  
349 - } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(signTask.getScopeType())) {  
350 - updateTaskStatus(task.getParentTaskId(), BpmTaskStatusEnum.REJECT.getStatus());  
351 - // 后加签 不添加拒绝意见。因为会把原来的意见覆盖. 337 + // 3. 处理其他进行中的任务
  338 + // 3.1 如果当前任务时被加签的,则加它的根任务也标记成未通过
  339 + // 疑问:为什么要标记未通过呢?
  340 + // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。
  341 + if (task.getParentTaskId() != null) {
  342 + String rootParentId = getTaskRootParentId(task);
  343 + updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(),
  344 + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
  345 + taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
  346 + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
  347 + }
  348 + // 3.2 其它未结束的任务,直接取消
  349 + // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢?
  350 + // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景
  351 + List<Task> taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), null, null, null);
  352 + taskList.forEach(otherTask -> {
  353 + if (!otherTask.getId().equals(task.getId())) { // 不需要处理当前任务
  354 + return;
352 } 355 }
353 - // 3.2.2 添加评论  
354 - taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(),  
355 - BpmCommentTypeEnum.REJECT.getType(), REJECT_BY_ADD_SIGN_TASK_REJECT.getComment());  
356 - // 3.2.3 更新还在进行中的加签任务状态为取消  
357 - List<Task> addSignTaskList = getTaskListByParentTaskId(task.getParentTaskId());  
358 - updateTaskStatusWhenCanceled(CollectionUtils.filterList(addSignTaskList, item -> !item.getId().equals(task.getId())),  
359 - reqVO.getReason());  
360 - } 356 + Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
  357 + if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) {
  358 + return;
  359 + }
  360 + updateTaskStatusWhenCanceled(otherTask.getId());
  361 + });
  362 + taskList.stream().filter(otherTask -> !otherTask.getId().equals(task.getId())) // 需要排除当前任务
  363 + .forEach(otherTask -> updateTaskStatusWhenCanceled(otherTask.getId()));
361 364
362 // 4.1 驳回到指定的任务节点 365 // 4.1 驳回到指定的任务节点
363 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); 366 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
@@ -372,26 +375,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -372,26 +375,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
372 return; 375 return;
373 } 376 }
374 377
375 - // 4.2.1 更新其它正在运行的任务状态为取消。需要过滤掉当前任务和被加签的任务  
376 - // TODO @jason:如果过滤掉被加签的任务,这些任务被对应的审批人看到是啥状态哈? @芋艿 为不通过状态。  
377 - List<Task> taskList = getRunningTaskListByProcessInstanceId(instance.getProcessInstanceId(), false, null, null);  
378 - updateTaskStatusWhenCanceled(  
379 - CollectionUtils.filterList(taskList, item -> !item.getId().equals(task.getId()) && !item.getId().equals(task.getParentTaskId())),  
380 - reqVO.getReason());  
381 - // 4.2.2 终止流程 378 + // 4.2 终止流程
382 Set<String> activityIds = convertSet(taskList, Task::getTaskDefinitionKey); 379 Set<String> activityIds = convertSet(taskList, Task::getTaskDefinitionKey);
383 EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); 380 EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
384 Assert.notNull(endEvent, "结束节点不能未空"); 381 Assert.notNull(endEvent, "结束节点不能未空");
385 - processInstanceService.updateProcessInstanceReject(instance, CollUtil.newArrayList(activityIds), endEvent.getId(), reqVO.getReason());  
386 - }  
387 -  
388 - private void updateTaskStatusWhenCanceled(List<Task> taskList, String reason) {  
389 - taskList.forEach(task -> {  
390 - updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason());  
391 - taskService.addComment(task.getId(), task.getProcessInstanceId(),  
392 - BpmCommentTypeEnum.CANCEL.getType(), BpmCommentTypeEnum.CANCEL.formatComment(reason));  
393 -  
394 - }); 382 + processInstanceService.updateProcessInstanceReject(instance, activityIds, endEvent.getId(), reqVO.getReason());
395 } 383 }
396 384
397 /** 385 /**
@@ -440,9 +428,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -440,9 +428,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
440 updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); 428 updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
441 } 429 }
442 430
  431 + /**
  432 + * 重要补充说明:该方法目前主要有两个情况会调用到:
  433 + *
  434 + * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消
  435 + * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消
  436 + */
443 @Override 437 @Override
444 public void updateTaskStatusWhenCanceled(String taskId) { 438 public void updateTaskStatusWhenCanceled(String taskId) {
445 - // @芋艿。这里是不是可以不要了,要不然。 updateTaskStatusAndReason 会报错 task 已经删除了。  
446 Task task = getTask(taskId); 439 Task task = getTask(taskId);
447 // 1. 可能只是活动,不是任务,所以查询不到 440 // 1. 可能只是活动,不是任务,所以查询不到
448 if (task == null) { 441 if (task == null) {
@@ -500,6 +493,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -500,6 +493,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
500 .includeTaskLocalVariables(); 493 .includeTaskLocalVariables();
501 if (BooleanUtil.isTrue(assigned)) { 494 if (BooleanUtil.isTrue(assigned)) {
502 taskQuery.taskAssigned(); 495 taskQuery.taskAssigned();
  496 + } else if (BooleanUtil.isFalse(assigned)) {
  497 + taskQuery.taskUnassigned();
503 } 498 }
504 if (StrUtil.isNotEmpty(executionId)) { 499 if (StrUtil.isNotEmpty(executionId)) {
505 taskQuery.executionId(executionId); 500 taskQuery.executionId(executionId);
@@ -888,6 +883,29 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -888,6 +883,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
888 return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); 883 return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
889 } 884 }
890 885
  886 + /**
  887 + * 获得任务根任务的父任务编号
  888 + *
  889 + * @param task 任务
  890 + * @return 根任务的父任务编号
  891 + */
  892 + private String getTaskRootParentId(Task task) {
  893 + if (task == null || task.getParentTaskId() == null) {
  894 + return null;
  895 + }
  896 + for (int i = 0; i < Short.MAX_VALUE; i++) {
  897 + Task parentTask = getTask(task.getParentTaskId());
  898 + if (parentTask == null) {
  899 + return null;
  900 + }
  901 + if (parentTask.getParentTaskId() == null) {
  902 + return parentTask.getId();
  903 + }
  904 + task = parentTask;
  905 + }
  906 + throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId()));
  907 + }
  908 +
891 @Override 909 @Override
892 public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) { 910 public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) {
893 if (CollUtil.isEmpty(taskIds)) { 911 if (CollUtil.isEmpty(taskIds)) {