iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -2,23 +2,28 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.iailab.framework.common.enums.CommonStatusEnum;
import com.iailab.framework.common.util.object.ObjectUtils;
import com.iailab.framework.datapermission.core.annotation.DataPermission;
import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG;
@@ -57,7 +62,14 @@
        List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        // 遍历所有的 UserTask,校验审批人配置
        userTaskList.forEach(userTask -> {
            // 1. 非空校验
            // 1.1 非人工审批,无需校验审批人配置
            Integer approveType = BpmnModelUtils.parseApproveType(userTask);
            if (ObjectUtils.equalsAny(approveType,
                    BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
                    BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
                return;
            }
            // 1.2 非空校验
            Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
            String param = BpmnModelUtils.parseCandidateParam(userTask);
            if (strategy == null) {
@@ -79,20 +91,66 @@
     * @return 用户编号集合
     */
    @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人
    public Set<Long> calculateUsers(DelegateExecution execution) {
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
        String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
    public Set<Long> calculateUsersByTask(DelegateExecution execution) {
        // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
        FlowElement flowElement = execution.getCurrentFlowElement();
        Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
        if (ObjectUtils.equalsAny(approveType,
                BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
                BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
            return new HashSet<>();
        }
        // 1.1 计算任务的候选人
        Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
        String param = BpmnModelUtils.parseCandidateParam(flowElement);
        Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param);
        // 1.2 移除被禁用的用户
        removeDisableUsers(userIds);
        // 2. 校验是否有候选人
        // 2. 候选人为空时,根据“审批人为空”的配置补充
        if (CollUtil.isEmpty(userIds)) {
            log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
                    execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
            throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
            userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
                    .calculateUsersByTask(execution, param);
            // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
        }
        // 3. 移除发起人的用户
        ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
                .getProcessInstance(execution.getProcessInstanceId());
        Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
        removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId()));
        return userIds;
    }
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
        FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
        Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
        if (ObjectUtils.equalsAny(approveType,
                BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
                BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
            return new HashSet<>();
        }
        // 1.1 计算任务的候选人
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
        String param = BpmnModelUtils.parseCandidateParam(flowElement);
        Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param,
                startUserId, processDefinitionId, processVariables);
        // 1.2 移除被禁用的用户
        removeDisableUsers(userIds);
        // 2. 候选人为空时,根据“审批人为空”的配置补充
        if (CollUtil.isEmpty(userIds)) {
            userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
                    .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables);
            // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
        }
        // 3. 移除发起人的用户
        removeStartUserIfSkip(userIds, flowElement, startUserId);
        return userIds;
    }
@@ -104,10 +162,31 @@
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
        assigneeUserIds.removeIf(id -> {
            AdminUserRespDTO user = userMap.get(id);
            return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
            return user == null || CommonStatusEnum.isDisable(user.getStatus());
        });
    }
    /**
     * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人
     *
     * 注意:如果只有一个候选人,则不处理,避免无法审批
     *
     * @param assigneeUserIds 当前分配的候选人
     * @param flowElement 当前节点
     * @param startUserId 发起人
     */
    @VisibleForTesting
    void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) {
        if (CollUtil.size(assigneeUserIds) <= 1) {
            return;
        }
        Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement);
        if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
            return;
        }
        assigneeUserIds.remove(startUserId);
    }
    private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
        BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
        Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);