Browse Source

BPM 模型重构 7:任务分配规则的后端,全部实现完成

YunaiV 3 years ago
parent
commit
902f2ecbad

+ 4 - 2
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/enums/BpmErrorCodeConstants.java

@@ -45,8 +45,10 @@ public interface BpmErrorCodeConstants {
 
     // ========== 流程任务分配规则 1-009-006-000 ==========
     ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则");
-    ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1009006000, "流程任务分配规则不存在");
-    ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1009006000, "只有流程模型的任务分配规则,才允许被修改");
+    ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1009006001, "流程任务分配规则不存在");
+    ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1009006002, "只有流程模型的任务分配规则,才允许被修改");
+    ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1009006003, "操作失败,原因:找不到任务的审批人!");
+    ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1009006004, "操作失败,原因:任务分配脚本({}) 不存在");
 
     // ========== 动态表单模块 1-009-010-000 ==========
     ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在");

+ 66 - 16
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehavior.java

@@ -4,11 +4,18 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
+import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.definition.BpmUserGroupDO;
 import cn.iocoder.yudao.adminserver.modules.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
+import cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
 import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmTaskAssignRuleService;
+import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmUserGroupService;
+import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
+import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
+import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
 import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
 import org.activiti.bpmn.model.UserTask;
 import org.activiti.engine.ActivitiException;
 import org.activiti.engine.delegate.DelegateExecution;
@@ -17,26 +24,49 @@ import org.activiti.engine.impl.el.ExpressionManager;
 import org.activiti.engine.impl.persistence.entity.TaskEntity;
 import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
 
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS;
+import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
  * 自定义的流程任务的 assignee 负责人的分配
  *
  * @author 芋道源码
  */
+@Slf4j
 public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
 
     @Setter
     private BpmTaskAssignRuleService bpmTaskRuleService;
     @Setter
     private SysPermissionService permissionService;
+    @Setter
+    private SysDeptService deptService;
+    @Setter
+    private BpmUserGroupService userGroupService;
+    @Resource
+    private SysUserService userService;
+    /**
+     * 任务分配脚本
+     */
+    private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
 
     public BpmUserTaskActivitiBehavior(UserTask userTask) {
         super(userTask);
     }
 
+    public void setScripts(List<BpmTaskAssignScript> scripts) {
+        this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
+    }
+
     @Override
     protected void handleAssignments(TaskEntityManager taskEntityManager,
                                      String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups,
@@ -51,7 +81,7 @@ public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
         // 设置候选人们
         candidateUserIds.remove(assigneeUserId); // 已经成为负责人了,就不要在扮演候选人了
         if (CollUtil.isNotEmpty(candidateUserIds)) {
-            task.addCandidateUsers(CollectionUtils.convertSet(candidateUserIds, String::valueOf));
+            task.addCandidateUsers(convertSet(candidateUserIds, String::valueOf));
         }
     }
 
@@ -80,20 +110,23 @@ public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
         if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
             assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
         } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
-            assigneeUserIds = calculateTaskCandidateUsersByDept(task, rule);
+            assigneeUserIds = calculateTaskCandidateUsersByMember(task, rule);
         } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
-            assigneeUserIds = calculateTaskCandidateUsersByDept(task, rule);
-        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
+            assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(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 = calculateTaskCandidateUsersByUser(task, rule);
+            assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
+        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
+            assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
         }
 
         // TODO 芋艿:统一过滤掉被禁用的用户
         // 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
         if (CollUtil.isEmpty(assigneeUserIds)) {
-            throw new ActivitiException(StrUtil.format("流程任务({}/{}/{}) 任务规则({}) 找不到候选人",
-                    task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
+            log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]",
+                    task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
+            throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
         }
         return assigneeUserIds;
     }
@@ -102,12 +135,14 @@ public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
         return permissionService.getUserRoleIdListByRoleIds(rule.getOptions());
     }
 
-    private Set<Long> calculateTaskCandidateUsersByDept(TaskEntity task, BpmTaskAssignRuleDO rule) {
-        throw new UnsupportedOperationException("暂不支持该任务规则");
+    private Set<Long> calculateTaskCandidateUsersByMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
+        List<SysUserDO> users = userService.getUsersByPostIds(rule.getOptions());
+        return convertSet(users, SysUserDO::getId);
     }
 
     private Set<Long> calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
-        throw new UnsupportedOperationException("暂不支持该任务规则");
+        List<SysDeptDO> depts = deptService.getDepts(rule.getOptions());
+        return convertSet(depts, SysDeptDO::getLeaderUserId);
     }
 
     private Set<Long> calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
@@ -115,11 +150,26 @@ public class BpmUserTaskActivitiBehavior extends UserTaskActivityBehavior {
     }
 
     private Set<Long> calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
-        throw new UnsupportedOperationException("暂不支持该任务规则");
+        List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
+        Set<Long> userIds = new HashSet<>();
+        userGroups.forEach(bpmUserGroupDO -> userIds.addAll(bpmUserGroupDO.getMemberUserIds()));
+        return userIds;
     }
 
     private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
-        throw new UnsupportedOperationException("暂不支持该任务规则");
+        // 获得对应的脚本
+        List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
+        rule.getOptions().forEach(id -> {
+            BpmTaskAssignScript script = scriptMap.get(id);
+            if (script == null) {
+                throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id);
+            }
+            scripts.add(script);
+        });
+        // 逐个计算任务
+        Set<Long> userIds = new HashSet<>();
+        scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
+        return userIds;
     }
 
 }

+ 8 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script;
 
+import cn.iocoder.yudao.adminserver.modules.bpm.enums.definition.BpmTaskRuleScriptEnum;
 import org.activiti.engine.impl.persistence.entity.TaskEntity;
 
 import java.util.List;
@@ -23,4 +24,11 @@ public interface BpmTaskAssignScript {
      */
     List<Long> calculateTaskCandidateUsers(TaskEntity task);
 
+    /**
+     * 获得枚举值
+     *
+     * @return 枚举值
+     */
+    BpmTaskRuleScriptEnum getEnum();
+
 }

+ 4 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/mysql/user/SysUserMapper.java

@@ -68,5 +68,9 @@ public interface SysUserMapper extends BaseMapperX<SysUserDO> {
         return selectList("status", status);
     }
 
+    default List<SysUserDO> selectListByDeptIds(Collection<Integer> deptIds) {
+        return selectList("dept_id", deptIds);
+    }
+
 }
 

+ 8 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/SysDeptService.java

@@ -84,6 +84,14 @@ public interface SysDeptService {
      */
     SysDeptDO getDept(Long id);
 
+    /**
+     * 获得部门信息数组
+     *
+     * @param ids 部门编号数组
+     * @return 部门信息数组
+     */
+    List<SysDeptDO> getDepts(Collection<Long> ids);
+
     /**
      * 获得所有子部门,从缓存中
      *

+ 5 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.system.service.dept.impl;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -233,6 +232,11 @@ public class SysDeptServiceImpl implements SysDeptService {
         return deptMapper.selectById(id);
     }
 
+    @Override
+    public List<SysDeptDO> getDepts(Collection<Long> ids) {
+        return deptMapper.selectBatchIds(ids);
+    }
+
     private void checkCreateOrUpdate(Long id, Long parentId, String name) {
         // 校验自己存在
         checkDeptExists(id);

+ 16 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/user/SysUserService.java

@@ -174,6 +174,22 @@ public interface SysUserService {
      */
     List<SysUserDO> getUsersByStatus(Integer status);
 
+    /**
+     * 获得指定岗位的用户数组
+     *
+     * @param postIds 岗位数组
+     * @return 用户数组
+     */
+    List<SysUserDO> getUsersByPostIds(Collection<Long> postIds);
+
+    /**
+     * 获得指定部门的用户数组
+     *
+     * @param deptIds 部门数组
+     * @return 用户数组
+     */
+    List<SysUserDO> getUsersByDeptIds(Collection<Integer> deptIds);
+
     /**
      * 校验用户们是否有效。如下情况,视为无效:
      * 1. 用户编号不存在

+ 20 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/user/impl/SysUserServiceImpl.java

@@ -389,6 +389,26 @@ public class SysUserServiceImpl implements SysUserService {
         return userMapper.selectListByStatus(status);
     }
 
+    @Override
+    public List<SysUserDO> getUsersByPostIds(Collection<Long> postIds) {
+        if (CollUtil.isEmpty(postIds)) {
+            return Collections.emptyList();
+        }
+        // 过滤不符合条件的
+        // TODO 芋艿:暂时只能内存过滤。解决方案:1、新建一个关联表;2、基于 where + 函数;3、json 字段,适合 mysql 8+ 版本
+        List<SysUserDO> users = userMapper.selectList();
+        users.removeIf(user -> !CollUtil.containsAny(user.getPostIds(), postIds));
+        return users;
+    }
+
+    @Override
+    public List<SysUserDO> getUsersByDeptIds(Collection<Integer> deptIds) {
+        if (CollUtil.isEmpty(deptIds)) {
+            return Collections.emptyList();
+        }
+        return userMapper.selectListByDeptIds(deptIds);
+    }
+
     @Override
     public void validUsers(Set<Long> ids) {
         if (CollUtil.isEmpty(ids)) {

+ 2 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -50,14 +50,14 @@ public class CollectionUtils {
         if (CollUtil.isEmpty(from)) {
             return new ArrayList<>();
         }
-        return from.stream().map(func).collect(Collectors.toList());
+        return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
     }
 
     public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return new HashSet<>();
         }
-        return from.stream().map(func).collect(Collectors.toSet());
+        return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
     }
 
     public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {