Browse Source

增加会签或签

kemengkai 3 years ago
parent
commit
0e988ce51d

File diff suppressed because it is too large
+ 394 - 1139
sql/ruoyi-vue-pro.sql


+ 8 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 
 import java.util.Collection;
 import java.util.Collection;
 import java.util.List;
 import java.util.List;
@@ -22,4 +23,11 @@ public interface BpmTaskExtMapper extends BaseMapperX<BpmTaskExtDO> {
     default List<BpmTaskExtDO> selectListByProcessInstanceId(String processInstanceId) {
     default List<BpmTaskExtDO> selectListByProcessInstanceId(String processInstanceId) {
         return selectList("process_instance_id", processInstanceId);
         return selectList("process_instance_id", processInstanceId);
     }
     }
+
+    /**
+     * 修改或签任务信息
+     *
+     * @param entity 任务信息
+     */
+    void updateUserOrSignTask(@Param("entity") BpmTaskExtDO entity);
 }
 }

+ 2 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskAssignRuleTypeEnum.java

@@ -13,15 +13,13 @@ import lombok.Getter;
 public enum BpmTaskAssignRuleTypeEnum {
 public enum BpmTaskAssignRuleTypeEnum {
 
 
     ROLE(10, "角色"),
     ROLE(10, "角色"),
-
     DEPT_MEMBER(20, "部门的成员"), // 包括负责人
     DEPT_MEMBER(20, "部门的成员"), // 包括负责人
     DEPT_LEADER(21, "部门的负责人"),
     DEPT_LEADER(21, "部门的负责人"),
     POST(22, "岗位"),
     POST(22, "岗位"),
-
     USER(30, "用户"),
     USER(30, "用户"),
-
+    USER_SIGN(31, "用户---会签"),
+    USER_OR_SIGN(32, "用户---或签"),
     USER_GROUP(40, "用户组"),
     USER_GROUP(40, "用户组"),
-
     SCRIPT(50, "自定义脚本"), // 例如说,发起人所在部门的领导、发起人所在部门的领导的领导
     SCRIPT(50, "自定义脚本"), // 例如说,发起人所在部门的领导、发起人所在部门的领导的领导
     ;
     ;
 
 

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

@@ -10,7 +10,10 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.EqualsAndHashCode;
 import lombok.Setter;
 import lombok.Setter;
 import lombok.ToString;
 import lombok.ToString;
+import org.flowable.bpmn.model.Activity;
 import org.flowable.bpmn.model.UserTask;
 import org.flowable.bpmn.model.UserTask;
+import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
 import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
 import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
 import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
 import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
 
 
@@ -52,4 +55,18 @@ public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
         userTaskActivityBehavior.setScripts(scripts);
         userTaskActivityBehavior.setScripts(scripts);
         return userTaskActivityBehavior;
         return userTaskActivityBehavior;
     }
     }
+
+    @Override
+    public ParallelMultiInstanceBehavior createParallelMultiInstanceBehavior(Activity activity,
+        AbstractBpmnActivityBehavior innerActivityBehavior) {
+        BpmParallelMultiInstanceActivityBehavior bpmParallelMultiInstanceActivityBehavior =
+            new BpmParallelMultiInstanceActivityBehavior(activity, innerActivityBehavior);
+        bpmParallelMultiInstanceActivityBehavior.setBpmTaskRuleService(bpmTaskRuleService);
+        bpmParallelMultiInstanceActivityBehavior.setPermissionApi(permissionApi);
+        bpmParallelMultiInstanceActivityBehavior.setDeptApi(deptApi);
+        bpmParallelMultiInstanceActivityBehavior.setUserGroupService(userGroupService);
+        bpmParallelMultiInstanceActivityBehavior.setAdminUserApi(adminUserApi);
+        bpmParallelMultiInstanceActivityBehavior.setScripts(scripts);
+        return bpmParallelMultiInstanceActivityBehavior;
+    }
 }
 }

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

@@ -0,0 +1,194 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.google.common.annotations.VisibleForTesting;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.Activity;
+import org.flowable.common.engine.api.FlowableException;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.util.CommandContextUtil;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
+
+/**
+ * @author kemengkai
+ * @create 2022-04-21 16:57
+ */
+@Slf4j
+public class BpmParallelMultiInstanceActivityBehavior extends ParallelMultiInstanceBehavior {
+
+    @Setter
+    private BpmTaskAssignRuleService bpmTaskRuleService;
+    @Setter
+    private BpmUserGroupService userGroupService;
+    @Setter
+    private DeptApi deptApi;
+    @Setter
+    private AdminUserApi adminUserApi;
+    @Setter
+    private PermissionApi permissionApi;
+    /**
+     * EL表达式集合模板
+     */
+    private final static String EXPRESSION_TEXT_TEMPLATE = "${coll_userList}";
+
+    /**
+     * 任务分配脚本
+     */
+    private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
+
+    public BpmParallelMultiInstanceActivityBehavior(Activity activity,
+        AbstractBpmnActivityBehavior innerActivityBehavior) {
+        super(activity, innerActivityBehavior);
+    }
+
+    public void setScripts(List<BpmTaskAssignScript> scripts) {
+        this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
+    }
+
+    /**
+     * 创建并行任务
+     *
+     * @param multiInstanceRootExecution 并行任务入参
+     *
+     * @return 返回结果
+     */
+    @Override
+    protected int createInstances(DelegateExecution multiInstanceRootExecution) {
+        // 查找任务信息
+        BpmTaskAssignRuleDO taskRule = getTaskRule(multiInstanceRootExecution);
+        // 获取任务用户
+        Set<Long> assigneeUserIds = calculateTaskCandidateUsers(multiInstanceRootExecution, taskRule);
+        // 设置任务集合变量
+        String expressionText = String.format("%s_userList", taskRule.getTaskDefinitionKey());
+        // 设置任务集合变量与任务关系
+        multiInstanceRootExecution.setVariable(expressionText, assigneeUserIds);
+        // 设置任务集合EL表达式
+        this.collectionExpression = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager()
+            .createExpression(String.format("${%s}", expressionText));
+        // 根据会签,或签类型,设置会签,或签条件
+        if (BpmTaskAssignRuleTypeEnum.USER_SIGN.getType().equals(taskRule.getType())) {
+            // 会签
+            this.completionCondition = "${ nrOfInstances == nrOfCompletedInstances }";
+        } else {
+            // 或签
+            this.completionCondition = "${ nrOfCompletedInstances == 1 }";
+        }
+        // 设置取出集合变量
+        this.collectionElementVariable = "user";
+        return super.createInstances(multiInstanceRootExecution);
+    }
+
+    @Override
+    protected Object resolveCollection(DelegateExecution execution) {
+        Object collection = null;
+        if (EXPRESSION_TEXT_TEMPLATE.equals(this.collectionExpression.getExpressionText())) {
+            // 查找任务信息
+            BpmTaskAssignRuleDO taskRule = getTaskRule(execution);
+            // 设置任务集合变量
+            String expressionText = String.format("%s_userList", execution.getCurrentActivityId());
+            // 获取任务用户
+            Set<Long> assigneeUserIds = calculateTaskCandidateUsers(execution, taskRule);
+            // 设置任务集合变量与任务关系
+            execution.setVariable(expressionText, assigneeUserIds);
+            // 设置任务集合EL表达式
+            this.collectionExpression = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager()
+                .createExpression(String.format("${%s}", expressionText));
+        }
+        if (this.collectionExpression != null) {
+            collection = this.collectionExpression.getValue(execution);
+        } else if (this.collectionVariable != null) {
+            collection = execution.getVariable(this.collectionVariable);
+        } else if (this.collectionString != null) {
+            collection = this.collectionString;
+        }
+
+        return collection;
+    }
+
+    private BpmTaskAssignRuleDO getTaskRule(DelegateExecution task) {
+        List<BpmTaskAssignRuleDO> taskRules =
+            bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
+                task.getCurrentActivityId());
+        if (CollUtil.isEmpty(taskRules)) {
+            throw new FlowableException(
+                StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则", task.getId(), task.getProcessDefinitionId(),
+                    task.getCurrentActivityId()));
+        }
+        if (taskRules.size() > 1) {
+            throw new FlowableException(
+                StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})", task.getId(), task.getProcessDefinitionId(),
+                    task.getCurrentActivityId(), taskRules.size()));
+        }
+        return taskRules.get(0);
+    }
+
+    Set<Long> calculateTaskCandidateUsers(DelegateExecution task, BpmTaskAssignRuleDO rule) {
+        Set<Long> assigneeUserIds = null;
+        //        if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
+        //        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByDeptMember(task, rule);
+        //        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(task, rule);
+        //        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByPost(task, rule);
+        //        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
+        //        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
+        //        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
+        //            assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
+        if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_SIGN.getType(), rule.getType())) {
+            assigneeUserIds = calculateTaskCandidateUsersSignByUser(task, rule);
+        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType(), rule.getType())) {
+            assigneeUserIds = calculateTaskCandidateUsersSignByUser(task, rule);
+        }
+
+        // 移除被禁用的用户
+        removeDisableUsers(assigneeUserIds);
+        // 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
+        if (CollUtil.isEmpty(assigneeUserIds)) {
+            log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", task.getId(),
+                task.getProcessDefinitionId(), task.getCurrentActivityId(), toJsonString(rule));
+            throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
+        }
+        return assigneeUserIds;
+    }
+
+    private Set<Long> calculateTaskCandidateUsersSignByUser(DelegateExecution task, BpmTaskAssignRuleDO rule) {
+        return rule.getOptions();
+    }
+
+    @VisibleForTesting
+    void removeDisableUsers(Set<Long> assigneeUserIds) {
+        if (CollUtil.isEmpty(assigneeUserIds)) {
+            return;
+        }
+        //TODO 芋艿 这里有数据权限的问题。默认会加上数据权限 dept_id IN (deptId). 导致查询不到数据
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
+        assigneeUserIds.removeIf(id -> {
+            AdminUserRespDTO user = userMap.get(id);
+            return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
+        });
+    }
+}

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

@@ -75,34 +75,46 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
 
 
     @Override
     @Override
     @DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
     @DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
-    protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
-        boolean isMultiInstance = hasMultiInstanceCharacteristics();
-        if(isMultiInstance){
+    protected void handleAssignments(TaskService taskService, String assignee, String owner,
+        List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
+        DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
+        /*boolean isMultiInstance = hasMultiInstanceCharacteristics();
+        if (isMultiInstance) {
             //多实例 会签/或签,执行多次每个人 待办人都在execution里面获取
             //多实例 会签/或签,执行多次每个人 待办人都在execution里面获取
             Integer assigneeUserId = execution.getVariableLocal("user", Integer.class);
             Integer assigneeUserId = execution.getVariableLocal("user", Integer.class);
             TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
             TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
-        }else {
+        } else {
             // 第一步,获得任务的规则
             // 第一步,获得任务的规则
             BpmTaskAssignRuleDO rule = getTaskRule(task);
             BpmTaskAssignRuleDO rule = getTaskRule(task);
             // 第二步,获得任务的候选用户们
             // 第二步,获得任务的候选用户们
             Set<Long> candidateUserIds = calculateTaskCandidateUsers(task, rule);
             Set<Long> candidateUserIds = calculateTaskCandidateUsers(task, rule);
             // 第三步,设置一个作为负责人
             // 第三步,设置一个作为负责人
-            Long assigneeUserId = chooseTaskAssignee(candidateUserIds);
+            Long assigneeUserId = chooseTaskAssignee(execution, candidateUserIds);
             TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
             TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
         }
         }
-
+        */
+        // 第一步,获得任务的规则
+        BpmTaskAssignRuleDO rule = getTaskRule(task);
+        // 第二步,获得任务的候选用户们
+        Set<Long> candidateUserIds = calculateTaskCandidateUsers(task, rule);
+        // 第三步,设置一个作为负责人
+        Long assigneeUserId = chooseTaskAssignee(execution, candidateUserIds);
+        TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
     }
     }
 
 
     private BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
     private BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
-        List<BpmTaskAssignRuleDO> taskRules = bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
+        List<BpmTaskAssignRuleDO> taskRules =
+            bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
                 task.getTaskDefinitionKey());
                 task.getTaskDefinitionKey());
         if (CollUtil.isEmpty(taskRules)) {
         if (CollUtil.isEmpty(taskRules)) {
-            throw new FlowableException(StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则",
-                    task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
+            throw new FlowableException(
+                StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则", task.getId(), task.getProcessDefinitionId(),
+                    task.getTaskDefinitionKey()));
         }
         }
         if (taskRules.size() > 1) {
         if (taskRules.size() > 1) {
-            throw new FlowableException(StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})",
-                    task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), taskRules.size()));
+            throw new FlowableException(
+                StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})", task.getId(), task.getProcessDefinitionId(),
+                    task.getTaskDefinitionKey(), taskRules.size()));
         }
         }
         return taskRules.get(0);
         return taskRules.get(0);
     }
     }
@@ -123,14 +135,18 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
             assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
             assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
         } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
         } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
             assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
             assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
+        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_SIGN.getType(), rule.getType())) {
+            assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
+        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType(), rule.getType())) {
+            assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
         }
         }
 
 
         // 移除被禁用的用户
         // 移除被禁用的用户
         removeDisableUsers(assigneeUserIds);
         removeDisableUsers(assigneeUserIds);
         // 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
         // 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
         if (CollUtil.isEmpty(assigneeUserIds)) {
         if (CollUtil.isEmpty(assigneeUserIds)) {
-            log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]",
-                    task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
+            log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", task.getId(),
+                task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
             throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
             throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
         }
         }
         return assigneeUserIds;
         return assigneeUserIds;
@@ -182,7 +198,17 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
         return userIds;
         return userIds;
     }
     }
 
 
-    private Long chooseTaskAssignee(Set<Long> candidateUserIds) {
+    private Long chooseTaskAssignee(DelegateExecution execution, Set<Long> candidateUserIds) {
+        // 获取任务变量
+        Map<String, Object> variables = execution.getVariables();
+        // 设置任务集合变量key
+        String expressionText = String.format("%s_userList", execution.getCurrentActivityId());
+        // 判断当前任务是否为并行任务, 是的话获取任务变量
+        if (variables.containsKey(expressionText)) {
+            String user = variables.get("user").toString();
+            return Long.valueOf(user);
+        }
+
         // TODO 芋艿:未来可以优化下,改成轮询的策略
         // TODO 芋艿:未来可以优化下,改成轮询的策略
         int index = RandomUtil.randomInt(candidateUserIds.size());
         int index = RandomUtil.randomInt(candidateUserIds.size());
         return CollUtil.get(candidateUserIds, index);
         return CollUtil.get(candidateUserIds, index);

+ 18 - 13
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java

@@ -39,7 +39,7 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
 @Service
 @Service
 @Validated
 @Validated
 @Slf4j
 @Slf4j
-public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
+public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
 
 
     @Resource
     @Resource
     private BpmTaskAssignRuleMapper taskRuleMapper;
     private BpmTaskAssignRuleMapper taskRuleMapper;
@@ -63,7 +63,8 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
     private DictDataApi dictDataApi;
     private DictDataApi dictDataApi;
 
 
     @Override
     @Override
-    public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId, String taskDefinitionKey) {
+    public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
+        String taskDefinitionKey) {
         return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, taskDefinitionKey);
         return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, taskDefinitionKey);
     }
     }
 
 
@@ -101,15 +102,15 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
         // 校验参数
         // 校验参数
         validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
         validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
         // 校验是否已经配置
         // 校验是否已经配置
-        BpmTaskAssignRuleDO existRule = taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(
-                reqVO.getModelId(), reqVO.getTaskDefinitionKey());
+        BpmTaskAssignRuleDO existRule =
+            taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(reqVO.getModelId(), reqVO.getTaskDefinitionKey());
         if (existRule != null) {
         if (existRule != null) {
             throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
             throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
         }
         }
 
 
         // 存储
         // 存储
         BpmTaskAssignRuleDO rule = BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO)
         BpmTaskAssignRuleDO rule = BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO)
-                .setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型,才允许新建
+            .setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型,才允许新建
         taskRuleMapper.insert(rule);
         taskRuleMapper.insert(rule);
         return rule.getId();
         return rule.getId();
     }
     }
@@ -142,15 +143,15 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
         }
         }
 
 
         // 遍历,匹配对应的规则
         // 遍历,匹配对应的规则
-        Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap = CollectionUtils.convertMap(processInstanceRules,
-                BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
+        Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap =
+            CollectionUtils.convertMap(processInstanceRules, BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
         for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
         for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
             BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
             BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
             if (processInstanceRule == null) {
             if (processInstanceRule == null) {
                 return false;
                 return false;
             }
             }
-            if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType())
-                    || !ObjectUtil.equal(modelRule.getOptions(), processInstanceRule.getOptions())) {
+            if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType()) || !ObjectUtil.equal(
+                modelRule.getOptions(), processInstanceRule.getOptions())) {
                 return false;
                 return false;
             }
             }
         }
         }
@@ -165,8 +166,8 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
         }
         }
         // 开始复制
         // 开始复制
         List<BpmTaskAssignRuleDO> newRules = BpmTaskAssignRuleConvert.INSTANCE.convertList2(rules);
         List<BpmTaskAssignRuleDO> newRules = BpmTaskAssignRuleConvert.INSTANCE.convertList2(rules);
-        newRules.forEach(rule -> rule.setProcessDefinitionId(toProcessDefinitionId).setId(null)
-                .setCreateTime(null).setUpdateTime(null));
+        newRules.forEach(rule -> rule.setProcessDefinitionId(toProcessDefinitionId).setId(null).setCreateTime(null)
+            .setUpdateTime(null));
         taskRuleMapper.insertBatch(newRules);
         taskRuleMapper.insertBatch(newRules);
     }
     }
 
 
@@ -189,7 +190,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
         if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
         if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
             roleApi.validRoles(options);
             roleApi.validRoles(options);
         } else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
         } else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
-                BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
+            BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
             deptApi.validDepts(options);
             deptApi.validDepts(options);
         } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
         } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
             postApi.validPosts(options);
             postApi.validPosts(options);
@@ -197,9 +198,13 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
             adminUserApi.validUsers(options);
             adminUserApi.validUsers(options);
         } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
         } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
             userGroupService.validUserGroups(options);
             userGroupService.validUserGroups(options);
+        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_SIGN.getType())) {
+            adminUserApi.validUsers(options);
+        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType())) {
+            adminUserApi.validUsers(options);
         } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
         } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
             dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
             dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
-                    CollectionUtils.convertSet(options, String::valueOf));
+                CollectionUtils.convertSet(options, String::valueOf));
         } else {
         } else {
             throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
             throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
         }
         }

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

@@ -7,8 +7,11 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 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.dal.dataobject.definition.BpmTaskAssignRuleDO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -66,6 +69,8 @@ public class BpmTaskServiceImpl implements BpmTaskService{
     private BpmTaskExtMapper taskExtMapper;
     private BpmTaskExtMapper taskExtMapper;
     @Resource
     @Resource
     private BpmMessageService messageService;
     private BpmMessageService messageService;
+    @Resource
+    private BpmTaskAssignRuleMapper taskAssignRuleMapper;
 
 
     @Override
     @Override
     public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
     public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
@@ -183,9 +188,22 @@ public class BpmTaskServiceImpl implements BpmTaskService{
 
 
         // 完成任务,审批通过
         // 完成任务,审批通过
         taskService.complete(task.getId(), instance.getProcessVariables());
         taskService.complete(task.getId(), instance.getProcessVariables());
+
         // 更新任务拓展表为通过
         // 更新任务拓展表为通过
-        taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
-                .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()).setComment(reqVO.getComment()));
+        taskExtMapper.updateByTaskId(
+            new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
+                .setComment(reqVO.getComment()));
+        // 判断任务是否为或签,或签时删除其余不用审批的任务
+        List<BpmTaskAssignRuleDO> bpmTaskAssignRuleList =
+            taskAssignRuleMapper.selectListByProcessDefinitionId(task.getProcessDefinitionId(),
+                task.getTaskDefinitionKey());
+        if (CollUtil.isNotEmpty(bpmTaskAssignRuleList) && bpmTaskAssignRuleList.size() > 0) {
+            if (BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType().equals(bpmTaskAssignRuleList.get(0).getType())) {
+                taskExtMapper.updateUserOrSignTask(
+                    (BpmTaskExtDO)new BpmTaskExtDO().setTaskId(task.getId()).setName(task.getName())
+                        .setProcessInstanceId(task.getProcessInstanceId()).setDeleted(true));
+            }
+        }
     }
     }
 
 
     @Override
     @Override

+ 9 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/resources/mapper/BpmTaskExtMapper.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper">
+
+    
+    <update id="updateUserOrSignTask">
+        UPDATE bpm_task_ext SET deleted=1 WHERE process_instance_id = #{entity.processInstanceId} AND name = #{entity.name} AND task_id != #{entity.taskId}
+    </update>
+</mapper>

+ 2 - 2
yudao-ui-admin/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue

@@ -15,7 +15,7 @@
         <el-form-item label="循环基数" key="loopCardinality">
         <el-form-item label="循环基数" key="loopCardinality">
           <el-input v-model="loopInstanceForm.loopCardinality" clearable @change="updateLoopCardinality" />
           <el-input v-model="loopInstanceForm.loopCardinality" clearable @change="updateLoopCardinality" />
         </el-form-item>
         </el-form-item>
-        <el-form-item label="集合" key="collection">
+        <el-form-item label="集合" key="collection" v-show="false">
           <el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
           <el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
         </el-form-item>
         </el-form-item>
         <el-form-item label="元素变量" key="elementVariable">
         <el-form-item label="元素变量" key="elementVariable">
@@ -131,7 +131,7 @@ export default {
       if (type === "SequentialMultiInstance") {
       if (type === "SequentialMultiInstance") {
         this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: true });
         this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: true });
       } else {
       } else {
-        this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics");
+        this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { collection: "${coll_userList}" });
       }
       }
       window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
       window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
         loopCharacteristics: this.multiLoopInstance
         loopCharacteristics: this.multiLoopInstance

+ 4 - 4
yudao-ui-admin/src/views/bpm/taskAssignRule/taskAssignRuleDialog.vue

@@ -53,7 +53,7 @@
             <el-option v-for="item in postOptions" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
             <el-option v-for="item in postOptions" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
           </el-select>
           </el-select>
         </el-form-item>
         </el-form-item>
-        <el-form-item v-if="form.type === 30" label="指定用户" prop="userIds">
+        <el-form-item v-if="form.type === 30 || form.type === 31 || form.type === 32" label="指定用户" prop="userIds">
           <el-select v-model="form.userIds" multiple clearable style="width: 100%">
           <el-select v-model="form.userIds" multiple clearable style="width: 100%">
             <el-option v-for="item in userOptions" :key="parseInt(item.id)" :label="item.nickname" :value="parseInt(item.id)" />
             <el-option v-for="item in userOptions" :key="parseInt(item.id)" :label="item.nickname" :value="parseInt(item.id)" />
           </el-select>
           </el-select>
@@ -215,7 +215,7 @@ export default {
         this.form.deptIds.push(...row.options);
         this.form.deptIds.push(...row.options);
       } else if (row.type === 22) {
       } else if (row.type === 22) {
         this.form.postIds.push(...row.options);
         this.form.postIds.push(...row.options);
-      } else if (row.type === 30) {
+      } else if (row.type === 30 || row.type === 31 || row.type === 32) {
         this.form.userIds.push(...row.options);
         this.form.userIds.push(...row.options);
       } else if (row.type === 40) {
       } else if (row.type === 40) {
         this.form.userGroupIds.push(...row.options);
         this.form.userGroupIds.push(...row.options);
@@ -240,7 +240,7 @@ export default {
             form.options = form.deptIds;
             form.options = form.deptIds;
           } else if (form.type === 22) {
           } else if (form.type === 22) {
             form.options = form.postIds;
             form.options = form.postIds;
-          } else if (form.type === 30) {
+          } else if (form.type === 30 || form.type === 31 || form.type === 32) {
             form.options = form.userIds;
             form.options = form.userIds;
           } else if (form.type === 40) {
           } else if (form.type === 40) {
             form.options = form.userGroupIds;
             form.options = form.userGroupIds;
@@ -302,7 +302,7 @@ export default {
             return postOption.name;
             return postOption.name;
           }
           }
         }
         }
-      } else if (type === 30) {
+      } else if (type === 30 || type === 31 || type === 32) {
         for (const userOption of this.userOptions) {
         for (const userOption of this.userOptions) {
           if (userOption.id === option) {
           if (userOption.id === option) {
             return userOption.nickname;
             return userOption.nickname;