Browse Source

【功能新增】工作流:支持审批人为空时,根据配置进行自动通过、自动拒绝、指定人审批的效果

YunaiV 11 months ago
parent
commit
0d738fa397
13 changed files with 197 additions and 22 deletions
  1. 29 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java
  2. 2 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
  3. 20 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  4. 6 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
  5. 6 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
  6. 35 3
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
  7. 7 9
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
  8. 54 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java
  9. 1 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
  10. 9 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
  11. 12 3
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
  12. 12 6
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  13. 4 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

+ 29 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * BPM 用户任务的审批人为空时,处理类型枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable {
+
+    APPROVE(1), // 自动通过
+    REJECT(2), // 自动拒绝
+    ASSIGN_USER(3), // 指定人员审批
+    ASSIGN_ADMIN(4), // 转交给流程管理员
+    ;
+
+    private final Integer type;
+
+    @Override
+    public int[] array() {
+        return new int[0];
+    }
+
+}

+ 2 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java

@@ -28,6 +28,8 @@ public enum BpmReasonEnum {
     ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
     ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
     ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
+    ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
+    ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"),
     ;
 
     private final String reason;

+ 20 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java

@@ -86,6 +86,11 @@ public class BpmSimpleModelNodeVO {
     @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
     private Integer assignStartUserHandlerType;
 
+    /**
+     * 空处理策略
+     */
+    private AssignEmptyHandler assignEmptyHandler;
+
     @Data
     @Schema(description = "审批节点拒绝处理策略")
     public static class RejectHandler {
@@ -121,6 +126,21 @@ public class BpmSimpleModelNodeVO {
 
     }
 
+    @Data
+    @Schema(description = "空处理策略")
+    @Valid
+    public static class AssignEmptyHandler {
+
+        @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "空处理类型不能为空")
+        @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class)
+        private Integer type;
+
+        @Schema(description = "指定人员审批的用户编号数组", example = "1")
+        private List<Long> userIds;
+
+    }
+
     @Data
     @Schema(description = "操作按钮设置")
     public static class OperationButtonSetting {

+ 6 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
 import lombok.Setter;
@@ -49,6 +51,10 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
 
         // 第二步,获取任务的所有处理人
         Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
+        if (CollUtil.isEmpty(assigneeUserIds)) {
+            // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务,避免自动通过!
+            assigneeUserIds = SetUtils.asSet((Long) null);
+        }
         execution.setVariable(super.collectionVariable, assigneeUserIds);
         return assigneeUserIds.size();
     }

+ 6 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
 import lombok.Setter;
@@ -43,6 +45,10 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
 
         // 第二步,获取任务的所有处理人
         Set<Long> assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!!
+        if (CollUtil.isEmpty(assigneeUserIds)) {
+            // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务,避免自动通过!
+            assigneeUserIds = SetUtils.asSet((Long) null);
+        }
         execution.setVariable(super.collectionVariable, assigneeUserIds);
         return assigneeUserIds.size();
     }

+ 35 - 3
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java

@@ -1,9 +1,16 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.RandomUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.bpmn.model.UserTask;
@@ -14,6 +21,8 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
 import org.flowable.engine.impl.util.TaskHelper;
 import org.flowable.task.service.TaskService;
 import org.flowable.task.service.impl.persistence.entity.TaskEntity;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import java.util.List;
 import java.util.Set;
@@ -41,9 +50,29 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
         DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
         // 第一步,获得任务的候选用户
         Long assigneeUserId = calculateTaskCandidateUsers(execution);
-        Assert.notNull(assigneeUserId, "任务处理人不能为空");
         // 第二步,设置作为负责人
-        TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
+        if (assigneeUserId != null) {
+            TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
+            return;
+        }
+
+        // 特殊:审批人为空,根据配置是否要自动通过、自动拒绝
+        Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTask);
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+
+            @Override
+            public void afterCommit() {
+                if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
+                    SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
+                            .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
+                } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
+                    SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
+                            .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
+                }
+
+            }
+
+        });
     }
 
     private Long calculateTaskCandidateUsers(DelegateExecution execution) {
@@ -56,6 +85,9 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
         // 情况二,如果非多实例的任务,则计算任务处理人
         // 第一步,先计算可处理该任务的处理人们
         Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
+        if (CollUtil.isEmpty(candidateUserIds)) {
+            return null;
+        }
         // 第二步,后随机选择一个任务的处理人
         // 疑问:为什么一定要选择一个任务处理人?
         // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。

+ 7 - 9
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java

@@ -27,7 +27,6 @@ import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG;
-import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
 
 /**
  * {@link BpmTaskCandidateStrategy} 的调用者,用于调用对应的策略,实现任务的候选人的计算
@@ -89,17 +88,16 @@ public class BpmTaskCandidateInvoker {
         String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
         // 1.1 计算任务的候选人
         Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
-        // 1.2 移除被禁用的用户
-        removeDisableUsers(userIds);
+        // 1.2 候选人为空时,根据“审批人为空”的配置补充
+        if (CollUtil.isEmpty(userIds)) {
+            userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
+                    .calculateUsers(execution, param);
+        }
         // 1.3 移除发起人的用户
         removeStartUserIfSkip(execution, userIds);
 
-        // 2. 校验是否有候选人
-        if (CollUtil.isEmpty(userIds)) {
-            log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
-                    execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
-            throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
-        }
+        // 2. 移除被禁用的用户
+        removeDisableUsers(userIds);
         return userIds;
     }
 

+ 54 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import jakarta.annotation.Resource;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类
+ *
+ * @author kyle
+ */
+@Component
+public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy {
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Override
+    public BpmTaskCandidateStrategyEnum getStrategy() {
+        return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
+    }
+
+    @Override
+    public void validateParam(String param) {
+    }
+
+    @Override
+    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
+        // 情况一:指定人员审批
+        Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement());
+        if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
+            return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement()));
+        }
+
+        // 情况二:流程管理员
+        if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
+            // TODO 芋艿:需要等待流程实例的管理员支持
+            throw new UnsupportedOperationException("暂时实现!!!");
+        }
+
+        // 都不满足,还是返回空
+        return new HashSet<>();
+    }
+
+}

+ 1 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java

@@ -30,6 +30,7 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable {
     START_USER_MULTI_LEVEL_DEPT_LEADER(38, "发起人连续多级部门的负责人"),
     USER_GROUP(40, "用户组"),
     EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
+    ASSIGN_EMPTY(1, "审批人为空"),
     ;
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray();

+ 9 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java

@@ -38,6 +38,15 @@ public interface BpmnModelConstants {
      */
     String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType";
 
+    /**
+     * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型
+     */
+    String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType";
+    /**
+     * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组
+     */
+    String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds";
+
     /**
      * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型
      */

+ 12 - 3
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java

@@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
@@ -45,16 +46,24 @@ public class BpmnModelUtils {
     }
 
     public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
-        Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
+        Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
         return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
     }
 
     public static String parseReturnTaskId(FlowElement flowElement) {
-        return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
+        return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
     }
 
     public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
-        return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
+        return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
+    }
+
+    public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
+        return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE));
+    }
+
+    public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) {
+        return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ",");
     }
 
     public static String parseExtensionElement(FlowElement flowElement, String elementName) {

+ 12 - 6
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -19,10 +19,7 @@ import org.flowable.bpmn.BpmnAutoLayout;
 import org.flowable.bpmn.model.Process;
 import org.flowable.bpmn.model.*;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 
 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
@@ -456,7 +453,6 @@ public class SimpleModelUtils {
             userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
         }
 
-        // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈?
         // 添加候选人元素
         addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
         // 添加表单字段权限属性元素
@@ -469,6 +465,8 @@ public class SimpleModelUtils {
         addTaskRejectElements(node.getRejectHandler(), userTask);
         // 添加用户任务的审批人与发起人相同时的处理元素
         addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
+        // 添加用户任务的空处理元素
+        addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
         return userTask;
     }
 
@@ -487,6 +485,14 @@ public class SimpleModelUtils {
         addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
     }
 
+    private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
+        if (emptyHandler == null) {
+            return;
+        }
+        addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
+        addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
+    }
+
     private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
         BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod);
         if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) {
@@ -496,7 +502,7 @@ public class SimpleModelUtils {
         addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD,
                 approveMethod == null ? null : approveMethod.toString());
         MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
-        //  设置 collectionVariable。本系统用不到。会在 仅仅为了校验。
+        // 设置 collectionVariable。本系统用不到。仅仅为了 Flowable 校验不报错
         multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
         if (bpmApproveMethodEnum == BpmApproveMethodEnum.ANY) {
             multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION);

+ 4 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -191,7 +191,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
      */
     private Task validateTask(Long userId, String taskId) {
         Task task = validateTaskExist(taskId);
-        if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
+        // 为什么判断 assignee 非空的情况下?
+        // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过
+        if (StrUtil.isNotBlank(task.getAssignee())
+            && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) {
             throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
         }
         return task;