Commit 40db4cf7b7f3eda04ba7665593d2c866e2ce2604
1 parent
71e42cb0
【功能新增】审批节点的审批人与发起人相同时,具体实现处理。
Showing
4 changed files
with
83 additions
and
13 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java
| @@ -15,7 +15,7 @@ public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuabl | @@ -15,7 +15,7 @@ public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuabl | ||
| 15 | 15 | ||
| 16 | START_USER_AUDIT(1), // 由发起人对自己审批 | 16 | START_USER_AUDIT(1), // 由发起人对自己审批 |
| 17 | SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 | 17 | SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 |
| 18 | - ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批 | 18 | + ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 |
| 19 | 19 | ||
| 20 | private final Integer type; | 20 | private final Integer type; |
| 21 | 21 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
| @@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; | @@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.lang.Assert; | 4 | import cn.hutool.core.lang.Assert; |
| 5 | +import cn.hutool.core.util.ObjectUtil; | ||
| 5 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 8 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 7 | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; | 9 | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; |
| 10 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | ||
| 8 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | 11 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 9 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 13 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | ||
| 10 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | 14 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 11 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | 15 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 12 | import com.google.common.annotations.VisibleForTesting; | 16 | import com.google.common.annotations.VisibleForTesting; |
| @@ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; | ||
| 14 | import org.flowable.bpmn.model.BpmnModel; | 18 | import org.flowable.bpmn.model.BpmnModel; |
| 15 | import org.flowable.bpmn.model.UserTask; | 19 | import org.flowable.bpmn.model.UserTask; |
| 16 | import org.flowable.engine.delegate.DelegateExecution; | 20 | import org.flowable.engine.delegate.DelegateExecution; |
| 21 | +import org.flowable.engine.runtime.ProcessInstance; | ||
| 17 | 22 | ||
| 18 | import java.util.HashMap; | 23 | import java.util.HashMap; |
| 19 | import java.util.List; | 24 | import java.util.List; |
| @@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker { | @@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker { | ||
| 86 | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); | 91 | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); |
| 87 | // 1.2 移除被禁用的用户 | 92 | // 1.2 移除被禁用的用户 |
| 88 | removeDisableUsers(userIds); | 93 | removeDisableUsers(userIds); |
| 94 | + // 1.3 移除发起人的用户 | ||
| 95 | + removeStartUserIfSkip(execution, userIds); | ||
| 89 | 96 | ||
| 90 | // 2. 校验是否有候选人 | 97 | // 2. 校验是否有候选人 |
| 91 | if (CollUtil.isEmpty(userIds)) { | 98 | if (CollUtil.isEmpty(userIds)) { |
| @@ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker { | @@ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker { | ||
| 108 | }); | 115 | }); |
| 109 | } | 116 | } |
| 110 | 117 | ||
| 118 | + /** | ||
| 119 | + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 | ||
| 120 | + * | ||
| 121 | + * 注意:如果只有一个候选人,则不处理,避免无法审批 | ||
| 122 | + * | ||
| 123 | + * @param execution 执行中的任务 | ||
| 124 | + * @param assigneeUserIds 当前分配的候选人 | ||
| 125 | + */ | ||
| 126 | + @VisibleForTesting | ||
| 127 | + void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) { | ||
| 128 | + if (CollUtil.size(assigneeUserIds) <= 1) { | ||
| 129 | + return; | ||
| 130 | + } | ||
| 131 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); | ||
| 132 | + if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | ||
| 133 | + return; | ||
| 134 | + } | ||
| 135 | + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) | ||
| 136 | + .getProcessInstance(execution.getProcessInstanceId()); | ||
| 137 | + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); | ||
| 138 | + assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); | ||
| 139 | + } | ||
| 140 | + | ||
| 111 | private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { | 141 | private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { |
| 112 | BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); | 142 | BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); |
| 113 | Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); | 143 | Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| @@ -53,6 +53,10 @@ public class BpmnModelUtils { | @@ -53,6 +53,10 @@ public class BpmnModelUtils { | ||
| 53 | return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); | 53 | return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | + public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { | ||
| 57 | + return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); | ||
| 58 | + } | ||
| 59 | + | ||
| 56 | public static String parseExtensionElement(FlowElement flowElement, String elementName) { | 60 | public static String parseExtensionElement(FlowElement flowElement, String elementName) { |
| 57 | if (flowElement == null) { | 61 | if (flowElement == null) { |
| 58 | return null; | 62 | return null; |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| 1 | package cn.iocoder.yudao.module.bpm.service.task; | 1 | package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.lang.Assert; | ||
| 4 | import cn.hutool.core.util.*; | 5 | import cn.hutool.core.util.*; |
| 5 | import cn.hutool.extra.spring.SpringUtil; | 6 | import cn.hutool.extra.spring.SpringUtil; |
| 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; | 8 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 8 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | 9 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 10 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||
| 9 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; | 11 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 10 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | 12 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| 11 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | 13 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 12 | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | 14 | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; |
| 15 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | ||
| 13 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; | 16 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; |
| 14 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; | 17 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; |
| 15 | import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; | 18 | import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; |
| @@ -22,6 +25,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | @@ -22,6 +25,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | ||
| 22 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | 25 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |
| 23 | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; | 26 | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; |
| 24 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | 27 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; |
| 28 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | ||
| 29 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | ||
| 25 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | 30 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 26 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | 31 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 27 | import jakarta.annotation.Resource; | 32 | import jakarta.annotation.Resource; |
| @@ -47,7 +52,6 @@ import org.springframework.stereotype.Service; | @@ -47,7 +52,6 @@ import org.springframework.stereotype.Service; | ||
| 47 | import org.springframework.transaction.annotation.Transactional; | 52 | import org.springframework.transaction.annotation.Transactional; |
| 48 | import org.springframework.transaction.support.TransactionSynchronization; | 53 | import org.springframework.transaction.support.TransactionSynchronization; |
| 49 | import org.springframework.transaction.support.TransactionSynchronizationManager; | 54 | import org.springframework.transaction.support.TransactionSynchronizationManager; |
| 50 | -import org.springframework.util.Assert; | ||
| 51 | 55 | ||
| 52 | import java.util.*; | 56 | import java.util.*; |
| 53 | import java.util.stream.Stream; | 57 | import java.util.stream.Stream; |
| @@ -86,6 +90,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -86,6 +90,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 86 | 90 | ||
| 87 | @Resource | 91 | @Resource |
| 88 | private AdminUserApi adminUserApi; | 92 | private AdminUserApi adminUserApi; |
| 93 | + @Resource | ||
| 94 | + private DeptApi deptApi; | ||
| 89 | 95 | ||
| 90 | // ========== Query 查询相关方法 ========== | 96 | // ========== Query 查询相关方法 ========== |
| 91 | 97 | ||
| @@ -500,11 +506,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -500,11 +506,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 500 | 506 | ||
| 501 | // 3. 根据不同的 RejectHandler 处理策略 | 507 | // 3. 根据不同的 RejectHandler 处理策略 |
| 502 | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | 508 | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |
| 503 | - FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | 509 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| 504 | // 3.1 情况一:驳回到指定的任务节点 | 510 | // 3.1 情况一:驳回到指定的任务节点 |
| 505 | - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); | 511 | + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); |
| 506 | if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { | 512 | if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { |
| 507 | - String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); | 513 | + String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); |
| 508 | Assert.notNull(returnTaskId, "回退的节点不能为空"); | 514 | Assert.notNull(returnTaskId, "回退的节点不能为空"); |
| 509 | returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) | 515 | returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) |
| 510 | .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); | 516 | .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); |
| @@ -924,17 +930,47 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -924,17 +930,47 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 924 | log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); | 930 | log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); |
| 925 | return; | 931 | return; |
| 926 | } | 932 | } |
| 927 | - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); | ||
| 928 | - if (bpmnModel == null) { | ||
| 929 | - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); | ||
| 930 | - return; | ||
| 931 | - } | ||
| 932 | 933 | ||
| 933 | // 审批人与提交人为同一人时,根据策略进行处理 | 934 | // 审批人与提交人为同一人时,根据策略进行处理 |
| 934 | if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { | 935 | if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { |
| 935 | - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | ||
| 936 | - .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); | ||
| 937 | - return; | 936 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); |
| 937 | + if (bpmnModel == null) { | ||
| 938 | + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); | ||
| 939 | + return; | ||
| 940 | + } | ||
| 941 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | ||
| 942 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); | ||
| 943 | + | ||
| 944 | + // 情况一:自动跳过 | ||
| 945 | + if (ObjectUtils.equalsAny(assignStartUserHandlerType, | ||
| 946 | + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | ||
| 947 | + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | ||
| 948 | + .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); | ||
| 949 | + return; | ||
| 950 | + } | ||
| 951 | + // 情况二:转交给部门负责人审批 | ||
| 952 | + if (ObjectUtils.equalsAny(assignStartUserHandlerType, | ||
| 953 | + BpmUserTaskAssignStartUserHandlerTypeEnum.ASSIGN_DEPT_LEADER.getType())) { | ||
| 954 | + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | ||
| 955 | + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); | ||
| 956 | + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; | ||
| 957 | + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); | ||
| 958 | + // 找不到部门负责人的情况下,自动审批通过 | ||
| 959 | + // noinspection DataFlowIssue | ||
| 960 | + if (dept.getLeaderUserId() == null) { | ||
| 961 | + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | ||
| 962 | + .setId(task.getId()).setReason("审批人与提交人为同一人时,找不到部门负责人,自动通过")); | ||
| 963 | + return; | ||
| 964 | + } | ||
| 965 | + // 找得到部门负责人的情况下,修改负责人 | ||
| 966 | + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { | ||
| 967 | + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() | ||
| 968 | + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) | ||
| 969 | + .setReason("审批人与提交人为同一人时,转交给部门负责人审批")); | ||
| 970 | + return; | ||
| 971 | + } | ||
| 972 | + // 如果部门负责人是自己,还是自己审批吧~ | ||
| 973 | + } | ||
| 938 | } | 974 | } |
| 939 | 975 | ||
| 940 | AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | 976 | AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); |