Commit 62379641fa58a400219bf93bc68468cfdeb22c18

Authored by 芋道源码
Committed by Gitee
2 parents b4884209 c616066a

!1133 bpm 仿钉钉/飞书模式

Merge pull request !1133 from 芋道源码/feature/bpm
Showing 103 changed files with 2995 additions and 1585 deletions

Too many changes.

To preserve performance only 100 of 103 files are displayed.

.image/工作流设计器-bpmn.jpg 0 → 100644

177 KB

.image/工作流设计器-simple.jpg 0 → 100644

127 KB

README.md
... ... @@ -149,18 +149,22 @@
149 149  
150 150 ### 工作流程
151 151  
152   -| | 功能 | 描述 |
153   -|-----|-------|----------------------------------------|
154   -| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 |
155   -| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
156   -| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
157   -| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
158   -| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 |
159   -| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
160   -| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
  152 +| | 功能 | 描述 |
  153 +|----|-------|-----------------------------------------|
  154 +| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
  155 +| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
  156 +| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
  157 +| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
  158 +| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
  159 +| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
  160 +| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
161 161  
162 162 ![功能图](/.image/common/bpm-feature.png)
163 163  
  164 +| BPMN 设计器 | 钉钉/飞书设计器 |
  165 +|------------------------------|--------------------------------|
  166 +| ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
  167 +
164 168 ### 支付系统
165 169  
166 170 | | 功能 | 描述 |
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import com.google.common.collect.ImmutableMap;
7 8  
8 9 import java.util.*;
... ... @@ -73,6 +74,13 @@ public class CollectionUtils {
73 74 return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
74 75 }
75 76  
  77 + public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
  78 + if (ArrayUtil.isEmpty(from)) {
  79 + return new PageResult<>(from.getTotal());
  80 + }
  81 + return new PageResult<>(convertList(from.getList(), func), from.getTotal());
  82 + }
  83 +
76 84 public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
77 85 Function<T, ? extends Stream<? extends U>> func) {
78 86 if (CollUtil.isEmpty(from)) {
... ... @@ -324,7 +332,7 @@ public class CollectionUtils {
324 332 }
325 333  
326 334 public static <T> List<T> newArrayList(List<List<T>> list) {
327   - return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
  335 + return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
328 336 }
329 337  
330 338 }
331 339 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
... ... @@ -64,8 +64,10 @@ public abstract class AbstractWebSocketMessageSender implements WebSocketMessage
64 64 sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);
65 65 }
66 66 if (CollUtil.isEmpty(sessions)) {
67   - log.info("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
68   - sessionId, userType, userId, messageType, messageContent);
  67 + if (log.isDebugEnabled()) {
  68 + log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
  69 + sessionId, userType, userId, messageType, messageContent);
  70 + }
69 71 }
70 72 // 2. 执行发送
71 73 doSend(sessions, messageType, messageContent);
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
... ... @@ -23,7 +23,7 @@ public interface ErrorCodeConstants {
23 23 "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
24 24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
25 25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
26   - ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程的管理员");
  26 + ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
27 27  
28 28 // ========== 流程定义 1-009-003-000 ==========
29 29 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
... ... @@ -35,8 +35,8 @@ public interface ErrorCodeConstants {
35 35 ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
36 36 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
37 37 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
38   - ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置");
39   - ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在");
  38 + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置");
  39 + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
40 40 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
41 41  
42 42 // ========== 流程任务 1-009-005-000 ==========
... ... @@ -44,7 +44,7 @@ public interface ErrorCodeConstants {
44 44 ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
45 45 ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
46 46 ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
47   - ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
  47 + ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退回任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
48 48 ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
49 49 ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
50 50 ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java
... ... @@ -18,30 +18,26 @@ import java.util.Objects;
18 18 public enum BpmSimpleModelNodeType implements IntArrayValuable {
19 19  
20 20 // 0 ~ 1 开始和结束
21   - START_NODE(0, "startEvent", "开始节点"),
22   - END_NODE(1, "endEvent", "结束节点"),
  21 + START_NODE(0, "开始", "startEvent"),
  22 + END_NODE(1, "结束", "endEvent"),
23 23  
24 24 // 10 ~ 49 各种节点
25   - START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点。前端的开始节点,Id 固定
26   - APPROVE_NODE(11, "userTask", "审批人节点"),
27   - COPY_NODE(12, "serviceTask", "抄送人节点"),
  25 + START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定
  26 + APPROVE_NODE(11, "审批人", "userTask"),
  27 + COPY_NODE(12, "抄送人", "serviceTask"),
28 28  
29 29 // 50 ~ 条件分支
30   - CONDITION_NODE(50, "sequenceFlow", "条件节点"), // 用于构建流转条件的表达式
31   - CONDITION_BRANCH_NODE(51, " “parallelGateway”", "条件分支节点"), // TODO @jason:是不是改成叫 条件分支?
32   - PARALLEL_BRANCH_NODE(52, "exclusiveGateway", "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关
33   - INCLUSIVE_BRANCH_NODE(53, "inclusiveGateway", "包容分支节点"),
34   - // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~
35   - // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。
  30 + CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
  31 + CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
  32 + PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
  33 + INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
36 34 ;
37 35  
38 36 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
39 37  
40   - public static final String BPMN_USER_TASK_TYPE = "userTask";
41   -
42 38 private final Integer type;
43   - private final String bpmnType;
44 39 private final String name;
  40 + private final String bpmnType;
45 41  
46 42 /**
47 43 * 判断是否为分支节点
... ... @@ -54,16 +50,6 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
54 50 || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type);
55 51 }
56 52  
57   - /**
58   - * 判断是否需要记录的节点
59   - *
60   - * @param bpmnType bpmn节点类型
61   - */
62   - public static boolean isRecordNode(String bpmnType) {
63   - return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType)
64   - || Objects.equals(END_NODE.getBpmnType(), bpmnType);
65   - }
66   -
67 53 public static BpmSimpleModelNodeType valueOf(Integer type) {
68 54 return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
69 55 }
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java
... ... @@ -16,20 +16,23 @@ import java.util.Arrays;
16 16 @AllArgsConstructor
17 17 public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable {
18 18  
19   - RANDOM(1, "随机挑选一人审批"),
20   - RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例)
21   - ANY(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人)
22   - SEQUENTIAL(4, "依次审批"); // 依次审批
  19 + RANDOM(1, "随机挑选一人审批", null),
  20 + RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例)
  21 + ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人)
  22 + SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批
23 23  
24 24 /**
25 25 * 审批方式
26 26 */
27 27 private final Integer method;
28   -
29 28 /**
30 29 * 名字
31 30 */
32 31 private final String name;
  32 + /**
  33 + * 完成表达式
  34 + */
  35 + private final String completionCondition;
33 36  
34 37 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray();
35 38  
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
... ... @@ -38,6 +38,10 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
38 38 return ARRAYS;
39 39 }
40 40  
  41 + public static boolean isRejectStatus(Integer status) {
  42 + return REJECT.getStatus().equals(status);
  43 + }
  44 +
41 45 public static boolean isProcessEndStatus(Integer status) {
42 46 return ObjectUtils.equalsAny(status,
43 47 APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus());
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
1 1 package cn.iocoder.yudao.module.bpm.enums.task;
2 2  
  3 +import cn.hutool.core.util.ObjUtil;
3 4 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
4 5 import lombok.AllArgsConstructor;
5 6 import lombok.Getter;
... ... @@ -20,7 +21,6 @@ public enum BpmTaskStatusEnum {
20 21 CANCEL(4, "已取消"),
21 22  
22 23 RETURN(5, "已退回"),
23   - DELEGATE(6, "委派中"),
24 24  
25 25 /**
26 26 * 使用场景:
... ... @@ -45,6 +45,10 @@ public enum BpmTaskStatusEnum {
45 45 */
46 46 private final String name;
47 47  
  48 + public static boolean isRejectStatus(Integer status) {
  49 + return REJECT.getStatus().equals(status);
  50 + }
  51 +
48 52 /**
49 53 * 判断该状态是否已经处于 End 最终状态
50 54 * <p>
... ... @@ -59,4 +63,8 @@ public enum BpmTaskStatusEnum {
59 63 RETURN.getStatus(), APPROVING.getStatus());
60 64 }
61 65  
  66 + public static boolean isCancelStatus(Integer status) {
  67 + return ObjUtil.equal(status, CANCEL.getStatus());
  68 + }
  69 +
62 70 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
... ... @@ -9,11 +9,14 @@ public class UserSimpleBaseVO {
9 9  
10 10 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
11 11 private Long id;
12   -
13 12 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
14 13 private String nickname;
15   -
16 14 @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
17 15 private String avatar;
18 16  
  17 + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private Long deptId;
  19 + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
  20 + private String deptName;
  21 +
19 22 }
20 23 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmCategoryController.java
... ... @@ -48,6 +48,15 @@ public class BpmCategoryController {
48 48 return success(true);
49 49 }
50 50  
  51 + @PutMapping("/update-sort-batch")
  52 + @Operation(summary = "批量更新流程分类的排序")
  53 + @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3")
  54 + @PreAuthorize("@ss.hasPermission('bpm:category:update')")
  55 + public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) {
  56 + categoryService.updateCategorySortBatch(ids);
  57 + return success(true);
  58 + }
  59 +
51 60 @DeleteMapping("/delete")
52 61 @Operation(summary = "删除流程分类")
53 62 @Parameter(name = "id", description = "编号", required = true)
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
... ... @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
8 6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 7 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
... ... @@ -28,7 +26,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
28 26 import org.springframework.validation.annotation.Validated;
29 27 import org.springframework.web.bind.annotation.*;
30 28  
31   -import java.util.HashSet;
  29 +import java.util.Collections;
32 30 import java.util.List;
33 31 import java.util.Map;
34 32 import java.util.Set;
... ... @@ -56,38 +54,38 @@ public class BpmModelController {
56 54 @Resource
57 55 private AdminUserApi adminUserApi;
58 56  
59   - @GetMapping("/page")
  57 + @GetMapping("/list")
60 58 @Operation(summary = "获得模型分页")
61   - public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
62   - PageResult<Model> pageResult = modelService.getModelPage(pageVO);
63   - if (CollUtil.isEmpty(pageResult.getList())) {
64   - return success(PageResult.empty(pageResult.getTotal()));
  59 + @Parameter(name = "name", description = "模型名称", example = "芋艿")
  60 + public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) {
  61 + List<Model> list = modelService.getModelList(name);
  62 + if (CollUtil.isEmpty(list)) {
  63 + return success(Collections.emptyList());
65 64 }
66 65  
67   - // 拼接数据
68 66 // 获得 Form 表单
69   - Set<Long> formIds = convertSet(pageResult.getList(), model -> {
  67 + Set<Long> formIds = convertSet(list, model -> {
70 68 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
71 69 return metaInfo != null ? metaInfo.getFormId() : null;
72 70 });
73 71 Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
74 72 // 获得 Category Map
75 73 Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
76   - convertSet(pageResult.getList(), Model::getCategory));
  74 + convertSet(list, Model::getCategory));
77 75 // 获得 Deployment Map
78   - Set<String> deploymentIds = new HashSet<>();
79   - pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
80   - Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
  76 + Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
  77 + convertSet(list, Model::getDeploymentId));
81 78 // 获得 ProcessDefinition Map
82   - List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
  79 + List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
  80 + deploymentMap.keySet());
83 81 Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
84 82 // 获得 User Map
85   - Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), model -> {
  83 + Set<Long> userIds = convertSetByFlatMap(list, model -> {
86 84 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
87 85 return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
88 86 });
89 87 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
90   - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult,
  88 + return success(BpmModelConvert.INSTANCE.buildModelList(list,
91 89 formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
92 90 }
93 91  
... ... @@ -111,6 +109,7 @@ public class BpmModelController {
111 109 return success(modelService.createModel(createRetVO));
112 110 }
113 111  
  112 +
114 113 @PutMapping("/update")
115 114 @Operation(summary = "修改模型")
116 115 @PreAuthorize("@ss.hasPermission('bpm:model:update')")
... ... @@ -119,6 +118,14 @@ public class BpmModelController {
119 118 return success(true);
120 119 }
121 120  
  121 + @PutMapping("/update-sort-batch")
  122 + @Operation(summary = "批量修改模型排序")
  123 + @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
  124 + public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) {
  125 + modelService.updateModelSortBatch(getLoginUserId(), ids);
  126 + return success(true);
  127 + }
  128 +
122 129 @PostMapping("/deploy")
123 130 @Operation(summary = "部署模型")
124 131 @Parameter(name = "id", description = "编号", required = true, example = "1024")
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConver
9 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
11 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
12   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
13 12 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
14 13 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
15 14 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
... ... @@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Parameter;
18 17 import io.swagger.v3.oas.annotations.tags.Tag;
19 18 import jakarta.annotation.Resource;
20 19 import org.flowable.bpmn.model.BpmnModel;
21   -import org.flowable.bpmn.model.UserTask;
22 20 import org.flowable.engine.repository.Deployment;
23 21 import org.flowable.engine.repository.ProcessDefinition;
24 22 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -115,9 +113,8 @@ public class BpmProcessDefinitionController {
115 113 }
116 114 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
117 115 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
118   - List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
119 116 return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
120   - processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList));
  117 + processDefinition, null, processDefinitionInfo, null, null, bpmnModel));
121 118 }
122 119  
123 120 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
... ... @@ -15,7 +15,9 @@ import java.util.List;
15 15 * BPM 流程 MetaInfo Response DTO
16 16 * 主要用于 { Model#setMetaInfo(String)} 的存储
17 17 *
18   - * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的
  18 + * 最终,它的字段和
  19 + * {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO}
  20 + * 是一致的
19 21 *
20 22 * @author 芋道源码
21 23 */
... ... @@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO {
40 42 @NotNull(message = "表单类型不能为空")
41 43 private Integer formType;
42 44 @Schema(description = "表单编号", example = "1024")
43   - private Long formId; // formType 为 NORMAL 使用,必须非空
44   - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址",
45   - example = "/bpm/oa/leave/create")
46   - private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
47   - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址",
48   - example = "/bpm/oa/leave/view")
49   - private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
  45 + private Long formId; // formType 为 NORMAL 使用,必须非空
  46 + @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
  47 + private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
  48 + @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
  49 + private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
50 50  
51 51 @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
52 52 @NotNull(message = "是否可见不能为空")
... ... @@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO {
59 59 @NotEmpty(message = "可管理用户编号数组不能为空")
60 60 private List<Long> managerUserIds;
61 61  
  62 + @Schema(description = "排序", example = "1")
  63 + private Long sort; // 创建时,后端自动生成
  64 +
62 65 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java deleted
1   -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import io.swagger.v3.oas.annotations.media.Schema;
5   -import lombok.Data;
6   -import lombok.EqualsAndHashCode;
7   -import lombok.ToString;
8   -
9   -
10   -@Schema(description = "管理后台 - 流程模型分页 Request VO")
11   -@Data
12   -public class BpmModelPageReqVO extends PageParam {
13   -
14   - @Schema(description = "标识,精准匹配", example = "process1641042089407")
15   - private String key;
16   -
17   - @Schema(description = "名字,模糊匹配", example = "芋道")
18   - private String name;
19   -
20   - @Schema(description = "流程分类", example = "1")
21   - private String category;
22   -
23   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.bpm.enums.definition.*;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6   -import com.fasterxml.jackson.annotation.JsonIgnore;
7 6 import com.fasterxml.jackson.annotation.JsonInclude;
8 7 import io.swagger.v3.oas.annotations.media.Schema;
9 8 import jakarta.validation.Valid;
... ... @@ -31,7 +30,6 @@ public class BpmSimpleModelNodeVO {
31 30 @Schema(description = "模型节点名称", example = "领导审批")
32 31 private String name;
33 32  
34   - // TODO @jason:和 gpt 大模型对了下这个字段的命名,貌似叫 displayText 合适点。可以等最后我们全局替换下。(优先级:低)
35 33 @Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
36 34 private String showText;
37 35  
... ... @@ -79,12 +77,6 @@ public class BpmSimpleModelNodeVO {
79 77 @Schema(description = "操作按钮设置", example = "[]")
80 78 private List<OperationButtonSetting> buttonsSetting; // 用于审批节点
81 79  
82   - // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到
83   - /**
84   - * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。
85   - */
86   - @JsonIgnore
87   - private String attachNodeId;
88 80 /**
89 81 * 审批节点拒绝处理
90 82 */
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
... ... @@ -33,6 +33,9 @@ public class BpmProcessDefinitionRespVO {
33 33 @Schema(description = "流程分类名字", example = "请假")
34 34 private String categoryName;
35 35  
  36 + @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  37 + private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
  38 +
36 39 @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
37 40 private Integer formType;
38 41 @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")
... ... @@ -59,9 +62,12 @@ public class BpmProcessDefinitionRespVO {
59 62 @Schema(description = "BPMN XML")
60 63 private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
61 64  
62   - @Schema(description = "发起用户需要选择审批人的任务数组")
63   - private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
  65 + @Schema(description = "SIMPLE 设计器模型数据 json 格式")
  66 + private String simpleModel; // 非必须返回
64 67  
  68 + @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  69 + private Long sort;
  70 +
65 71 @Schema(description = "BPMN UserTask 用户任务")
66 72 @Data
67 73 public static class UserTask {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java deleted
1   -package cn.iocoder.yudao.module.bpm.controller.admin.task;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
5   -import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert;
6   -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
7   -import io.swagger.v3.oas.annotations.tags.Tag;
8   -import io.swagger.v3.oas.annotations.Parameter;
9   -import io.swagger.v3.oas.annotations.Operation;
10   -import org.springframework.security.access.prepost.PreAuthorize;
11   -import org.springframework.validation.annotation.Validated;
12   -import org.springframework.web.bind.annotation.GetMapping;
13   -import org.springframework.web.bind.annotation.RequestMapping;
14   -import org.springframework.web.bind.annotation.RequestParam;
15   -import org.springframework.web.bind.annotation.RestController;
16   -
17   -import jakarta.annotation.Resource;
18   -import java.util.List;
19   -
20   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
21   -
22   -@Tag(name = "管理后台 - 流程活动实例")
23   -@RestController
24   -@RequestMapping("/bpm/activity")
25   -@Validated
26   -public class BpmActivityController {
27   -
28   - @Resource
29   - private BpmActivityService activityService;
30   -
31   - @GetMapping("/list")
32   - @Operation(summary = "生成指定流程实例的高亮流程图",
33   - description = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
34   - @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
35   - @PreAuthorize("@ss.hasPermission('bpm:task:query')")
36   - public CommonResult<List<BpmActivityRespVO>> getActivityList(
37   - @RequestParam("processInstanceId") String processInstanceId) {
38   - return success(BpmActivityConvert.INSTANCE.convertList(activityService.getActivityListByProcessInstanceId(processInstanceId)));
39   - }
40   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http 0 → 100644
  1 +### 请求 /bpm/process-instance/get-bpmn 接口 => 成功
  2 +GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
  3 +Content-Type: application/json
  4 +tenant-id: 1
  5 +Authorization: Bearer {{token}}
  6 +
  7 +### 请求 /bpm/process-instance/get-bpmn 接口 => 失败
  8 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
  9 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911
  10 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
  11 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932
  12 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b
  13 +GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b
  14 +Content-Type: application/json
  15 +tenant-id: 1
  16 +Authorization: Bearer {{token}}
0 17 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
8 8 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
9 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
11   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
12 11 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
13 12 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
14 13 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
... ... @@ -128,15 +127,13 @@ public class BpmProcessInstanceController {
128 127 processInstance.getProcessDefinitionId());
129 128 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
130 129 processInstance.getProcessDefinitionId());
131   - String bpmnXml = BpmnModelUtils.getBpmnXml(
132   - processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()));
133 130 AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
134 131 DeptRespDTO dept = null;
135 132 if (startUser != null && startUser.getDeptId() != null) {
136 133 dept = deptApi.getDept(startUser.getDeptId());
137 134 }
138 135 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance,
139   - processDefinition, processDefinitionInfo, bpmnXml, startUser, dept));
  136 + processDefinition, processDefinitionInfo, startUser, dept));
140 137 }
141 138  
142 139 @DeleteMapping("/cancel-by-start-user")
... ... @@ -157,14 +154,6 @@ public class BpmProcessInstanceController {
157 154 return success(true);
158 155 }
159 156  
160   - @GetMapping("/get-form-fields-permission")
161   - @Operation(summary = "获得表单字段权限")
162   - @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
163   - public CommonResult<Map<String, String>> getFormFieldsPermission(
164   - @Valid BpmFormFieldsPermissionReqVO reqVO) {
165   - return success(processInstanceService.getFormFieldsPermission(reqVO));
166   - }
167   -
168 157 @GetMapping("/get-approval-detail")
169 158 @Operation(summary = "获得审批详情")
170 159 @Parameter(name = "id", description = "流程实例的编号", required = true)
... ... @@ -173,4 +162,11 @@ public class BpmProcessInstanceController {
173 162 return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
174 163 }
175 164  
  165 + @GetMapping("/get-bpmn-model-view")
  166 + @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
  167 + @Parameter(name = "id", description = "流程实例的编号", required = true)
  168 + public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
  169 + return success(processInstanceService.getProcessInstanceBpmnModelView(id));
  170 + }
  171 +
176 172 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 7 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
8 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
9 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO;
10 11 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
11 12 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
... ... @@ -28,8 +29,7 @@ import java.util.Map;
28 29 import java.util.stream.Stream;
29 30  
30 31 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
31   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
32   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
33 33 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
34 34  
35 35 @Tag(name = "管理后台 - 流程实例抄送")
... ... @@ -62,11 +62,15 @@ public class BpmProcessInstanceCopyController {
62 62 convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
63 63 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
64 64 copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator()))));
65   - return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> {
66   - MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname()));
67   - MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname()));
  65 + return success(convertPage(pageResult, copy -> {
  66 + BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
  67 + MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()),
  68 + user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
  69 + MapUtils.findAndThen(userMap, copy.getStartUserId(),
  70 + user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
68 71 MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
69 72 processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime())));
  73 + return copyVO;
70 74 }));
71 75 }
72 76  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
8 8 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
9 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
10 10 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
11   -import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
12 11 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
13 12 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
14 13 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
... ... @@ -20,7 +19,6 @@ import io.swagger.v3.oas.annotations.Parameter;
20 19 import io.swagger.v3.oas.annotations.tags.Tag;
21 20 import jakarta.annotation.Resource;
22 21 import jakarta.validation.Valid;
23   -import org.flowable.bpmn.model.BpmnModel;
24 22 import org.flowable.bpmn.model.UserTask;
25 23 import org.flowable.engine.history.HistoricProcessInstance;
26 24 import org.flowable.engine.runtime.ProcessInstance;
... ... @@ -52,8 +50,6 @@ public class BpmTaskController {
52 50 private BpmProcessInstanceService processInstanceService;
53 51 @Resource
54 52 private BpmFormService formService;
55   - @Resource
56   - private BpmProcessDefinitionService bpmProcessDefinitionService;
57 53  
58 54 @Resource
59 55 private AdminUserApi adminUserApi;
... ... @@ -121,27 +117,22 @@ public class BpmTaskController {
121 117 @PreAuthorize("@ss.hasPermission('bpm:task:query')")
122 118 public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
123 119 @RequestParam("processInstanceId") String processInstanceId) {
124   - List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId);
  120 + List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true);
125 121 if (CollUtil.isEmpty(taskList)) {
126 122 return success(Collections.emptyList());
127 123 }
128 124  
129 125 // 拼接数据
130   - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
131   - // 获得 User 和 Dept Map
132 126 Set<Long> userIds = convertSetByFlatMap(taskList, task ->
133 127 Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
134   - userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
135 128 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
136 129 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
137 130 convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
138 131 // 获得 Form Map
139 132 Map<Long, BpmFormDO> formMap = formService.getFormMap(
140 133 convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey())));
141   - // 获得 BpmnModel
142   - BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId());
143   - return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance,
144   - formMap, userMap, deptMap, bpmnModel));
  134 + return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList,
  135 + formMap, userMap, deptMap));
145 136 }
146 137  
147 138 @PutMapping("/approve")
... ... @@ -161,7 +152,7 @@ public class BpmTaskController {
161 152 }
162 153  
163 154 @GetMapping("/list-by-return")
164   - @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
  155 + @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮")
165 156 @Parameter(name = "taskId", description = "当前任务ID", required = true)
166 157 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
167 158 public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) {
... ... @@ -171,7 +162,7 @@ public class BpmTaskController {
171 162 }
172 163  
173 164 @PutMapping("/return")
174   - @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮")
  165 + @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮")
175 166 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
176 167 public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
177 168 taskService.returnTask(getLoginUserId(), reqVO);
... ... @@ -210,6 +201,14 @@ public class BpmTaskController {
210 201 return success(true);
211 202 }
212 203  
  204 + @PutMapping("/copy")
  205 + @Operation(summary = "抄送任务")
  206 + @PreAuthorize("@ss.hasPermission('bpm:task:update')")
  207 + public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) {
  208 + taskService.copyTask(getLoginUserId(), reqVO);
  209 + return success(true);
  210 + }
  211 +
213 212 @GetMapping("/list-by-parent-task-id")
214 213 @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
215 214 @Parameter(name = "parentTaskId", description = "父级任务编号", required = true)
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java
1 1 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc;
2 2  
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.Data;
5 6  
... ... @@ -12,31 +13,31 @@ public class BpmProcessInstanceCopyRespVO {
12 13 @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 14 private Long id;
14 15  
15   - @Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
16   - private Long startUserId;
17   - @Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
18   - private String startUserName;
  16 + @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + private UserSimpleBaseVO startUser;
19 18  
20 19 @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233")
21 20 private String processInstanceId;
22   - @Schema(description = "流程实例的名称")
  21 + @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
23 22 private String processInstanceName;
24   - @Schema(description = "流程实例的发起时间")
  23 + @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
25 24 private LocalDateTime processInstanceStartTime;
26 25  
27   - @Schema(description = "抄送的节点的活动编号")
  26 + @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED)
28 27 private String activityId;
29   - @Schema(description = "发起抄送的任务编号")
  28 + @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + private String activityName;
  30 +
  31 + @Schema(description = "流程活动的编号")
30 32 private String taskId;
31   - @Schema(description = "发起抄送的任务名称")
32   - private String taskName;
33 33  
34   - @Schema(description = "抄送人")
35   - private String creator;
36   - @Schema(description = "抄送人昵称")
37   - private String creatorName;
  34 + @Schema(description = "抄送人意见")
  35 + private String reason;
  36 +
  37 + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
  38 + private UserSimpleBaseVO createUser;
38 39  
39   - @Schema(description = "抄送时间")
  40 + @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED)
40 41 private LocalDateTime createTime;
41 42  
42 43 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
... ... @@ -6,16 +6,27 @@ import io.swagger.v3.oas.annotations.media.Schema;
6 6 import jakarta.validation.constraints.AssertTrue;
7 7 import lombok.Data;
8 8  
9   -// TODO @jason:这个可以简化下,使用 @RequestParam。嘿嘿,主要 VO 项不要太多
  9 +import java.util.Map;
  10 +
10 11 @Schema(description = "管理后台 - 审批详情 Request VO")
11 12 @Data
12 13 public class BpmApprovalDetailReqVO {
13 14  
14 15 @Schema(description = "流程定义的编号", example = "1024")
15   - private String processDefinitionId;
  16 + private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID
  17 +
  18 + @Schema(description = "流程变量")
  19 + private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
16 20  
17 21 @Schema(description = "流程实例的编号", example = "1024")
18   - private String processInstanceId;
  22 + private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID
  23 +
  24 + // TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。
  25 + @Schema(description = "流程活动编号", example = "StartUserNode")
  26 + private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限;
  27 +
  28 + @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
  29 + private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限
19 30  
20 31 @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
21 32 @JsonIgnore
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
1 1 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
2 2  
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
  5 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
  6 +import com.fasterxml.jackson.annotation.JsonIgnore;
3 7 import io.swagger.v3.oas.annotations.media.Schema;
4 8 import lombok.Data;
5 9  
6 10 import java.time.LocalDateTime;
7 11 import java.util.List;
  12 +import java.util.Map;
8 13  
9 14  
10 15 @Schema(description = "管理后台 - 审批详情 Response VO")
... ... @@ -14,12 +19,28 @@ public class BpmApprovalDetailRespVO {
14 19 @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
15 20 private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
16 21  
17   - @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED)
18   - private List<ApprovalNodeInfo> approveNodes;
  22 + @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED)
  23 + private List<ActivityNode> activityNodes;
19 24  
20   - @Schema(description = "审批节点信息")
  25 + @Schema(description = "表单字段权限")
  26 + private Map<String, String> formFieldsPermission;
  27 +
  28 + @Schema(description = "待办任务")
  29 + private BpmTaskRespVO todoTask;
  30 +
  31 + /**
  32 + * 所属流程定义信息
  33 + */
  34 + private BpmProcessDefinitionRespVO processDefinition;
  35 +
  36 + /**
  37 + * 所属流程实例信息
  38 + */
  39 + private BpmProcessInstanceRespVO processInstance;
  40 +
  41 + @Schema(description = "活动节点信息")
21 42 @Data
22   - public static class ApprovalNodeInfo {
  43 + public static class ActivityNode {
23 44  
24 45 @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
25 46 private String id;
... ... @@ -39,42 +60,40 @@ public class BpmApprovalDetailRespVO {
39 60 private LocalDateTime endTime;
40 61  
41 62 @Schema(description = "审批节点的任务信息")
42   - private List<ApprovalTaskInfo> tasks;
  63 + private List<ActivityNodeTask> tasks;
43 64  
44   - @Schema(description = "候选人用户列表")
45   - // TODO @jason:candidateUserList => candidateUsers,保持和 tasks 的命名风格一致哈
46   - private List<User> candidateUserList; // 用于未运行任务节点
  65 + @Schema(description = "候选人策略", example = "35")
  66 + private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选
47 67  
48   - }
  68 + @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
  69 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers
  70 + private List<Long> candidateUserIds;
49 71  
50   - // TODO @jason:可以替换成 UserSimpleBaseVO。简化下
51   - @Schema(description = "用户信息")
52   - @Data
53   - public static class User {
54   -
55   - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
56   - private Long id;
57   -
58   - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
59   - private String nickname;
60   -
61   - @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
62   - private String avatar;
  72 + @Schema(description = "候选人用户列表")
  73 + private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
63 74  
64 75 }
65 76  
66   - @Schema(description = "审批任务信息")
  77 + @Schema(description = "活动节点的任务信息")
67 78 @Data
68   - public static class ApprovalTaskInfo {
  79 + public static class ActivityNodeTask {
69 80  
70 81 @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
71 82 private String id;
72 83  
  84 + @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
  85 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
  86 + private Long owner;
  87 +
73 88 @Schema(description = "任务所属人", example = "1024")
74   - private User ownerUser;
  89 + private UserSimpleBaseVO ownerUser;
  90 +
  91 + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  92 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
  93 + private Long assignee;
75 94  
76 95 @Schema(description = "任务分配人", example = "2048")
77   - private User assigneeUser;
  96 + private UserSimpleBaseVO assigneeUser;
78 97  
79 98 @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
80 99 private Integer status; // 参见 BpmTaskStatusEnum 枚举
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java deleted
1   -package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import com.fasterxml.jackson.annotation.JsonIgnore;
5   -import io.swagger.v3.oas.annotations.media.Schema;
6   -import jakarta.validation.constraints.AssertTrue;
7   -import lombok.Data;
8   -
9   -@Schema(description = "管理后台 - 表单字段权限 Request VO")
10   -@Data
11   -public class BpmFormFieldsPermissionReqVO {
12   -
13   - @Schema(description = "流程定义的编号", example = "1024")
14   - private String processDefinitionId;
15   -
16   - @Schema(description = "流程实例的编号", example = "1024")
17   - private String processInstanceId;
18   -
19   - @Schema(description = "流程活动编号", example = "StartUserNode")
20   - private String activityId; // 对应 BPMN XML 节点 Id
21   -
22   - @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
23   - private String taskId; // UserTask 对应的Id
24   -
25   - @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
26   - @JsonIgnore
27   - public boolean isValidProcessParam() {
28   - return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
29   - }
30   -
31   - @AssertTrue(message = "流程活动编号和流程任务编号编号不能同时为空")
32   - @JsonIgnore
33   - public boolean isValidActivityParam() {
34   - return StrUtil.isNotEmpty(activityId) || StrUtil.isNotEmpty(taskId);
35   - }
36   -
37   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +import java.util.Set;
  10 +
  11 +@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO")
  12 +@Data
  13 +public class BpmProcessInstanceBpmnModelViewRespVO {
  14 +
  15 + // ========== 基本信息 ==========
  16 +
  17 + @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED)
  18 + private BpmProcessInstanceRespVO processInstance;
  19 +
  20 + @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED)
  21 + private List<BpmTaskRespVO> tasks;
  22 +
  23 + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
  24 + private String bpmnXml;
  25 +
  26 + @Schema(description = "SIMPLE 模型")
  27 + private BpmSimpleModelNodeVO simpleModel;
  28 +
  29 + // ========== 进度信息 ==========
  30 +
  31 + @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
  32 + private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask
  33 +
  34 + @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow
  36 +
  37 + @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
  38 + private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow
  39 +
  40 + @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + private Set<String> rejectedTaskActivityIds; // 只包括 UserTask
  42 +
  43 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
1 1 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
2 2  
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
3 4 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
4 5 import io.swagger.v3.oas.annotations.media.Schema;
5 6 import lombok.Data;
... ... @@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO {
44 45 /**
45 46 * 发起流程的用户
46 47 */
47   - private User startUser;
  48 + private UserSimpleBaseVO startUser;
48 49  
49 50 @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
50 51 private String processDefinitionId;
... ... @@ -58,22 +59,6 @@ public class BpmProcessInstanceRespVO {
58 59 */
59 60 private List<Task> tasks; // 仅在流程实例分页才返回
60 61  
61   - @Schema(description = "用户信息")
62   - @Data
63   - public static class User {
64   -
65   - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
66   - private Long id;
67   - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
68   - private String nickname;
69   -
70   - @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
71   - private Long deptId;
72   - @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
73   - private String deptName;
74   -
75   - }
76   -
77 62 @Schema(description = "流程任务")
78 63 @Data
79 64 public static class Task {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
... ... @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotEmpty;
5 5 import lombok.Data;
6 6  
7   -import java.util.Collection;
8 7 import java.util.Map;
9 8  
10 9 @Schema(description = "管理后台 - 通过流程任务的 Request VO")
... ... @@ -19,9 +18,6 @@ public class BpmTaskApproveReqVO {
19 18 @NotEmpty(message = "审批意见不能为空")
20 19 private String reason;
21 20  
22   - @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2")
23   - private Collection<Long> copyUserIds;
24   -
25 21 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
26 22 private Map<String, Object> variables;
27 23  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import lombok.Data;
  6 +
  7 +import java.util.Collection;
  8 +
  9 +@Schema(description = "管理后台 - 抄送流程任务的 Request VO")
  10 +@Data
  11 +public class BpmTaskCopyReqVO {
  12 +
  13 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotEmpty(message = "任务编号不能为空")
  15 + private String id;
  16 +
  17 + @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
  18 + @NotEmpty(message = "抄送用户不能为空")
  19 + private Collection<Long> copyUserIds;
  20 +
  21 + @Schema(description = "抄送意见", example = "帮忙看看!")
  22 + private String reason;
  23 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
1 1 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
2 2  
3   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
  4 +import com.fasterxml.jackson.annotation.JsonIgnore;
4 5 import io.swagger.v3.oas.annotations.media.Schema;
5 6 import lombok.Data;
6 7  
... ... @@ -33,14 +34,21 @@ public class BpmTaskRespVO {
33 34 @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
34 35 private String reason;
35 36  
  37 + @Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  38 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
  39 + private Long owner;
36 40 /**
37 41 * 负责人的用户信息
38 42 */
39   - private BpmProcessInstanceRespVO.User ownerUser;
  43 + private UserSimpleBaseVO ownerUser;
  44 +
  45 + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  46 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
  47 + private Long assignee;
40 48 /**
41 49 * 审核的用户信息
42 50 */
43   - private BpmProcessInstanceRespVO.User assigneeUser;
  51 + private UserSimpleBaseVO assigneeUser;
44 52  
45 53 @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
46 54 private String taskDefinitionKey;
... ... @@ -55,7 +63,7 @@ public class BpmTaskRespVO {
55 63 @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
56 64 private String parentTaskId;
57 65 @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask")
58   - private List<BpmTaskRespVO> children;
  66 + private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务
59 67  
60 68 @Schema(description = "表单编号", example = "1024")
61 69 private Long formId;
... ... @@ -67,9 +75,6 @@ public class BpmTaskRespVO {
67 75 private List<String> formFields;
68 76 @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
69 77 private Map<String, Object> formVariables;
70   - // @芋艿 都改成了 fieldsPermission。 buttonsSetting。和 BpmSimpleModelNodeVO 统一
71   - @Schema(description = "表单字段权限值")
72   - private Map<String, String> fieldsPermission;
73 78 @Schema(description = "操作按钮设置值")
74 79 private Map<Integer, OperationButtonSetting> buttonsSetting;
75 80  
... ... @@ -92,7 +97,7 @@ public class BpmTaskRespVO {
92 97 /**
93 98 * 发起人的用户信息
94 99 */
95   - private BpmProcessInstanceRespVO.User startUser;
  100 + private UserSimpleBaseVO startUser;
96 101  
97 102 }
98 103  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java
... ... @@ -5,7 +5,7 @@ import lombok.Data;
5 5  
6 6 import jakarta.validation.constraints.NotEmpty;
7 7  
8   -@Schema(description = "管理后台 - 回退流程任务的 Request VO")
  8 +@Schema(description = "管理后台 - 退回流程任务的 Request VO")
9 9 @Data
10 10 public class BpmTaskReturnReqVO {
11 11  
... ... @@ -13,12 +13,12 @@ public class BpmTaskReturnReqVO {
13 13 @NotEmpty(message = "任务编号不能为空")
14 14 private String id;
15 15  
16   - @Schema(description = "回退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
17   - @NotEmpty(message = "回退到的任务 Key 不能为空")
  16 + @Schema(description = "退回到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + @NotEmpty(message = "退回到的任务 Key 不能为空")
18 18 private String targetTaskDefinitionKey;
19 19  
20   - @Schema(description = "回退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
21   - @NotEmpty(message = "回退意见不能为空")
  20 + @Schema(description = "退回意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
  21 + @NotEmpty(message = "退回意见不能为空")
22 22 private String reason;
23 23  
24 24 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
1 1 package cn.iocoder.yudao.module.bpm.convert.definition;
2 2  
3 3 import cn.hutool.core.util.ArrayUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
6 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -22,6 +21,7 @@ import org.mapstruct.Mapper;
22 21 import org.mapstruct.factory.Mappers;
23 22  
24 23 import java.util.Collections;
  24 +import java.util.Comparator;
25 25 import java.util.List;
26 26 import java.util.Map;
27 27  
... ... @@ -37,25 +37,28 @@ public interface BpmModelConvert {
37 37  
38 38 BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
39 39  
40   - default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult,
41   - Map<Long, BpmFormDO> formMap,
42   - Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap,
43   - Map<String, ProcessDefinition> processDefinitionMap,
44   - Map<Long, AdminUserRespDTO> userMap) {
45   - List<BpmModelRespVO> list = convertList(pageResult.getList(), model -> {
  40 + default List<BpmModelRespVO> buildModelList(List<Model> list,
  41 + Map<Long, BpmFormDO> formMap,
  42 + Map<String, BpmCategoryDO> categoryMap,
  43 + Map<String, Deployment> deploymentMap,
  44 + Map<String, ProcessDefinition> processDefinitionMap,
  45 + Map<Long, AdminUserRespDTO> userMap) {
  46 + List<BpmModelRespVO> result = convertList(list, model -> {
46 47 BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
47 48 BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
48 49 BpmCategoryDO category = categoryMap.get(model.getCategory());
49 50 Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
50   - ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
  51 + ProcessDefinition processDefinition = model.getDeploymentId() != null ?
  52 + processDefinitionMap.get(model.getDeploymentId()) : null;
51 53 List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
52 54 return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers);
53 55 });
54   - return new PageResult<>(list, pageResult.getTotal());
  56 + // 排序
  57 + result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
  58 + return result;
55 59 }
56 60  
57   - default BpmModelRespVO buildModel(Model model,
58   - byte[] bpmnBytes) {
  61 + default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) {
59 62 BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
60 63 BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
61 64 if (ArrayUtil.isNotEmpty(bpmnBytes)) {
... ... @@ -112,6 +115,10 @@ public interface BpmModelConvert {
112 115 if (vo.getStartUserIds() == null) {
113 116 vo.setStartUserIds(Collections.emptyList());
114 117 }
  118 + // 如果为空,兜底处理,使用 createTime 创建时间
  119 + if (vo.getSort() == null) {
  120 + vo.setSort(model.getCreateTime().getTime());
  121 + }
115 122 return vo;
116 123 }
117 124  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
... ... @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
11 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
12 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
13 13 import org.flowable.bpmn.model.BpmnModel;
14   -import org.flowable.bpmn.model.UserTask;
15 14 import org.flowable.common.engine.impl.db.SuspensionState;
16 15 import org.flowable.engine.repository.Deployment;
17 16 import org.flowable.engine.repository.ProcessDefinition;
... ... @@ -20,6 +19,7 @@ import org.mapstruct.Mapping;
20 19 import org.mapstruct.MappingTarget;
21 20 import org.mapstruct.factory.Mappers;
22 21  
  22 +import java.util.Comparator;
23 23 import java.util.List;
24 24 import java.util.Map;
25 25  
... ... @@ -47,7 +47,7 @@ public interface BpmProcessDefinitionConvert {
47 47 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap,
48 48 Map<Long, BpmFormDO> formMap,
49 49 Map<String, BpmCategoryDO> categoryMap) {
50   - return CollectionUtils.convertList(list, definition -> {
  50 + List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> {
51 51 Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
52 52 BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class);
53 53 BpmFormDO form = null;
... ... @@ -55,8 +55,11 @@ public interface BpmProcessDefinitionConvert {
55 55 form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
56 56 }
57 57 BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class);
58   - return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null);
  58 + return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null);
59 59 });
  60 + // 排序
  61 + result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort));
  62 + return result;
60 63 }
61 64  
62 65 default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition,
... ... @@ -64,8 +67,7 @@ public interface BpmProcessDefinitionConvert {
64 67 BpmProcessDefinitionInfoDO processDefinitionInfo,
65 68 BpmFormDO form,
66 69 BpmCategoryDO category,
67   - BpmnModel bpmnModel,
68   - List<UserTask> startUserSelectUserTaskList) {
  70 + BpmnModel bpmnModel) {
69 71 BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
70 72 respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
71 73 // Deployment
... ... @@ -87,7 +89,6 @@ public interface BpmProcessDefinitionConvert {
87 89 // BpmnModel
88 90 if (bpmnModel != null) {
89 91 respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
90   - respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class));
91 92 }
92 93 return respVO;
93 94 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java deleted
1   -package cn.iocoder.yudao.module.bpm.convert.task;
2   -
3   -import cn.iocoder.yudao.framework.common.util.date.DateUtils;
4   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
5   -import org.flowable.engine.history.HistoricActivityInstance;
6   -import org.mapstruct.Mapper;
7   -import org.mapstruct.Mapping;
8   -import org.mapstruct.Mappings;
9   -import org.mapstruct.factory.Mappers;
10   -
11   -import java.util.List;
12   -
13   -/**
14   - * BPM 活动 Convert
15   - *
16   - * @author 芋道源码
17   - */
18   -@Mapper(uses = DateUtils.class)
19   -public interface BpmActivityConvert {
20   -
21   - BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class);
22   -
23   - List<BpmActivityRespVO> convertList(List<HistoricActivityInstance> list);
24   -
25   - @Mappings({
26   - @Mapping(source = "activityId", target = "key"),
27   - @Mapping(source = "activityType", target = "type")
28   - })
29   - BpmActivityRespVO convert(HistoricActivityInstance bean);
30   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
1 1 package cn.iocoder.yudao.module.bpm.convert.task;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
5 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
6 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
  11 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
7 12 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
  13 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO;
  14 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO;
8 15 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
  16 +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
  17 +import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert;
9 18 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 19 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
11 20 import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceStatusEvent;
  21 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
12 22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
13 23 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
14 24 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
15 25 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
16 26 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  27 +import org.flowable.bpmn.model.BpmnModel;
17 28 import org.flowable.engine.history.HistoricProcessInstance;
18 29 import org.flowable.engine.repository.ProcessDefinition;
19 30 import org.flowable.engine.runtime.ProcessInstance;
20 31 import org.flowable.task.api.Task;
  32 +import org.flowable.task.api.history.HistoricTaskInstance;
21 33 import org.mapstruct.Mapper;
22 34 import org.mapstruct.Mapping;
23 35 import org.mapstruct.MappingTarget;
24 36 import org.mapstruct.factory.Mappers;
25 37  
  38 +import java.util.HashSet;
26 39 import java.util.List;
27 40 import java.util.Map;
  41 +import java.util.Set;
  42 +
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 45  
29 46 /**
30 47 * 流程实例 Convert
... ... @@ -55,7 +72,7 @@ public interface BpmProcessInstanceConvert {
55 72 if (userMap != null) {
56 73 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId()));
57 74 if (startUser != null) {
58   - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
  75 + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
59 76 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
60 77 }
61 78 }
... ... @@ -65,20 +82,18 @@ public interface BpmProcessInstanceConvert {
65 82  
66 83 default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance,
67 84 ProcessDefinition processDefinition,
68   - BpmProcessDefinitionInfoDO processDefinitionExt,
69   - String bpmnXml,
  85 + BpmProcessDefinitionInfoDO processDefinitionInfo,
70 86 AdminUserRespDTO startUser,
71 87 DeptRespDTO dept) {
72 88 BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class);
73   - respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance));
74   - respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
  89 + respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))
  90 + .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
75 91 // definition
76 92 respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
77   - copyTo(processDefinitionExt, respVO.getProcessDefinition());
78   - respVO.getProcessDefinition().setBpmnXml(bpmnXml);
  93 + copyTo(processDefinitionInfo, respVO.getProcessDefinition());
79 94 // user
80 95 if (startUser != null) {
81   - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
  96 + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
82 97 if (dept != null) {
83 98 respVO.getStartUser().setDeptName(dept.getName());
84 99 }
... ... @@ -89,7 +104,7 @@ public interface BpmProcessInstanceConvert {
89 104 @Mapping(source = "from.id", target = "to.id", ignore = true)
90 105 void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
91 106  
92   - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {;
  107 + default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {
93 108 return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
94 109 .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
95 110 }
... ... @@ -109,4 +124,156 @@ public interface BpmProcessInstanceConvert {
109 124 .setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
110 125 }
111 126  
  127 + default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance,
  128 + List<HistoricTaskInstance> taskInstances,
  129 + BpmnModel bpmnModel,
  130 + BpmSimpleModelNodeVO simpleModel,
  131 + Set<String> unfinishedTaskActivityIds,
  132 + Set<String> finishedTaskActivityIds,
  133 + Set<String> finishedSequenceFlowActivityIds,
  134 + Set<String> rejectTaskActivityIds,
  135 + Map<Long, AdminUserRespDTO> userMap,
  136 + Map<Long, DeptRespDTO> deptMap) {
  137 + BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO();
  138 + // 基本信息
  139 + respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o
  140 + .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)))
  141 + .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap)));
  142 + respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class)
  143 + .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task))
  144 + .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap))
  145 + .setOwnerUser(buildUser(task.getOwner(), userMap, deptMap))));
  146 + respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
  147 + respVO.setSimpleModel(simpleModel);
  148 + // 进度信息
  149 + respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds)
  150 + .setFinishedTaskActivityIds(finishedTaskActivityIds)
  151 + .setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds)
  152 + .setRejectedTaskActivityIds(rejectTaskActivityIds);
  153 + return respVO;
  154 + }
  155 +
  156 + default UserSimpleBaseVO buildUser(String userIdStr,
  157 + Map<Long, AdminUserRespDTO> userMap,
  158 + Map<Long, DeptRespDTO> deptMap) {
  159 + if (StrUtil.isEmpty(userIdStr)) {
  160 + return null;
  161 + }
  162 + Long userId = NumberUtils.parseLong(userIdStr);
  163 + return buildUser(userId, userMap, deptMap);
  164 + }
  165 +
  166 + default UserSimpleBaseVO buildUser(Long userId,
  167 + Map<Long, AdminUserRespDTO> userMap,
  168 + Map<Long, DeptRespDTO> deptMap) {
  169 + if (userId == null) {
  170 + return null;
  171 + }
  172 + AdminUserRespDTO user = userMap.get(userId);
  173 + if (user == null) {
  174 + return null;
  175 + }
  176 + UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class);
  177 + DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null;
  178 + if (dept != null) {
  179 + userVO.setDeptName(dept.getName());
  180 + }
  181 + return userVO;
  182 + }
  183 +
  184 + default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) {
  185 + if (task == null) {
  186 + return null;
  187 + }
  188 + return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class)
  189 + .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
  190 + }
  191 +
  192 + default Set<Long> parseUserIds(HistoricProcessInstance processInstance,
  193 + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
  194 + BpmTaskRespVO todoTask) {
  195 + Set<Long> userIds = new HashSet<>();
  196 + if (processInstance != null) {
  197 + userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
  198 + }
  199 + for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) {
  200 + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee));
  201 + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner));
  202 + CollUtil.addAll(userIds, activityNode.getCandidateUserIds());
  203 + }
  204 + if (todoTask != null) {
  205 + CollUtil.addIfAbsent(userIds, todoTask.getAssignee());
  206 + CollUtil.addIfAbsent(userIds, todoTask.getOwner());
  207 + if (CollUtil.isNotEmpty(todoTask.getChildren())) {
  208 + CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee));
  209 + CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner));
  210 + }
  211 + }
  212 + return userIds;
  213 + }
  214 +
  215 + default Set<Long> parseUserIds02(HistoricProcessInstance processInstance,
  216 + List<HistoricTaskInstance> tasks) {
  217 + Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
  218 + tasks.forEach(task -> {
  219 + CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee())));
  220 + CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner())));
  221 + });
  222 + return userIds;
  223 + }
  224 +
  225 + default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel,
  226 + ProcessDefinition processDefinition,
  227 + BpmProcessDefinitionInfoDO processDefinitionInfo,
  228 + HistoricProcessInstance processInstance,
  229 + Integer processInstanceStatus,
  230 + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
  231 + BpmTaskRespVO todoTask,
  232 + Map<String, String> formFieldsPermission,
  233 + Map<Long, AdminUserRespDTO> userMap,
  234 + Map<Long, DeptRespDTO> deptMap) {
  235 + // 1.1 流程实例
  236 + BpmProcessInstanceRespVO processInstanceResp = null;
  237 + if (processInstance != null) {
  238 + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
  239 + DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null;
  240 + processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept);
  241 + }
  242 +
  243 + // 1.2 流程定义
  244 + BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
  245 + processDefinition, null, processDefinitionInfo, null, null, bpmnModel);
  246 +
  247 + // 1.3 流程节点
  248 + activityNodes.forEach(approveNode -> {
  249 + if (approveNode.getTasks() != null) {
  250 + approveNode.getTasks().forEach(task -> {
  251 + task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap));
  252 + task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap));
  253 + });
  254 + }
  255 + approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap)));
  256 + });
  257 +
  258 + // 1.4 待办任务
  259 + if (todoTask != null) {
  260 + todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap));
  261 + todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap));
  262 + if (CollUtil.isNotEmpty(todoTask.getChildren())) {
  263 + todoTask.getChildren().forEach(childTask -> {
  264 + childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap));
  265 + childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap));
  266 + });
  267 + }
  268 + }
  269 +
  270 + // 2. 拼接起来
  271 + return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus)
  272 + .setProcessDefinition(definitionResp)
  273 + .setProcessInstance(processInstanceResp)
  274 + .setFormFieldsPermission(formFieldsPermission)
  275 + .setTodoTask(todoTask)
  276 + .setActivityNodes(activityNodes);
  277 + }
  278 +
112 279 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
1 1 package cn.iocoder.yudao.module.bpm.convert.task;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.map.MapUtil;
4   -import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9   -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
  9 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
10 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
11 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
12 12 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
13   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
14 13 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
15 14 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
16 15 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
17 16 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
18   -import org.flowable.bpmn.model.BpmnModel;
19 17 import org.flowable.engine.history.HistoricProcessInstance;
20 18 import org.flowable.engine.runtime.ProcessInstance;
21 19 import org.flowable.task.api.Task;
... ... @@ -28,7 +26,7 @@ import java.util.Date;
28 26 import java.util.List;
29 27 import java.util.Map;
30 28  
31   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
32 30 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
33 31  
34 32 /**
... ... @@ -51,7 +49,7 @@ public interface BpmTaskConvert {
51 49 }
52 50 taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
53 51 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
54   - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
  52 + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
55 53 });
56 54 }
57 55  
... ... @@ -65,7 +63,7 @@ public interface BpmTaskConvert {
65 63 // 用户信息
66 64 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
67 65 if (assignUser != null) {
68   - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
  66 + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
69 67 findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
70 68 }
71 69 // 流程实例
... ... @@ -73,7 +71,7 @@ public interface BpmTaskConvert {
73 71 if (processInstance != null) {
74 72 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
75 73 taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
76   - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
  74 + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
77 75 }
78 76 return taskVO;
79 77 });
... ... @@ -81,19 +79,17 @@ public interface BpmTaskConvert {
81 79 }
82 80  
83 81 default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList,
84   - HistoricProcessInstance processInstance,
85 82 Map<Long, BpmFormDO> formMap,
86 83 Map<Long, AdminUserRespDTO> userMap,
87   - Map<Long, DeptRespDTO> deptMap,
88   - BpmnModel bpmnModel) {
89   - List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> {
  84 + Map<Long, DeptRespDTO> deptMap) {
  85 + return CollectionUtils.convertList(taskList, task -> {
  86 + // 特殊:已取消的任务,不返回
90 87 BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
91 88 Integer taskStatus = FlowableUtils.getTaskStatus(task);
  89 + if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) {
  90 + return null;
  91 + }
92 92 taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task));
93   - // 流程实例
94   - AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
95   - taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
96   - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
97 93 // 表单信息
98 94 BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class);
99 95 if (form != null) {
... ... @@ -101,35 +97,10 @@ public interface BpmTaskConvert {
101 97 .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task));
102 98 }
103 99 // 用户信息
104   - AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
105   - if (assignUser != null) {
106   - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
107   - findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
108   - }
109   - AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
110   - if (ownerUser != null) {
111   - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
112   - findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
113   - }
114   - if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){
115   - // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置
116   - // TODO @jason:貌似这么返回,主要解决当前审批 task 的表单权限,但是不同抄送人的表单权限,可能不太对。例如说,对 A 抄送人是隐藏某个字段。
117   - // @芋艿 表单权限需要分离开。单独的接口来获取了 BpmProcessInstanceService.getProcessInstanceFormFieldsPermission
118   - taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey()));
119   - // 操作按钮设置
120   - taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey()));
121   - }
122   - return taskVO;
  100 + buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap);
  101 + buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap);
  102 + return taskVO;
123 103 });
124   -
125   - // 拼接父子关系
126   - Map<String, List<BpmTaskRespVO>> childrenTaskMap = convertMultiMap(
127   - filterList(taskVOList, r -> StrUtil.isNotEmpty(r.getParentTaskId())),
128   - BpmTaskRespVO::getParentTaskId);
129   - for (BpmTaskRespVO taskVO : taskVOList) {
130   - taskVO.setChildren(childrenTaskMap.get(taskVO.getId()));
131   - }
132   - return filterList(taskVOList, r -> StrUtil.isEmpty(r.getParentTaskId()));
133 104 }
134 105  
135 106 default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList,
... ... @@ -138,7 +109,7 @@ public interface BpmTaskConvert {
138 109 return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> {
139 110 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
140 111 if (assignUser != null) {
141   - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
  112 + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
142 113 DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
143 114 if (dept != null) {
144 115 taskVO.getAssigneeUser().setDeptName(dept.getName());
... ... @@ -146,12 +117,21 @@ public interface BpmTaskConvert {
146 117 }
147 118 AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
148 119 if (ownerUser != null) {
149   - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
  120 + taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
150 121 findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
151 122 }
152 123 }));
153 124 }
154 125  
  126 + default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
  127 + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) {
  128 + return BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
  129 + .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
  130 + .setButtonsSetting(buttonsSetting)
  131 + .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
  132 + .setStatus(FlowableUtils.getTaskStatus(childTask))));
  133 + }
  134 +
155 135 default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
156 136 Task task) {
157 137 BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
... ... @@ -162,6 +142,42 @@ public interface BpmTaskConvert {
162 142 return reqDTO;
163 143 }
164 144  
  145 + default void buildTaskOwner(BpmTaskRespVO task, String taskOwner,
  146 + Map<Long, AdminUserRespDTO> userMap,
  147 + Map<Long, DeptRespDTO> deptMap) {
  148 + AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner));
  149 + if (ownerUser != null) {
  150 + task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
  151 + findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName()));
  152 + }
  153 + }
  154 +
  155 + default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap,
  156 + Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
  157 + List<Task> childTasks = childrenTaskMap.get(task.getId());
  158 + if (CollUtil.isNotEmpty(childTasks)) {
  159 + task.setChildren(
  160 + convertList(childTasks, childTask -> {
  161 + BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class);
  162 + childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask));
  163 + buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap);
  164 + buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap);
  165 + return childTaskVO;
  166 + })
  167 + );
  168 + }
  169 + }
  170 +
  171 + default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee,
  172 + Map<Long, AdminUserRespDTO> userMap,
  173 + Map<Long, DeptRespDTO> deptMap) {
  174 + AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee));
  175 + if (assignUser != null) {
  176 + task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
  177 + findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
  178 + }
  179 + }
  180 +
165 181 /**
166 182 * 将父任务的属性,拷贝到子任务(加签任务)
167 183 * <p>
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
1 1 package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
4 5 import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
5 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
6 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
... ... @@ -122,6 +123,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
122 123 * 目的:如果 false 不可见,则不展示在“发起流程”的列表里
123 124 */
124 125 private Boolean visible;
  126 + /**
  127 + * 排序值
  128 + */
  129 + private Long sort;
125 130  
126 131 /**
127 132 * 可发起用户编号数组
... ... @@ -134,7 +139,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
134 139 * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起
135 140 * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的
136 141 */
137   - @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
  142 + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
138 143 private List<Long> startUserIds;
139 144  
140 145 /**
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
... ... @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import com.baomidou.mybatisplus.annotation.KeySequence;
5 5 import com.baomidou.mybatisplus.annotation.TableId;
6 6 import com.baomidou.mybatisplus.annotation.TableName;
  7 +import org.flowable.bpmn.model.FlowNode;
  8 +import org.flowable.task.api.history.HistoricTaskInstance;
7 9 import lombok.AllArgsConstructor;
8 10 import lombok.Builder;
9 11 import lombok.Data;
... ... @@ -54,23 +56,25 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
54 56 */
55 57 private String category;
56 58 /**
57   - * 流程活动编号
  59 + * 流程活动的编号
58 60 * <p/>
59   - * 对应 BPMN XML 节点编号,用于查询抄送节点的表单字段权限
60   - * 这里冗余的原因:如果是钉钉易搭的抄送节点 (ServiceTask),使用 taskId 可能查不到对应的 activityId
  61 + *
  62 + * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号
  63 + * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId
61 64 */
62 65 private String activityId;
63 66 /**
64   - * 任务主键
65   - * 关联 Task 的 id 属性
  67 + * 流程活动的名字
  68 + *
  69 + * 冗余 {@link FlowNode#getName()}
66 70 */
67   - private String taskId;
  71 + private String activityName;
68 72 /**
69   - * 任务名称
  73 + * 流程活动的编号
70 74 *
71   - * 冗余 Task 的 name 属性
  75 + * 关联 {@link HistoricTaskInstance#getId()}
72 76 */
73   - private String taskName;
  77 + private String taskId;
74 78  
75 79 /**
76 80 * 用户编号(被抄送的用户编号)
... ... @@ -79,4 +83,9 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
79 83 */
80 84 private Long userId;
81 85  
  86 + /**
  87 + * 抄送意见
  88 + */
  89 + private String reason;
  90 +
82 91 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
1 1 package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
2 2  
  3 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
3 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import org.apache.ibatis.annotations.Mapper;
... ... @@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX&lt;BpmProcessDe
18 19 return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId);
19 20 }
20 21  
  22 + default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) {
  23 + update(updateObj,
  24 + new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId));
  25 + }
  26 +
21 27 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
... ... @@ -53,7 +53,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
53 53 @SuppressWarnings("unchecked")
54 54 Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
55 55 if (assigneeUserIds == null) {
56   - assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
  56 + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
57 57 execution.setVariable(super.collectionVariable, assigneeUserIds);
58 58 if (CollUtil.isEmpty(assigneeUserIds)) {
59 59 // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
... ... @@ -46,7 +46,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
46 46 @SuppressWarnings("unchecked")
47 47 Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
48 48 if (assigneeUserIds == null) {
49   - assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
  49 + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
50 50 execution.setVariable(super.collectionVariable, assigneeUserIds);
51 51 if (CollUtil.isEmpty(assigneeUserIds)) {
52 52 // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
... ... @@ -57,7 +57,7 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
57 57  
58 58 // 情况二,如果非多实例的任务,则计算任务处理人
59 59 // 第一步,先计算可处理该任务的处理人们
60   - Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
  60 + Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
61 61 if (CollUtil.isEmpty(candidateUserIds)) {
62 62 return null;
63 63 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
... ... @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
18 18 import com.google.common.annotations.VisibleForTesting;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.flowable.bpmn.model.BpmnModel;
  21 +import org.flowable.bpmn.model.FlowElement;
21 22 import org.flowable.bpmn.model.UserTask;
22 23 import org.flowable.engine.delegate.DelegateExecution;
23 24 import org.flowable.engine.runtime.ProcessInstance;
... ... @@ -89,32 +90,66 @@ public class BpmTaskCandidateInvoker {
89 90 * @return 用户编号集合
90 91 */
91 92 @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人
92   - public Set<Long> calculateUsers(DelegateExecution execution) {
  93 + public Set<Long> calculateUsersByTask(DelegateExecution execution) {
93 94 // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
94   - Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement());
  95 + FlowElement flowElement = execution.getCurrentFlowElement();
  96 + Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
95 97 if (ObjectUtils.equalsAny(approveType,
96 98 BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
97 99 BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
98 100 return new HashSet<>();
99 101 }
100 102  
101   - Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
102   - String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
103 103 // 1.1 计算任务的候选人
104   - Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
  104 + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
  105 + String param = BpmnModelUtils.parseCandidateParam(flowElement);
  106 + Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param);
  107 + // 1.2 移除被禁用的用户
105 108 removeDisableUsers(userIds);
  109 +
  110 + // 2. 候选人为空时,根据“审批人为空”的配置补充
  111 + if (CollUtil.isEmpty(userIds)) {
  112 + userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
  113 + .calculateUsersByTask(execution, param);
  114 + // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
  115 + }
  116 +
  117 + // 3. 移除发起人的用户
  118 + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
  119 + .getProcessInstance(execution.getProcessInstanceId());
  120 + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
  121 + removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId()));
  122 + return userIds;
  123 + }
  124 +
  125 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
  126 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  127 + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
  128 + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
  129 + Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
  130 + if (ObjectUtils.equalsAny(approveType,
  131 + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
  132 + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
  133 + return new HashSet<>();
  134 + }
  135 +
  136 + // 1.1 计算任务的候选人
  137 + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
  138 + String param = BpmnModelUtils.parseCandidateParam(flowElement);
  139 + Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param,
  140 + startUserId, processDefinitionId, processVariables);
106 141 // 1.2 移除被禁用的用户
107 142 removeDisableUsers(userIds);
108 143  
109 144 // 2. 候选人为空时,根据“审批人为空”的配置补充
110 145 if (CollUtil.isEmpty(userIds)) {
111 146 userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
112   - .calculateUsers(execution, param);
  147 + .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables);
113 148 // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
114 149 }
115 150  
116 151 // 3. 移除发起人的用户
117   - removeStartUserIfSkip(execution, userIds);
  152 + removeStartUserIfSkip(userIds, flowElement, startUserId);
118 153 return userIds;
119 154 }
120 155  
... ... @@ -126,7 +161,7 @@ public class BpmTaskCandidateInvoker {
126 161 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
127 162 assigneeUserIds.removeIf(id -> {
128 163 AdminUserRespDTO user = userMap.get(id);
129   - return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
  164 + return user == null || CommonStatusEnum.isDisable(user.getStatus());
130 165 });
131 166 }
132 167  
... ... @@ -135,25 +170,23 @@ public class BpmTaskCandidateInvoker {
135 170 *
136 171 * 注意:如果只有一个候选人,则不处理,避免无法审批
137 172 *
138   - * @param execution 执行中的任务
139 173 * @param assigneeUserIds 当前分配的候选人
  174 + * @param flowElement 当前节点
  175 + * @param startUserId 发起人
140 176 */
141 177 @VisibleForTesting
142   - void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) {
  178 + void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) {
143 179 if (CollUtil.size(assigneeUserIds) <= 1) {
144 180 return;
145 181 }
146   - Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement());
  182 + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement);
147 183 if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
148 184 return;
149 185 }
150   - ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
151   - .getProcessInstance(execution.getProcessInstanceId());
152   - Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
153   - assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId()));
  186 + assigneeUserIds.remove(startUserId);
154 187 }
155 188  
156   - public BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
  189 + private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
157 190 BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
158 191 Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
159 192 BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
2 2  
3 3 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  4 +import org.flowable.bpmn.model.BpmnModel;
4 5 import org.flowable.engine.delegate.DelegateExecution;
5   -import org.flowable.engine.runtime.ProcessInstance;
6 6  
7   -import java.util.Collections;
  7 +import java.util.Map;
8 8 import java.util.Set;
9 9  
10 10 /**
... ... @@ -42,47 +42,44 @@ public interface BpmTaskCandidateStrategy {
42 42 /**
43 43 * 基于候选人参数,获得任务的候选用户们
44 44 *
  45 + * 注意:实现 calculateUsers 系列方法时,有两种选择:
  46 + * 1. 只重写 calculateUsers 默认方法
  47 + * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法
  48 + *
45 49 * @param param 执行任务
46 50 * @return 用户编号集合
47 51 */
48 52 default Set<Long> calculateUsers(String param) {
49   - return Collections.emptySet();
  53 + throw new UnsupportedOperationException("该分配方法未实现,请检查!");
50 54 }
51 55  
52 56 /**
53   - * 基于执行任务,获得任务的候选用户们
  57 + * 基于【执行任务】,获得任务的候选用户们
54 58 *
55 59 * @param execution 执行任务
56 60 * @return 用户编号集合
57 61 */
58   - default Set<Long> calculateUsers(DelegateExecution execution, String param) {
59   - Set<Long> users = calculateUsers(param);
60   - removeDisableUsers(users);
61   - return users;
  62 + default Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  63 + return calculateUsers(param);
62 64 }
63 65  
64 66 /**
65   - * 基于流程实例,获得任务的候选用户们
  67 + * 基于【流程活动】,获得任务的候选用户们
66 68 * <p>
67 69 * 目的:用于获取未执行节点的候选用户们
68 70 *
69   - * @param startUserId 流程发起人编号
70   - * @param processInstance 流程实例编号
71   - * @param activityId 活动 Id (对应 Bpmn XML id)
  71 + * @param bpmnModel 流程图
  72 + * @param activityId 活动 ID (对应 Bpmn XML id)
72 73 * @param param 节点的参数
  74 + * @param startUserId 流程发起人编号
  75 + * @param processDefinitionId 流程定义编号
  76 + * @param processVariables 流程变量
73 77 * @return 用户编号集合
74 78 */
75   - default Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
76   - Set<Long> users = calculateUsers(param);
77   - removeDisableUsers(users);
78   - return users;
  79 + @SuppressWarnings("unused")
  80 + default Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  81 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  82 + return calculateUsers(param);
79 83 }
80 84  
81   - /**
82   - * 移除被禁用的用户
83   - *
84   - * @param users 用户 Ids
85   - */
86   - void removeDisableUsers(Set<Long> users);
87   -
88 85 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java deleted
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
2   -
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
7   -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
8   -
9   -import java.util.Map;
10   -import java.util.Set;
11   -
12   -/**
13   - * {@link BpmTaskCandidateStrategy} 抽象类
14   - *
15   - * @author jason
16   - */
17   -public abstract class BpmTaskCandidateAbstractStrategy implements BpmTaskCandidateStrategy {
18   -
19   - protected AdminUserApi adminUserApi;
20   -
21   - public BpmTaskCandidateAbstractStrategy(AdminUserApi adminUserApi) {
22   - this.adminUserApi = adminUserApi;
23   - }
24   -
25   - @Override
26   - public void removeDisableUsers(Set<Long> users) {
27   - if (CollUtil.isEmpty(users)) {
28   - return;
29   - }
30   - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(users);
31   - users.removeIf(id -> {
32   - AdminUserRespDTO user = userMap.get(id);
33   - return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
34   - });
35   - }
36   -
37   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
... ... @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
6 6 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
7 7 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
8 8 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  9 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  10 +import jakarta.annotation.Resource;
9 11  
10 12 import java.util.*;
11 13  
... ... @@ -14,14 +16,12 @@ import java.util.*;
14 16 *
15 17 * @author jason
16 18 */
17   -public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
  19 +public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
18 20  
  21 + @Resource
19 22 protected DeptApi deptApi;
20   -
21   - public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
22   - super(adminUserApi);
23   - this.deptApi = deptApi;
24   - }
  23 + @Resource
  24 + protected AdminUserApi adminUserApi;
25 25  
26 26 /**
27 27 * 获得指定层级的部门负责人,只有第 level 的负责人
... ... @@ -75,4 +75,17 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTas
75 75 return deptLeaderIds;
76 76 }
77 77  
  78 + /**
  79 + * 获取发起人的部门
  80 + *
  81 + * @param startUserId 发起人 Id
  82 + */
  83 + protected DeptRespDTO getStartUserDept(Long startUserId) {
  84 + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
  85 + if (startUser.getDeptId() == null) { // 找不到部门
  86 + return null;
  87 + }
  88 + return deptApi.getDept(startUser.getDeptId());
  89 + }
  90 +
78 91 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7   -import cn.iocoder.yudao.module.system.api.dept.DeptApi;
8   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
9 7 import org.springframework.stereotype.Component;
10 8  
  9 +import java.util.List;
11 10 import java.util.Set;
12 11  
13 12 /**
... ... @@ -16,11 +15,7 @@ import java.util.Set;
16 15 * @author jason
17 16 */
18 17 @Component
19   -public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
20   -
21   - public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
22   - super(adminUserApi, deptApi);
23   - }
  18 +public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
24 19  
25 20 @Override
26 21 public BpmTaskCandidateStrategyEnum getStrategy() {
... ... @@ -32,14 +27,19 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs
32 27 // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级
33 28 String[] params = param.split("\\|");
34 29 Assert.isTrue(params.length == 2, "参数格式不匹配");
35   - deptApi.validateDeptList(StrUtils.splitToLong(params[0], ","));
36   - Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0");
  30 + List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
  31 + int level = Integer.parseInt(params[1]);
  32 + // 校验部门存在
  33 + deptApi.validateDeptList(deptIds);
  34 + Assert.isTrue(level > 0, "部门层级必须大于 0");
37 35 }
38 36  
39 37 @Override
40 38 public Set<Long> calculateUsers(String param) {
41 39 String[] params = param.split("\\|");
42   - return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1]));
  40 + List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
  41 + int level = Integer.parseInt(params[1]);
  42 + return super.getMultiLevelDeptLeaderIds(deptIds, level);
43 43 }
44 44  
45 45 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 6 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
7 7 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
8   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  8 +import jakarta.annotation.Resource;
9 9 import org.springframework.stereotype.Component;
10 10  
11 11 import java.util.List;
... ... @@ -19,14 +19,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
19 19 * @author kyle
20 20 */
21 21 @Component
22   -public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
  22 +public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
23 23  
24   - private final DeptApi deptApi;
25   -
26   - public BpmTaskCandidateDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
27   - super(adminUserApi);
28   - this.deptApi = deptApi;
29   - }
  24 + @Resource
  25 + private DeptApi deptApi;
30 26  
31 27 @Override
32 28 public BpmTaskCandidateStrategyEnum getStrategy() {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
6 6 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
7 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
8 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  9 +import jakarta.annotation.Resource;
9 10 import org.springframework.stereotype.Component;
10 11  
11 12 import java.util.List;
... ... @@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
19 20 * @author kyle
20 21 */
21 22 @Component
22   -public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstractStrategy {
  23 +public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy {
23 24  
24   - private final DeptApi deptApi;
25   -
26   - public BpmTaskCandidateDeptMemberStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
27   - super(adminUserApi);
28   - this.deptApi = deptApi;
29   - }
  25 + @Resource
  26 + private DeptApi deptApi;
  27 + @Resource
  28 + private AdminUserApi adminUserApi;
30 29  
31 30 @Override
32 31 public BpmTaskCandidateStrategyEnum getStrategy() {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
8   -import cn.iocoder.yudao.module.system.api.dept.DeptApi;
9 8 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
10   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11   -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 9 import jakarta.annotation.Resource;
  10 +import org.flowable.bpmn.model.BpmnModel;
13 11 import org.flowable.engine.delegate.DelegateExecution;
14 12 import org.flowable.engine.runtime.ProcessInstance;
15 13 import org.springframework.context.annotation.Lazy;
16 14 import org.springframework.stereotype.Component;
17 15  
18 16 import java.util.HashSet;
  17 +import java.util.Map;
19 18 import java.util.Set;
20 19  
21 20 import static cn.hutool.core.collection.ListUtil.toList;
... ... @@ -26,16 +25,12 @@ import static cn.hutool.core.collection.ListUtil.toList;
26 25 * @author jason
27 26 */
28 27 @Component
29   -public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
  28 +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
30 29  
31 30 @Resource
32 31 @Lazy
33 32 private BpmProcessInstanceService processInstanceService;
34 33  
35   - public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
36   - super(adminUserApi, deptApi);
37   - }
38   -
39 34 @Override
40 35 public BpmTaskCandidateStrategyEnum getStrategy() {
41 36 return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
... ... @@ -43,48 +38,33 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan
43 38  
44 39 @Override
45 40 public void validateParam(String param) {
46   - // 参数是部门的层级
47   - Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
  41 + int level = Integer.parseInt(param); // 参数是部门的层级
  42 + Assert.isTrue(level > 0, "部门的层级必须大于 0");
48 43 }
49 44  
50 45 @Override
51   - public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  46 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  47 + int level = Integer.parseInt(param); // 参数是部门的层级
52 48 // 获得流程发起人
53 49 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
54 50 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
55 51 // 获取发起人的 multi 部门负责人
56   - DeptRespDTO dept = getStartUserDept(startUserId);
  52 + DeptRespDTO dept = super.getStartUserDept(startUserId);
57 53 if (dept == null) {
58 54 return new HashSet<>();
59 55 }
60   - Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
61   - // TODO @jason:这里 removeDisableUsers 的原因是啥呀?
62   - removeDisableUsers(users);
63   - return users;
  56 + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
64 57 }
65 58  
66 59 @Override
67   - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
68   - DeptRespDTO dept = getStartUserDept(startUserId);
  60 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  61 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  62 + int level = Integer.parseInt(param); // 参数是部门的层级
  63 + DeptRespDTO dept = super.getStartUserDept(startUserId);
69 64 if (dept == null) {
70 65 return new HashSet<>();
71 66 }
72   - Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
73   - removeDisableUsers(users);
74   - return users;
75   - }
76   -
77   - /**
78   - * 获取发起人的部门
79   - *
80   - * @param startUserId 发起人 Id
81   - */
82   - protected DeptRespDTO getStartUserDept(Long startUserId) {
83   - AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
84   - if (startUser.getDeptId() == null) { // 找不到部门
85   - return null;
86   - }
87   - return deptApi.getDept(startUser.getDeptId());
  67 + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
88 68 }
89 69  
90 70 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
8   -import cn.iocoder.yudao.module.system.api.dept.DeptApi;
9 8 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
10   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11   -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 9 import jakarta.annotation.Resource;
  10 +import org.flowable.bpmn.model.BpmnModel;
13 11 import org.flowable.engine.delegate.DelegateExecution;
14 12 import org.flowable.engine.runtime.ProcessInstance;
15 13 import org.springframework.context.annotation.Lazy;
16 14 import org.springframework.stereotype.Component;
17 15  
18 16 import java.util.HashSet;
  17 +import java.util.Map;
19 18 import java.util.Set;
20 19  
21 20 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
... ... @@ -26,7 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
26 25 * @author jason
27 26 */
28 27 @Component
29   -public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
  28 +public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
30 29  
31 30 @Resource
32 31 @Lazy // 避免循环依赖
... ... @@ -37,10 +36,6 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat
37 36 return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER;
38 37 }
39 38  
40   - public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
41   - super(adminUserApi, deptApi);
42   - }
43   -
44 39 @Override
45 40 public void validateParam(String param) {
46 41 // 参数是部门的层级
... ... @@ -48,44 +43,29 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat
48 43 }
49 44  
50 45 @Override
51   - public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  46 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
52 47 // 获得流程发起人
53 48 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
54 49 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
55 50 // 获取发起人的部门负责人
56   - Set<Long> users = getStartUserDeptLeader(startUserId, param);
57   - removeDisableUsers(users);
58   - return users;
  51 + return getStartUserDeptLeader(startUserId, param);
59 52 }
60 53  
61 54 @Override
62   - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
  55 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  56 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
63 57 // 获取发起人的部门负责人
64   - Set<Long> users = getStartUserDeptLeader(startUserId, param);
65   - removeDisableUsers(users);
66   - return users;
  58 + return getStartUserDeptLeader(startUserId, param);
67 59 }
68 60  
69 61 private Set<Long> getStartUserDeptLeader(Long startUserId, String param) {
70   - DeptRespDTO dept = getStartUserDept(startUserId);
  62 + int level = Integer.parseInt(param); // 参数是部门的层级
  63 + DeptRespDTO dept = super.getStartUserDept(startUserId);
71 64 if (dept == null) {
72 65 return new HashSet<>();
73 66 }
74   - Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级
  67 + Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level);
75 68 return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>();
76 69 }
77 70  
78   - /**
79   - * 获取发起人的部门
80   - *
81   - * @param startUserId 发起人 Id
82   - */
83   - protected DeptRespDTO getStartUserDept(Long startUserId) {
84   - AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
85   - if (startUser.getDeptId() == null) { // 找不到部门
86   - return null;
87   - }
88   - return deptApi.getDept(startUser.getDeptId());
89   - }
90   -
91 71 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.ObjectUtil;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
5 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
7 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
8 10 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
9   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  11 +import com.google.common.collect.Sets;
10 12 import jakarta.annotation.Resource;
11 13 import org.flowable.bpmn.model.BpmnModel;
  14 +import org.flowable.bpmn.model.ServiceTask;
  15 +import org.flowable.bpmn.model.Task;
12 16 import org.flowable.bpmn.model.UserTask;
13 17 import org.flowable.engine.delegate.DelegateExecution;
14 18 import org.flowable.engine.runtime.ProcessInstance;
... ... @@ -23,16 +27,12 @@ import java.util.*;
23 27 * @author 芋道源码
24 28 */
25 29 @Component
26   -public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy {
  30 +public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
27 31  
28 32 @Resource
29 33 @Lazy // 延迟加载,避免循环依赖
30 34 private BpmProcessInstanceService processInstanceService;
31 35  
32   - public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) {
33   - super(adminUserApi);
34   - }
35   -
36 36 @Override
37 37 public BpmTaskCandidateStrategyEnum getStrategy() {
38 38 return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
... ... @@ -42,7 +42,12 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs
42 42 public void validateParam(String param) {}
43 43  
44 44 @Override
45   - public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  45 + public boolean isParamRequired() {
  46 + return false;
  47 + }
  48 +
  49 + @Override
  50 + public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
46 51 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
47 52 Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
48 53 Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
... ... @@ -50,47 +55,43 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs
50 55 execution.getProcessInstanceId());
51 56 // 获得审批人
52 57 List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
53   - Set<Long> users = new LinkedHashSet<>(assignees);
54   - removeDisableUsers(users);
55   - return users;
  58 + return new LinkedHashSet<>(assignees);
56 59 }
57 60  
58 61 @Override
59   - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
60   - if (processInstance == null) {
61   - return Collections.emptySet();
  62 + public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  63 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  64 + if (processVariables == null) {
  65 + return Sets.newLinkedHashSet();
  66 + }
  67 + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables);
  68 + if (startUserSelectAssignees == null) {
  69 + return Sets.newLinkedHashSet();
62 70 }
63   - Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
64   - Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId());
65 71 // 获得审批人
66 72 List<Long> assignees = startUserSelectAssignees.get(activityId);
67   - Set<Long> users = new LinkedHashSet<>(assignees);
68   - removeDisableUsers(users);
69   - return users;
70   - }
71   -
72   - @Override
73   - public boolean isParamRequired() {
74   - return false;
  73 + return new LinkedHashSet<>(assignees);
75 74 }
76 75  
77 76 /**
78   - * 获得发起人自选审批人的 UserTask 列表
  77 + * 获得发起人自选审批人或抄送人的 Task 列表
79 78 *
80 79 * @param bpmnModel BPMN 模型
81   - * @return UserTask 列表
  80 + * @return Task 列表
82 81 */
83   - public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) {
  82 + public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {
84 83 if (bpmnModel == null) {
85   - return null;
  84 + return Collections.emptyList();
86 85 }
87   - List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
88   - if (CollUtil.isEmpty(userTaskList)) {
89   - return null;
  86 + List<Task> tasks = new ArrayList<>();
  87 + tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));
  88 + tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));
  89 + if (CollUtil.isEmpty(tasks)) {
  90 + return Collections.emptyList();
90 91 }
91   - userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask),
  92 + tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),
92 93 BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
93   - return userTaskList;
  94 + return tasks;
94 95 }
95 96  
96 97 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  8 +import org.flowable.bpmn.model.BpmnModel;
  9 +import org.flowable.engine.delegate.DelegateExecution;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import java.util.Map;
  13 +import java.util.Set;
  14 +
  15 +/**
  16 + * 表单内部门负责人 {@link BpmTaskCandidateStrategy} 实现类
  17 + *
  18 + * @author jason
  19 + */
  20 +@Component
  21 +public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
  22 +
  23 + @Override
  24 + public BpmTaskCandidateStrategyEnum getStrategy() {
  25 + return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER;
  26 + }
  27 +
  28 + @Override
  29 + public void validateParam(String param) {
  30 + // 参数格式: | 分隔:1)左边为表单内部门字段。2)右边为部门层级
  31 + String[] params = param.split("\\|");
  32 + Assert.isTrue(params.length == 2, "参数格式不匹配");
  33 + Assert.notEmpty(param, "表单内部门字段不能为空");
  34 + int level = Integer.parseInt(params[1]);
  35 + Assert.isTrue(level > 0, "部门层级必须大于 0");
  36 + }
  37 +
  38 + @Override
  39 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  40 + String[] params = param.split("\\|");
  41 + Object result = execution.getVariable(params[0]);
  42 + int level = Integer.parseInt(params[1]);
  43 + return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
  44 + }
  45 +
  46 + @Override
  47 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
  48 + String param, Long startUserId, String processDefinitionId,
  49 + Map<String, Object> processVariables) {
  50 + String[] params = param.split("\\|");
  51 + Object result = processVariables == null ? null : processVariables.get(params[0]);
  52 + int level = Integer.parseInt(params[1]);
  53 + return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
  54 + }
  55 +
  56 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  8 +import org.flowable.bpmn.model.BpmnModel;
  9 +import org.flowable.engine.delegate.DelegateExecution;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import java.util.Map;
  13 +import java.util.Set;
  14 +
  15 +/**
  16 + * 表单内用户字段 {@link BpmTaskCandidateUserStrategy} 实现类
  17 + *
  18 + * @author jason
  19 + */
  20 +@Component
  21 +public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy {
  22 +
  23 + @Override
  24 + public BpmTaskCandidateStrategyEnum getStrategy() {
  25 + return BpmTaskCandidateStrategyEnum.FORM_USER;
  26 + }
  27 +
  28 + @Override
  29 + public void validateParam(String param) {
  30 + Assert.notEmpty(param, "表单内用户字段不能为空");
  31 + }
  32 +
  33 + @Override
  34 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  35 + Object result = execution.getVariable(param);
  36 + return Convert.toSet(Long.class, result);
  37 + }
  38 +
  39 + @Override
  40 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
  41 + String param, Long startUserId, String processDefinitionId,
  42 + Map<String, Object> processVariables) {
  43 + Object result = processVariables == null ? null : processVariables.get(param);
  44 + return Convert.toSet(Long.class, result);
  45 + }
  46 +
  47 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
... ... @@ -7,13 +7,15 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
7 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
8 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
9 9 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
10   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11 10 import jakarta.annotation.Resource;
  11 +import org.flowable.bpmn.model.BpmnModel;
  12 +import org.flowable.bpmn.model.FlowElement;
12 13 import org.flowable.engine.delegate.DelegateExecution;
13 14 import org.springframework.context.annotation.Lazy;
14 15 import org.springframework.stereotype.Component;
15 16  
16 17 import java.util.HashSet;
  18 +import java.util.Map;
17 19 import java.util.Objects;
18 20 import java.util.Set;
19 21  
... ... @@ -23,16 +25,12 @@ import java.util.Set;
23 25 * @author kyle
24 26 */
25 27 @Component
26   -public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy {
  28 +public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy {
27 29  
28 30 @Resource
29 31 @Lazy // 延迟加载,避免循环依赖
30 32 private BpmProcessDefinitionService processDefinitionService;
31 33  
32   - public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) {
33   - super(adminUserApi);
34   - }
35   -
36 34 @Override
37 35 public BpmTaskCandidateStrategyEnum getStrategy() {
38 36 return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
... ... @@ -43,19 +41,28 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac
43 41 }
44 42  
45 43 @Override
46   - public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  44 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
  45 + return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement());
  46 + }
  47 +
  48 + @Override
  49 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  50 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  51 + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
  52 + return getCandidateUsers(processDefinitionId, flowElement);
  53 + }
  54 +
  55 + private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) {
47 56 // 情况一:指定人员审批
48   - Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement());
  57 + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement);
49 58 if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
50   - Set<Long> users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement()));
51   - removeDisableUsers(users);
52   - return users;
  59 + return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement));
53 60 }
54 61  
55 62 // 情况二:流程管理员
56 63 if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
57   - BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
58   - Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId());
  64 + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId);
  65 + Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId);
59 66 return new HashSet<>(processDefinition.getManagerUserIds());
60 67 }
61 68  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
2 2  
3 3 import cn.hutool.core.convert.Convert;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
7   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  7 +import org.flowable.bpmn.model.BpmnModel;
8 8 import org.flowable.engine.delegate.DelegateExecution;
9 9 import org.springframework.stereotype.Component;
10 10  
  11 +import java.util.Map;
11 12 import java.util.Set;
12 13  
13 14 /**
... ... @@ -16,11 +17,7 @@ import java.util.Set;
16 17 * @author 芋道源码
17 18 */
18 19 @Component
19   -public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstractStrategy {
20   -
21   - public BpmTaskCandidateExpressionStrategy(AdminUserApi adminUserApi) {
22   - super(adminUserApi);
23   - }
  20 +public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
24 21  
25 22 @Override
26 23 public BpmTaskCandidateStrategyEnum getStrategy() {
... ... @@ -33,11 +30,16 @@ public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstract
33 30 }
34 31  
35 32 @Override
36   - public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  33 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
37 34 Object result = FlowableUtils.getExpressionValue(execution, param);
38   - Set<Long> users = Convert.toSet(Long.class, result);
39   - removeDisableUsers(users);
40   - return users;
  35 + return Convert.toSet(Long.class, result);
  36 + }
  37 +
  38 + @Override
  39 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  40 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  41 + Object result = FlowableUtils.getExpressionValue(processVariables, param);
  42 + return Convert.toSet(Long.class, result);
41 43 }
42 44  
43 45 }
44 46 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 7 import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
8   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  8 +import jakarta.annotation.Resource;
9 9 import org.springframework.stereotype.Component;
10 10  
11 11 import java.util.Collection;
... ... @@ -20,14 +20,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
20 20 * @author kyle
21 21 */
22 22 @Component
23   -public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrategy {
  23 +public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy {
24 24  
25   - private final BpmUserGroupService userGroupService;
26   -
27   - public BpmTaskCandidateGroupStrategy(AdminUserApi adminUserApi, BpmUserGroupService userGroupService) {
28   - super(adminUserApi);
29   - this.userGroupService = userGroupService;
30   - }
  25 + @Resource
  26 + private BpmUserGroupService userGroupService;
31 27  
32 28 @Override
33 29 public BpmTaskCandidateStrategyEnum getStrategy() {
... ... @@ -37,7 +33,7 @@ public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrat
37 33 @Override
38 34 public void validateParam(String param) {
39 35 Set<Long> groupIds = StrUtils.splitToLongSet(param);
40   - userGroupService.getUserGroupList(groupIds);
  36 + userGroupService.validUserGroups(groupIds);
41 37 }
42 38  
43 39 @Override
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
6 6 import cn.iocoder.yudao.module.system.api.dept.PostApi;
7 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
8 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  9 +import jakarta.annotation.Resource;
9 10 import org.springframework.stereotype.Component;
10 11  
11 12 import java.util.List;
... ... @@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
19 20 * @author kyle
20 21 */
21 22 @Component
22   -public class BpmTaskCandidatePostStrategy extends BpmTaskCandidateAbstractStrategy {
  23 +public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy {
23 24  
24   - private final PostApi postApi;
25   -
26   - public BpmTaskCandidatePostStrategy(AdminUserApi adminUserApi, PostApi postApi) {
27   - super(adminUserApi);
28   - this.postApi = postApi;
29   - }
  25 + @Resource
  26 + private PostApi postApi;
  27 + @Resource
  28 + private AdminUserApi adminUserApi;
30 29  
31 30 @Override
32 31 public BpmTaskCandidateStrategyEnum getStrategy() {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 6 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
7 7 import cn.iocoder.yudao.module.system.api.permission.RoleApi;
8   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
9 8 import jakarta.annotation.Resource;
10 9 import org.springframework.stereotype.Component;
11 10  
... ... @@ -17,17 +16,13 @@ import java.util.Set;
17 16 * @author kyle
18 17 */
19 18 @Component
20   -public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy {
  19 +public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
21 20  
22 21 @Resource
23 22 private RoleApi roleApi;
24 23 @Resource
25 24 private PermissionApi permissionApi;
26 25  
27   - public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) {
28   - super(adminUserApi);
29   - }
30   -
31 26 @Override
32 27 public BpmTaskCandidateStrategyEnum getStrategy() {
33 28 return BpmTaskCandidateStrategyEnum.ROLE;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  4 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
4 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
5 6 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
6   -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
7 7 import jakarta.annotation.Resource;
  8 +import org.flowable.bpmn.model.BpmnModel;
8 9 import org.flowable.engine.delegate.DelegateExecution;
9 10 import org.flowable.engine.runtime.ProcessInstance;
10 11 import org.springframework.context.annotation.Lazy;
11 12 import org.springframework.stereotype.Component;
12 13  
  14 +import java.util.Map;
13 15 import java.util.Set;
14 16  
15 17 /**
... ... @@ -20,16 +22,12 @@ import java.util.Set;
20 22 * @author jason
21 23 */
22 24 @Component
23   -public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy {
  25 +public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy {
24 26  
25 27 @Resource
26 28 @Lazy // 延迟加载,避免循环依赖
27 29 private BpmProcessInstanceService processInstanceService;
28 30  
29   - public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) {
30   - super(adminUserApi);
31   - }
32   -
33 31 @Override
34 32 public BpmTaskCandidateStrategyEnum getStrategy() {
35 33 return BpmTaskCandidateStrategyEnum.START_USER;
... ... @@ -45,18 +43,15 @@ public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractS
45 43 }
46 44  
47 45 @Override
48   - public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  46 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
49 47 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
50   - Set<Long> users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
51   - removeDisableUsers(users);
52   - return users;
  48 + return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
53 49 }
54 50  
55 51 @Override
56   - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
57   - Set<Long> users = SetUtils.asSet(startUserId);
58   - removeDisableUsers(users);
59   - return users;
  52 + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
  53 + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
  54 + return SetUtils.asSet(startUserId);
60 55 }
61 56  
62 57 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.hutool.core.text.StrPool;
4 4 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  8 +import jakarta.annotation.Resource;
8 9 import org.springframework.stereotype.Component;
9 10  
10 11 import java.util.LinkedHashSet;
11   -import java.util.Set;
12 12  
13 13 /**
14 14 * 用户 {@link BpmTaskCandidateStrategy} 实现类
... ... @@ -16,11 +16,10 @@ import java.util.Set;
16 16 * @author kyle
17 17 */
18 18 @Component
19   -public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrategy {
  19 +public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy {
20 20  
21   - public BpmTaskCandidateUserStrategy(AdminUserApi adminUserApi) {
22   - super(adminUserApi);
23   - }
  21 + @Resource
  22 + private AdminUserApi adminUserApi;
24 23  
25 24 @Override
26 25 public BpmTaskCandidateStrategyEnum getStrategy() {
... ... @@ -33,7 +32,7 @@ public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrate
33 32 }
34 33  
35 34 @Override
36   - public Set<Long> calculateUsers(String param) {
  35 + public LinkedHashSet<Long> calculateUsers(String param) {
37 36 return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA));
38 37 }
39 38  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java
... ... @@ -4,9 +4,10 @@ import org.flowable.common.engine.api.variable.VariableContainer;
4 4 import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
5 5 import org.springframework.stereotype.Component;
6 6  
7   -// TODO @jason:这个自定义转换的原因是啥呀?
8 7 /**
9   - * 根据流程变量 variable 的类型, 转换参数的值
  8 + * 根据流程变量 variable 的类型,转换参数的值
  9 + *
  10 + * 目前用于 ConditionNodeConvert 的 buildConditionExpression 方法中
10 11 *
11 12 * @author jason
12 13 */
... ... @@ -20,11 +21,12 @@ public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVar
20 21 public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) {
21 22 Object variable = variableContainer.getVariable(variableName);
22 23 if (variable != null && parmaValue != null) {
23   - // 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串
  24 + // 如果值不是字符串类型,流程变量的类型是字符串,把值转成字符串
24 25 if (!(parmaValue instanceof String) && variable instanceof String ) {
25 26 return parmaValue.toString();
26 27 }
27 28 }
28 29 return parmaValue;
29 30 }
  31 +
30 32 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
... ... @@ -29,6 +29,8 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable {
29 29 START_USER_DEPT_LEADER(37, "发起人部门负责人"),
30 30 START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
31 31 USER_GROUP(40, "用户组"),
  32 + FORM_USER(50, "表单内用户字段"),
  33 + FORM_DEPT_LEADER(51, "表单内部门负责人"),
32 34 EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
33 35 ASSIGN_EMPTY(1, "审批人为空"),
34 36 ;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
... ... @@ -52,7 +52,7 @@ public interface BpmnModelConstants {
52 52 */
53 53 String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
54 54 /**
55   - * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id
  55 + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的退回的任务 Id
56 56 */
57 57 String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
58 58  
... ... @@ -104,10 +104,6 @@ public interface BpmnModelConstants {
104 104 * BPMN Start Event Node Id
105 105 */
106 106 String START_EVENT_NODE_ID = "StartEvent";
107   - /**
108   - * BPMN Start Event Node Name
109   - */
110   - String START_EVENT_NODE_NAME = "开始";
111 107  
112 108 /**
113 109 * 发起人节点 ID
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
... ... @@ -29,7 +29,12 @@ public class BpmnVariableConstants {
29 29 * @see ProcessInstance#getProcessVariables()
30 30 */
31 31 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
32   -
  32 + /**
  33 + * 流程实例的变量 - 发起用户 ID
  34 + *
  35 + * @see ProcessInstance#getProcessVariables()
  36 + */
  37 + public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID";
33 38 /**
34 39 * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
35 40 *
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java deleted
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
2   -
3   -// TODO @jason:要不合并到 BpmnModelConstants 那
4   -/**
5   - * 仿钉钉快搭 JSON 常量信息
6   - *
7   - * @author jason
8   - */
9   -public interface SimpleModelConstants {
10   -
11   - // TODO @芋艿:条件表达式的字段名
12   -
13   - /**
14   - * 网关节点默认序列流属性
15   - */
16   - String DEFAULT_FLOW_ATTRIBUTE = "defaultFlow";
17   -
18   - /**
19   - * 条件节点的条件类型属性
20   - */
21   - String CONDITION_TYPE_ATTRIBUTE = "conditionType";
22   -
23   - /**
24   - * 条件节点条件表达式属性
25   - */
26   - String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression";
27   -
28   - /**
29   - * 条件规则的条件组属性
30   - */
31   - String CONDITION_GROUPS_ATTRIBUTE = "conditionGroups";
32   -
33   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java
... ... @@ -15,8 +15,8 @@ import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCo
15 15  
16 16 /**
17 17 * 处理抄送用户的 {@link JavaDelegate} 的实现类
18   - *
19   - * 目前只有快搭模式的【抄送节点】使用
  18 + * <p>
  19 + * 目前只有仿钉钉/飞书模式的【抄送节点】使用
20 20 *
21 21 * @author jason
22 22 */
... ... @@ -34,14 +34,14 @@ public class BpmCopyTaskDelegate implements JavaDelegate {
34 34 @Override
35 35 public void execute(DelegateExecution execution) {
36 36 // 1. 获得抄送人
37   - Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution);
  37 + Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution);
38 38 if (CollUtil.isEmpty(userIds)) {
39 39 return;
40 40 }
41 41 // 2. 执行抄送
42 42 FlowElement currentFlowElement = execution.getCurrentFlowElement();
43   - processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
44   - currentFlowElement.getId(), null, currentFlowElement.getName());
  43 + processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(),
  44 + currentFlowElement.getId(), currentFlowElement.getName(), null);
45 45 }
46 46  
47 47 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
8 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
9 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
10 10 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
11   -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
12 11 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
13 12 import com.google.common.collect.ImmutableSet;
14 13 import jakarta.annotation.Resource;
... ... @@ -44,9 +43,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
44 43 @Resource
45 44 @Lazy // 解决循环依赖
46 45 private BpmTaskService taskService;
47   - @Resource
48   - @Lazy // 解决循环依赖
49   - private BpmActivityService activityService;
50 46  
51 47 public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
52 48 .add(FlowableEngineEventType.TASK_CREATED)
... ... @@ -72,7 +68,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
72 68  
73 69 @Override
74 70 protected void activityCancelled(FlowableActivityCancelledEvent event) {
75   - List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId());
  71 + List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId());
76 72 if (CollUtil.isEmpty(activityList)) {
77 73 log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());
78 74 return;
... ... @@ -87,6 +83,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
87 83 }
88 84  
89 85 @Override
  86 + @SuppressWarnings("PatternVariableCanBeUsed")
90 87 protected void timerFired(FlowableEngineEntityEvent event) {
91 88 // 1.1 只处理 BoundaryEvent 边界计时时间
92 89 String processDefinitionId = event.getProcessDefinitionId();
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -3,27 +3,116 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.map.MapUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.ObjUtil;
6 7 import cn.hutool.core.util.StrUtil;
  8 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 10 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
  11 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 12 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
  13 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
  14 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
  15 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
10 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
11 17 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
  18 +import com.google.common.collect.Maps;
  19 +import lombok.extern.slf4j.Slf4j;
12 20 import org.flowable.bpmn.converter.BpmnXMLConverter;
13 21 import org.flowable.bpmn.model.Process;
14 22 import org.flowable.bpmn.model.*;
  23 +import org.flowable.common.engine.api.FlowableException;
15 24 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
16 25  
17 26 import java.util.*;
18 27  
19 28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
20 29 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
  30 +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
21 31  
22 32 /**
23   - * 流程模型转操作工具类
  33 + * BPMN Model 操作工具类。目前分成三部分:
  34 + *
  35 + * 1. BPMN 修改 + 解析元素相关的方法
  36 + * 2. BPMN 简单查找相关的方法
  37 + * 3. BPMN 复杂遍历相关的方法
  38 + * 4. BPMN 流程预测相关的方法
  39 + *
  40 + * @author 芋道源码
24 41 */
  42 +@Slf4j
25 43 public class BpmnModelUtils {
26 44  
  45 + // ========== BPMN 修改 + 解析元素相关的方法 ==========
  46 +
  47 + public static void addExtensionElement(FlowElement element, String name, String value) {
  48 + if (value == null) {
  49 + return;
  50 + }
  51 + ExtensionElement extensionElement = new ExtensionElement();
  52 + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
  53 + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
  54 + extensionElement.setElementText(value);
  55 + extensionElement.setName(name);
  56 + element.addExtensionElement(extensionElement);
  57 + }
  58 +
  59 + public static void addExtensionElement(FlowElement element, String name, Integer value) {
  60 + if (value == null) {
  61 + return;
  62 + }
  63 + addExtensionElement(element, name, String.valueOf(value));
  64 + }
  65 +
  66 + public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) {
  67 + if (attributes == null) {
  68 + return;
  69 + }
  70 + ExtensionElement extensionElement = new ExtensionElement();
  71 + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
  72 + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
  73 + extensionElement.setName(name);
  74 + attributes.forEach((key, value) -> {
  75 + ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
  76 + extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
  77 + extensionElement.addAttribute(extensionAttribute);
  78 + });
  79 + element.addExtensionElement(extensionElement);
  80 + }
  81 +
  82 + /**
  83 + * 解析扩展元素
  84 + *
  85 + * @param flowElement 节点
  86 + * @param elementName 元素名称
  87 + * @return 扩展元素
  88 + */
  89 + public static String parseExtensionElement(FlowElement flowElement, String elementName) {
  90 + if (flowElement == null) {
  91 + return null;
  92 + }
  93 + ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
  94 + return element != null ? element.getElementText() : null;
  95 + }
  96 +
  97 + /**
  98 + * 给节点添加候选人元素
  99 + *
  100 + * @param candidateStrategy 候选人策略
  101 + * @param candidateParam 候选人参数,允许空
  102 + * @param flowElement 节点
  103 + */
  104 + public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
  105 + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
  106 + candidateStrategy == null ? null : candidateStrategy.toString());
  107 + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
  108 + }
  109 +
  110 + /**
  111 + * 解析候选人策略
  112 + *
  113 + * @param userTask 任务节点
  114 + * @return 候选人策略
  115 + */
27 116 public static Integer parseCandidateStrategy(FlowElement userTask) {
28 117 Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
29 118 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
... ... @@ -35,6 +124,12 @@ public class BpmnModelUtils {
35 124 return candidateStrategy;
36 125 }
37 126  
  127 + /**
  128 + * 解析候选人参数
  129 + *
  130 + * @param userTask 任务节点
  131 + * @return 候选人参数
  132 + */
38 133 public static String parseCandidateParam(FlowElement userTask) {
39 134 String candidateParam = userTask.getAttributeValue(
40 135 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
... ... @@ -45,39 +140,130 @@ public class BpmnModelUtils {
45 140 return candidateParam;
46 141 }
47 142  
  143 + /**
  144 + * 解析审批类型
  145 + *
  146 + * @see BpmUserTaskApproveTypeEnum
  147 + * @param userTask 任务节点
  148 + * @return 审批类型
  149 + */
48 150 public static Integer parseApproveType(FlowElement userTask) {
49 151 return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
50 152 }
51 153  
  154 + /**
  155 + * 添加任务拒绝处理元素
  156 + *
  157 + * @param rejectHandler 任务拒绝处理
  158 + * @param userTask 任务节点
  159 + */
  160 + public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) {
  161 + if (rejectHandler == null) {
  162 + return;
  163 + }
  164 + addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
  165 + addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
  166 + }
  167 +
  168 + /**
  169 + * 解析任务拒绝处理类型
  170 + *
  171 + * @param userTask 任务节点
  172 + * @return 任务拒绝处理类型
  173 + */
52 174 public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
53 175 Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
54 176 return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
55 177 }
56 178  
  179 + /**
  180 + * 解析任务拒绝返回任务节点 ID
  181 + *
  182 + * @param flowElement 任务节点
  183 + * @return 任务拒绝返回任务节点 ID
  184 + */
57 185 public static String parseReturnTaskId(FlowElement flowElement) {
58 186 return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
59 187 }
60 188  
  189 + /**
  190 + * 给节点添加用户任务的审批人与发起人相同时,处理类型枚举
  191 + *
  192 + * @see BpmUserTaskAssignStartUserHandlerTypeEnum
  193 + * @param assignStartUserHandlerType 发起人处理类型
  194 + * @param userTask 任务节点
  195 + */
  196 + public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
  197 + if (assignStartUserHandlerType == null) {
  198 + return;
  199 + }
  200 + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
  201 + }
  202 +
  203 + /**
  204 + * 给节点添加用户任务的审批人为空时,处理类型枚举
  205 + *
  206 + * @see BpmUserTaskAssignEmptyHandlerTypeEnum
  207 + * @param emptyHandler 空处理
  208 + * @param userTask 任务节点
  209 + */
  210 + public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
  211 + if (emptyHandler == null) {
  212 + return;
  213 + }
  214 + addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
  215 + addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
  216 + }
  217 +
  218 + /**
  219 + * 解析用户任务的审批人与发起人相同时,处理类型枚举
  220 + *
  221 + * @param userTask 任务节点
  222 + * @return 处理类型枚举
  223 + */
61 224 public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
62 225 return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
63 226 }
64 227  
  228 + /**
  229 + * 解析用户任务的审批人为空时,处理类型枚举
  230 + *
  231 + * @param userTask 任务节点
  232 + * @return 处理类型枚举
  233 + */
65 234 public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
66 235 return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE));
67 236 }
68 237  
  238 + /**
  239 + * 解析用户任务的审批人为空时,处理用户 ID 数组
  240 + *
  241 + * @param userTask 任务节点
  242 + * @return 处理用户 ID 数组
  243 + */
69 244 public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) {
70 245 return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ",");
71 246 }
72 247  
73   - public static String parseExtensionElement(FlowElement flowElement, String elementName) {
74   - if (flowElement == null) {
75   - return null;
  248 + /**
  249 + * 给节点添加表单字段权限元素
  250 + *
  251 + * @param fieldsPermissions 表单字段权限
  252 + * @param flowElement 节点
  253 + */
  254 + public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
  255 + if (CollUtil.isNotEmpty(fieldsPermissions)) {
  256 + fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
76 257 }
77   - ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
78   - return element != null ? element.getElementText() : null;
79 258 }
80 259  
  260 + /**
  261 + * 解析表单字段权限
  262 + *
  263 + * @param bpmnModel bpmnModel 对象
  264 + * @param flowElementId 元素 ID
  265 + * @return 表单字段权限
  266 + */
81 267 public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) {
82 268 if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) {
83 269 return null;
... ... @@ -101,6 +287,29 @@ public class BpmnModelUtils {
101 287 return fieldsPermission;
102 288 }
103 289  
  290 + /**
  291 + * 给节点添加操作按钮设置元素
  292 + */
  293 + public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) {
  294 + if (CollUtil.isNotEmpty(buttonsSetting)) {
  295 + List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> {
  296 + Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3);
  297 + settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId()));
  298 + settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName());
  299 + settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable()));
  300 + return settingMap;
  301 + });
  302 + list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item));
  303 + }
  304 + }
  305 +
  306 + /**
  307 + * 解析操作按钮设置
  308 + *
  309 + * @param bpmnModel bpmnModel 对象
  310 + * @param flowElementId 元素 ID
  311 + * @return 操作按钮设置
  312 + */
104 313 public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) {
105 314 FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
106 315 if (flowElement == null) {
... ... @@ -110,7 +319,7 @@ public class BpmnModelUtils {
110 319 if (CollUtil.isEmpty(extensionElements)) {
111 320 return null;
112 321 }
113   - Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = MapUtil.newHashMap(16);
  322 + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
114 323 extensionElements.forEach(element -> {
115 324 String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
116 325 String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
... ... @@ -124,6 +333,23 @@ public class BpmnModelUtils {
124 333 }
125 334  
126 335 /**
  336 + * 解析边界事件扩展元素
  337 + *
  338 + * @param boundaryEvent 边界事件
  339 + * @param customElement 元素
  340 + * @return 扩展元素
  341 + */
  342 + public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
  343 + if (boundaryEvent == null) {
  344 + return null;
  345 + }
  346 + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
  347 + return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
  348 + }
  349 +
  350 + // ========== BPM 简单查找相关的方法 ==========
  351 +
  352 + /**
127 353 * 根据节点,获取入口连线
128 354 *
129 355 * @param source 起始节点
... ... @@ -168,15 +394,14 @@ public class BpmnModelUtils {
168 394 * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
169 395 * @return 元素们
170 396 */
  397 + @SuppressWarnings("unchecked")
171 398 public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
172 399 List<T> result = new ArrayList<>();
173   - model.getProcesses().forEach(process -> {
174   - process.getFlowElements().forEach(flowElement -> {
175   - if (flowElement.getClass().isAssignableFrom(clazz)) {
176   - result.add((T) flowElement);
177   - }
178   - });
179   - });
  400 + model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> {
  401 + if (flowElement.getClass().isAssignableFrom(clazz)) {
  402 + result.add((T) flowElement);
  403 + }
  404 + }));
180 405 return result;
181 406 }
182 407  
... ... @@ -193,7 +418,7 @@ public class BpmnModelUtils {
193 418  
194 419 public static EndEvent getEndEvent(BpmnModel model) {
195 420 Process process = model.getMainProcess();
196   - // 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题
  421 + // 从 flowElementList 找 endEvent
197 422 return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
198 423 }
199 424  
... ... @@ -221,7 +446,7 @@ public class BpmnModelUtils {
221 446 return StrUtil.utf8Str(bpmnBytes);
222 447 }
223 448  
224   - // ========== 遍历相关的方法 ==========
  449 + // ========== BPMN 复杂遍历相关的方法 ==========
225 450  
226 451 /**
227 452 * 找到 source 节点之前的所有用户任务节点
... ... @@ -316,16 +541,16 @@ public class BpmnModelUtils {
316 541 return userTaskList;
317 542 }
318 543  
319   -
320 544 /**
321 545 * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
322   - * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
  546 + * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况
323 547 *
324 548 * @param source 起始节点
325 549 * @param target 目标节点
326 550 * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
327 551 * @return 结果
328 552 */
  553 + @SuppressWarnings("BooleanMethodIsAlwaysInverted")
329 554 public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
330 555 visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
331 556 // 不能是开始事件和子流程
... ... @@ -436,11 +661,136 @@ public class BpmnModelUtils {
436 661 return userTaskList;
437 662 }
438 663  
439   - public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
440   - if (boundaryEvent == null) {
441   - return null;
  664 + // ========== BPMN 流程预测相关的方法 ==========
  665 +
  666 + /**
  667 + * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果
  668 + *
  669 + * @param bpmnModel BPMN 图
  670 + * @param variables 变量
  671 + * @return 节点元素数组
  672 + */
  673 + public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) {
  674 + List<FlowElement> resultElements = new ArrayList<>();
  675 + Set<FlowElement> visitElements = new HashSet<>();
  676 +
  677 + // 从 StartEvent 开始遍历
  678 + StartEvent startEvent = getStartEvent(bpmnModel);
  679 + simulateNextFlowElements(startEvent, variables, resultElements, visitElements);
  680 +
  681 + // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中
  682 + List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements,
  683 + flowElement -> flowElement instanceof EndEvent);
  684 + resultElements.addAll(endEvents);
  685 + return resultElements;
  686 + }
  687 +
  688 + @SuppressWarnings("PatternVariableCanBeUsed")
  689 + private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
  690 + List<FlowElement> resultElements, Set<FlowElement> visitElements) {
  691 + // 如果为空,或者已经遍历过,则直接结束
  692 + if (currentElement == null) {
  693 + return;
  694 + }
  695 + if (visitElements.contains(currentElement)) {
  696 + return;
  697 + }
  698 + visitElements.add(currentElement);
  699 +
  700 + // 情况:StartEvent/EndEvent/UserTask/ServiceTask
  701 + if (currentElement instanceof StartEvent
  702 + || currentElement instanceof EndEvent
  703 + || currentElement instanceof UserTask
  704 + || currentElement instanceof ServiceTask) {
  705 + // 添加元素
  706 + FlowNode flowNode = (FlowNode) currentElement;
  707 + resultElements.add(flowNode);
  708 + // 遍历子节点
  709 + flowNode.getOutgoingFlows().forEach(
  710 + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
  711 + return;
  712 + }
  713 +
  714 + // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
  715 + if (currentElement instanceof ExclusiveGateway) {
  716 + // 查找满足条件的 SequenceFlow 路径
  717 + Gateway gateway = (Gateway) currentElement;
  718 + SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  719 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  720 + && evalConditionExpress(variables, flow.getConditionExpression()));
  721 + if (matchSequenceFlow == null) {
  722 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  723 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
  724 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  725 + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
  726 + matchSequenceFlow = gateway.getOutgoingFlows().get(0);
  727 + }
  728 + }
  729 + // 遍历满足条件的 SequenceFlow 路径
  730 + if (matchSequenceFlow != null) {
  731 + simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
  732 + }
  733 + return;
  734 + }
  735 +
  736 + // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
  737 + if (currentElement instanceof InclusiveGateway) {
  738 + // 查找满足条件的 SequenceFlow 路径
  739 + Gateway gateway = (Gateway) currentElement;
  740 + Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  741 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  742 + && evalConditionExpress(variables, flow.getConditionExpression()));
  743 + if (CollUtil.isEmpty(matchSequenceFlows)) {
  744 + matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  745 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
  746 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  747 + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
  748 + matchSequenceFlows = gateway.getOutgoingFlows();
  749 + }
  750 + }
  751 + // 遍历满足条件的 SequenceFlow 路径
  752 + matchSequenceFlows.forEach(
  753 + flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
  754 + }
  755 +
  756 + // 情况:ParallelGateway 并行,都满足,都走
  757 + if (currentElement instanceof ParallelGateway) {
  758 + Gateway gateway = (Gateway) currentElement;
  759 + // 遍历子节点
  760 + gateway.getOutgoingFlows().forEach(
  761 + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
  762 + return;
  763 + }
  764 + }
  765 +
  766 + /**
  767 + * 计算条件表达式是否为 true 满足条件
  768 + *
  769 + * @param variables 流程实例
  770 + * @param express 条件表达式
  771 + * @return 是否满足条件
  772 + */
  773 + public static boolean evalConditionExpress(Map<String, Object> variables, String express) {
  774 + if (express == null) {
  775 + return Boolean.FALSE;
  776 + }
  777 + try {
  778 + Object result = FlowableUtils.getExpressionValue(variables, express);
  779 + return Boolean.TRUE.equals(result);
  780 + } catch (FlowableException ex) {
  781 + log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, variables, ex);
  782 + return Boolean.FALSE;
442 783 }
443   - ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
444   - return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
445 784 }
  785 +
  786 + @SuppressWarnings("PatternVariableCanBeUsed")
  787 + public static boolean isSequentialUserTask(FlowElement flowElement) {
  788 + if (!(flowElement instanceof UserTask)) {
  789 + return false;
  790 + }
  791 + UserTask userTask = (UserTask) flowElement;
  792 + MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();
  793 + return loopCharacteristics != null && loopCharacteristics.isSequential();
  794 + }
  795 +
446 796 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 5 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
5 6 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
6 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
... ... @@ -8,6 +9,8 @@ import org.flowable.common.engine.api.delegate.Expression;
8 9 import org.flowable.common.engine.api.variable.VariableContainer;
9 10 import org.flowable.common.engine.impl.el.ExpressionManager;
10 11 import org.flowable.common.engine.impl.identity.Authentication;
  12 +import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer;
  13 +import org.flowable.engine.ManagementService;
11 14 import org.flowable.engine.ProcessEngineConfiguration;
12 15 import org.flowable.engine.history.HistoricProcessInstance;
13 16 import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
... ... @@ -95,15 +98,35 @@ public class FlowableUtils {
95 98 }
96 99  
97 100 /**
  101 + * 获得流程实例的审批原因
  102 + *
  103 + * @param processInstance 流程实例
  104 + * @return 审批原因
  105 + */
  106 + public static String getProcessInstanceReason(HistoricProcessInstance processInstance) {
  107 + return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);
  108 + }
  109 +
  110 + /**
  111 + * 获得流程实例的表单
  112 + *
  113 + * @param processInstance 流程实例
  114 + * @return 表单
  115 + */
  116 + public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) {
  117 + Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables());
  118 + return filterProcessInstanceFormVariable(processVariables);
  119 + }
  120 +
  121 + /**
98 122 * 获得流程实例的表单
99 123 *
100 124 * @param processInstance 流程实例
101 125 * @return 表单
102 126 */
103 127 public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) {
104   - Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
105   - filterProcessInstanceFormVariable(formVariables);
106   - return formVariables;
  128 + Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables());
  129 + return filterProcessInstanceFormVariable(processVariables);
107 130 }
108 131  
109 132 /**
... ... @@ -125,9 +148,22 @@ public class FlowableUtils {
125 148 * @param processInstance 流程实例
126 149 * @return 发起用户选择的审批人 Map
127 150 */
128   - @SuppressWarnings("unchecked")
129 151 public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) {
130   - return (Map<String, List<Long>>) processInstance.getProcessVariables().get(
  152 + return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null;
  153 + }
  154 +
  155 + /**
  156 + * 获得流程实例的发起用户选择的审批人 Map
  157 + *
  158 + * @param processVariables 流程变量
  159 + * @return 发起用户选择的审批人 Map
  160 + */
  161 + @SuppressWarnings("unchecked")
  162 + public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) {
  163 + if (processVariables == null) {
  164 + return null;
  165 + }
  166 + return (Map<String, List<Long>>) processVariables.get(
131 167 BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
132 168 }
133 169  
... ... @@ -181,13 +217,30 @@ public class FlowableUtils {
181 217  
182 218 // ========== Expression 相关的工具方法 ==========
183 219  
184   - public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
185   - ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
186   - assert processEngineConfiguration != null;
  220 + private static Object getExpressionValue(VariableContainer variableContainer, String expressionString,
  221 + ProcessEngineConfigurationImpl processEngineConfiguration) {
  222 + assert processEngineConfiguration!= null;
187 223 ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
188   - assert expressionManager != null;
  224 + assert expressionManager!= null;
189 225 Expression expression = expressionManager.createExpression(expressionString);
190 226 return expression.getValue(variableContainer);
191 227 }
192 228  
  229 + public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
  230 + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
  231 + if (processEngineConfiguration != null) {
  232 + return getExpressionValue(variableContainer, expressionString, processEngineConfiguration);
  233 + }
  234 + // 如果 ProcessEngineConfigurationImpl 获取不到,则需要通过 ManagementService 来获取
  235 + ManagementService managementService = SpringUtil.getBean(ManagementService.class);
  236 + assert managementService != null;
  237 + return managementService.executeCommand(context ->
  238 + getExpressionValue(variableContainer, expressionString, CommandContextUtil.getProcessEngineConfiguration()));
  239 + }
  240 +
  241 + public static Object getExpressionValue(Map<String, Object> variable, String expressionString) {
  242 + VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty());
  243 + return getExpressionValue(variableContainer, expressionString);
  244 + }
  245 +
193 246 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
... ... @@ -7,64 +7,47 @@ import cn.hutool.core.util.*;
7 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
10   -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler;
11   -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
12   -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
13   -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
14   -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum;
  10 +import cn.iocoder.yudao.module.bpm.enums.definition.*;
  11 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
15 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
16 13 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
17 14 import org.flowable.bpmn.BpmnAutoLayout;
  15 +import org.flowable.bpmn.constants.BpmnXMLConstants;
18 16 import org.flowable.bpmn.model.Process;
19 17 import org.flowable.bpmn.model.*;
20 18  
21   -import java.util.ArrayList;
22   -import java.util.List;
23   -import java.util.Map;
24   -import java.util.Objects;
25   -
26   -import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
27   -import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
28   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
29   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.*;
30   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER;
31   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP;
32   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER;
33   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER;
  19 +import java.util.*;
  20 +
34 21 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
35   -import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
  22 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
  23 +import static java.util.Arrays.asList;
36 24  
37 25 /**
38   - * 仿钉钉快搭模型相关的工具方法
  26 + * 仿钉钉/飞书的模型相关的工具方法
  27 + * <p>
  28 + * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法
  29 + * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类
39 30 *
40 31 * @author jason
41 32 */
42 33 public class SimpleModelUtils {
43 34  
44   - /**
45   - * 聚合网关节点 Id 后缀
46   - */
47   - public static final String JOIN_GATE_WAY_NODE_ID_SUFFIX = "_join";
48   -
49   - /**
50   - * 所有审批人同意的表达式
51   - */
52   - public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }";
53   -
54   - /**
55   - * 任一一名审批人同意的表达式
56   - */
57   - public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
58   -
59   - /**
60   - * 按通过比例完成表达式
61   - */
62   - public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
  35 + private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap();
63 36  
64   - // TODO @yunai:注释需要完善下;
  37 + static {
  38 + List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
  39 + new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(),
  40 + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert());
  41 + converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
  42 + }
65 43  
66 44 /**
67   - * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
  45 + * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model
  46 + * <p>
  47 + * 整体逻辑如下:
  48 + * 1. 创建:BpmnModel、Process 对象
  49 + * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素
  50 + * 3. 连接:构建并添加节点之间的连线 Sequence Flow
68 51 *
69 52 * @param processId 流程标识
70 53 * @param processName 流程名称
... ... @@ -72,42 +55,76 @@ public class SimpleModelUtils {
72 55 * @return Bpmn Model
73 56 */
74 57 public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
  58 + // 1. 创建 BpmnModel
75 59 BpmnModel bpmnModel = new BpmnModel();
76   - // 不加这个 解析 Message 会报 NPE 异常 .
77   - bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace;
78   - // TODO 芋艿:后续在 review
79   -
  60 + bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常
  61 + // 创建 Process 对象
80 62 Process process = new Process();
81 63 process.setId(processId);
82 64 process.setName(processName);
83   - process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么?
  65 + process.setExecutable(Boolean.TRUE);
84 66 bpmnModel.addProcess(process);
85 67  
86   - // TODO 芋艿:这里可能纠结下,到底前端传递,还是后端创建出来。
87   - // 目前前端的第一个节点是“发起人节点”。这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点
88   - BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode();
  68 + // 2.1 创建 StartNode 节点
  69 + // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点
  70 + BpmSimpleModelNodeVO startNode = buildStartNode();
89 71 startNode.setChildNode(simpleModelNode);
90   - // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process
  72 + // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中
91 73 traverseNodeToBuildFlowNode(startNode, process);
92   - // 找到 end event
93   - EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent);
94 74  
95   - // 构建并添加节点之间的连线 Sequence Flow
  75 + // 3. 构建并添加节点之间的连线 Sequence Flow
  76 + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
96 77 traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
97   - // 自动布局
  78 +
  79 + // 4. 自动布局
98 80 new BpmnAutoLayout(bpmnModel).execute();
99 81 return bpmnModel;
100 82 }
101 83  
102   - private static BpmSimpleModelNodeVO buildStartSimpleModelNode() {
103   - BpmSimpleModelNodeVO startNode = new BpmSimpleModelNodeVO();
104   - startNode.setId(START_EVENT_NODE_ID);
105   - startNode.setName(START_EVENT_NODE_NAME);
106   - startNode.setType(START_NODE.getType());
107   - return startNode;
  84 + private static BpmSimpleModelNodeVO buildStartNode() {
  85 + return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
  86 + .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
  87 + .setType(BpmSimpleModelNodeType.START_NODE.getType());
  88 + }
  89 +
  90 + /**
  91 + * 遍历节点,构建 FlowNode 元素
  92 + *
  93 + * @param node SIMPLE 节点
  94 + * @param process BPMN 流程
  95 + */
  96 + private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
  97 + // 1. 判断是否有效节点
  98 + if (!isValidNode(node)) {
  99 + return;
  100 + }
  101 + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
  102 + Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType());
  103 +
  104 + // 2. 处理当前节点
  105 + NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType);
  106 + Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType());
  107 + List<? extends FlowElement> flowElements = nodeConvert.convertList(node);
  108 + flowElements.forEach(process::addFlowElement);
  109 +
  110 + // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点
  111 + if (BpmSimpleModelNodeType.isBranchNode(node.getType())
  112 + && CollUtil.isNotEmpty(node.getConditionNodes())) {
  113 + // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件
  114 + node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
  115 + }
  116 +
  117 + // 3.2 情况二:如果有“子”节点,则递归处理子节点
  118 + traverseNodeToBuildFlowNode(node.getChildNode(), process);
108 119 }
109 120  
110   - // TODO @芋艿:在优化下这个注释
  121 + /**
  122 + * 遍历节点,构建 SequenceFlow 元素
  123 + *
  124 + * @param process Bpmn 流程
  125 + * @param node 当前节点
  126 + * @param targetNodeId 目标节点 ID
  127 + */
111 128 private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
112 129 // 1.1 无效节点返回
113 130 if (!isValidNode(node)) {
... ... @@ -116,516 +133,555 @@ public class SimpleModelUtils {
116 133 // 1.2 END_NODE 直接返回
117 134 BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
118 135 Assert.notNull(nodeType, "模型节点类型不支持");
119   - if (nodeType == END_NODE) {
  136 + if (nodeType == BpmSimpleModelNodeType.END_NODE) {
120 137 return;
121 138 }
  139 +
122 140 // 2.1 情况一:普通节点
123   - BpmSimpleModelNodeVO childNode = node.getChildNode();
124 141 if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) {
125   - if (!isValidNode(childNode)) {
126   - // 2.1.1 普通节点且无孩子节点。分两种情况
127   - // a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线。
128   - if (StrUtil.isNotEmpty(node.getAttachNodeId())) {
129   - // 2.1.1.1 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线
130   - List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), targetNodeId);
131   - sequenceFlows.forEach(process::addFlowElement);
132   - } else {
133   - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null);
134   - process.addFlowElement(sequenceFlow);
135   - }
136   - } else {
137   - // 2.1.2 普通节点且有孩子节点。建立连线
138   - if (StrUtil.isNotEmpty(node.getAttachNodeId())) {
139   - // 2.1.1.2 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线
140   - List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), childNode.getId());
141   - sequenceFlows.forEach(process::addFlowElement);
142   - } else {
143   - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null);
144   - process.addFlowElement(sequenceFlow);
145   - }
146   - // 递归调用后续节点
147   - traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
148   - }
  142 + traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId);
149 143 } else {
150 144 // 2.2 情况二:分支节点
151   - List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
152   - Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
153   - // 分支终点节点 Id
154   - String branchEndNodeId = null;
155   - if (nodeType == CONDITION_BRANCH_NODE) { // 条件分支
156   - // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点Id
157   - branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
158   - } else if (nodeType == PARALLEL_BRANCH_NODE) { // 并行分支
159   - // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。
160   - branchEndNodeId = node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX;
161   - }
162   - // TODO 包容网关待实现
163   - Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
164   - // 3.1 遍历分支节点. 如下情况:
165   - // 分支1、A->B->C->D->E 和 分支2、A->D->E。 A为分支节点, D为A孩子节点
166   - for (BpmSimpleModelNodeVO item : conditionNodes) {
167   - // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件
168   - // @芋艿 这个是啥意思。 这里的 item 的节点类型为 BpmSimpleModelNodeType.CONDITION_NODE 类型,没有对应的 bpmn 的节点。 仅仅用于构建条件表达式。
169   - Assert.isTrue(Objects.equals(item.getType(), CONDITION_NODE.getType()), "条件节点类型不符合");
170   - // 构建表达式,可以为空. 并行分支为空
171   - String conditionExpression = buildConditionExpression(item);
172   - BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
173   - // 3.2 分支有后续节点, 分支1: A->B->C->D
174   - if (isValidNode(nextNodeOnCondition)) {
175   - // 3.2.1 建立 A->B
176   - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(),
177   - item.getId(), item.getName(), conditionExpression);
178   - process.addFlowElement(sequenceFlow);
179   - // 3.2.2 递归调用后续节点连线。 建立 B->C->D 的连线
180   - traverseNodeToBuildSequenceFlow(process, nextNodeOnCondition, branchEndNodeId);
181   - } else {
182   - // 3.3 分支无后续节点 建立 A->D
183   - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId,
184   - item.getId(), item.getName(), conditionExpression);
185   - process.addFlowElement(sequenceFlow);
186   - }
187   - }
188   - // 如果是并行分支。由于是程序创建的聚合网关。需要手工创建聚合网关和下一个节点的连线
189   - if (nodeType == PARALLEL_BRANCH_NODE) {
190   - String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
191   - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId, null, null, null);
192   - process.addFlowElement(sequenceFlow);
193   - }
194   - // 4.递归调用后续节点 继续递归建立 D->E 的连线
195   - traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
  145 + traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId);
196 146 }
197 147 }
198 148  
199 149 /**
200   - * 构建有附加节点的连线
  150 + * 遍历普通(非条件)节点,构建 SequenceFlow 元素
201 151 *
202   - * @param nodeId 当前节点 Id
203   - * @param attachNodeId 附属节点 Id
204   - * @param targetNodeId 目标节点 Id
  152 + * @param process Bpmn 流程
  153 + * @param node 当前节点
  154 + * @param targetNodeId 目标节点 ID
205 155 */
206   - private static List<SequenceFlow> buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) {
207   - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null);
208   - SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null);
209   - return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow);
  156 + private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
  157 + BpmSimpleModelNodeVO childNode = node.getChildNode();
  158 + boolean isChildNodeValid = isValidNode(childNode);
  159 + // 情况一:有“子”节点,则建立连线
  160 + // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点
  161 + String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId;
  162 + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId);
  163 + process.addFlowElement(sequenceFlow);
  164 +
  165 + // 因为有子节点,递归调用后续子节点
  166 + if (isChildNodeValid) {
  167 + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
  168 + }
210 169 }
211 170  
212 171 /**
213   - * 构造条件表达式
  172 + * 遍历条件节点,构建 SequenceFlow 元素
214 173 *
215   - * @param conditionNode 条件节点
  174 + * @param process Bpmn 流程
  175 + * @param node 当前节点
  176 + * @param targetNodeId 目标节点 ID
216 177 */
217   - public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) {
218   - BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionNode.getConditionType());
219   - String conditionExpression = null;
220   - if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
221   - conditionExpression = conditionNode.getConditionExpression();
222   - } else if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
223   - ConditionGroups conditionGroups = conditionNode.getConditionGroups();
224   - if (conditionGroups != null && CollUtil.isNotEmpty(conditionGroups.getConditions())) {
225   - List<String> strConditionGroups = conditionGroups.getConditions().stream().map(item -> {
226   - if (CollUtil.isNotEmpty(item.getRules())) {
227   - Boolean and = item.getAnd();
228   - List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
229   - // 如果非数值类型加引号
230   - String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() : "\"" + rule.getRightSide() + "\"";
231   - return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
232   - });
233   - return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
234   - } else {
235   - return "";
236   - }
237   - }).toList();
238   - conditionExpression = String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
  178 + private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
  179 + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
  180 + BpmSimpleModelNodeVO childNode = node.getChildNode();
  181 + List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
  182 + Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
  183 + // 分支终点节点 ID
  184 + String branchEndNodeId = null;
  185 + if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支
  186 + // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID
  187 + branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
  188 + } else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
  189 + || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支
  190 + // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。
  191 + branchEndNodeId = buildGatewayJoinId(node.getId());
  192 + }
  193 + Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
  194 +
  195 + // 3. 遍历分支节点
  196 + // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点
  197 + for (BpmSimpleModelNodeVO item : conditionNodes) {
  198 + Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()),
  199 + "条件节点类型({})不符合", item.getType());
  200 + BpmSimpleModelNodeVO conditionChildNode = item.getChildNode();
  201 + // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
  202 + if (isValidNode(conditionChildNode)) {
  203 + // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线
  204 + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item);
  205 + process.addFlowElement(sequenceFlow);
  206 + // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
  207 + traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
  208 + } else {
  209 + // 3.2 分支没有后续节点。例如说,建立 A->D 的连线
  210 + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item);
  211 + process.addFlowElement(sequenceFlow);
239 212 }
240 213 }
241   - // TODO 待增加其它类型
242   - return conditionExpression;
  214 +
  215 + // 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线
  216 + if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
  217 + || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) {
  218 + String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
  219 + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId);
  220 + process.addFlowElement(sequenceFlow);
  221 + }
  222 +
  223 + // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线
  224 + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
  225 + }
  226 +
  227 + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) {
  228 + return buildBpmnSequenceFlow(sourceId, targetId, null, null, null);
243 229 }
244 230  
245   - private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) {
  231 + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId,
  232 + String sequenceFlowId, String sequenceFlowName,
  233 + String conditionExpression) {
246 234 Assert.notEmpty(sourceId, "sourceId 不能为空");
247 235 Assert.notEmpty(targetId, "targetId 不能为空");
248   - // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? @芋艿: 貌似不需要,Flowable 会默认生成
249   - // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧
  236 + // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。
  237 + // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。
250 238 SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
251   - if (StrUtil.isNotEmpty(conditionExpression)) {
252   - sequenceFlow.setConditionExpression(conditionExpression);
  239 + if (StrUtil.isNotEmpty(sequenceFlowId)) {
  240 + sequenceFlow.setId(sequenceFlowId);
253 241 }
254   - if (StrUtil.isNotEmpty(seqFlowId)) {
255   - sequenceFlow.setId(seqFlowId);
  242 + if (StrUtil.isNotEmpty(sequenceFlowName)) {
  243 + sequenceFlow.setName(sequenceFlowName);
256 244 }
257   - if (StrUtil.isNotEmpty(seqName)) {
258   - sequenceFlow.setName(seqName);
  245 + if (StrUtil.isNotEmpty(conditionExpression)) {
  246 + sequenceFlow.setConditionExpression(conditionExpression);
259 247 }
260 248 return sequenceFlow;
261 249 }
262 250  
263   - // TODO @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow
264   - private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
265   - // 判断是否有效节点
266   - if (!isValidNode(node)) {
267   - return;
  251 + public static boolean isValidNode(BpmSimpleModelNodeVO node) {
  252 + return node != null && node.getId() != null;
  253 + }
  254 +
  255 + public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
  256 + return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType())
  257 + && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod());
  258 + }
  259 +
  260 + // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ==========
  261 +
  262 + private interface NodeConvert {
  263 +
  264 + default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
  265 + return Collections.singletonList(convert(node));
268 266 }
269   - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
270   - Assert.notNull(nodeType, "模型节点类型不支持");
271 267  
272   - List<FlowElement> flowElements = buildFlowNode(node, nodeType);
273   - flowElements.forEach(process::addFlowElement);
  268 + default FlowElement convert(BpmSimpleModelNodeVO node) {
  269 + throw new UnsupportedOperationException("请实现该方法");
  270 + }
274 271  
275   - // 如果不是网关类型的接口, 并且chileNode为空退出
276   - // 如果是“分支”节点,则递归处理条件
277   - if (BpmSimpleModelNodeType.isBranchNode(node.getType())
278   - && ArrayUtil.isNotEmpty(node.getConditionNodes())) {
279   - node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
  272 + BpmSimpleModelNodeType getType();
  273 +
  274 + }
  275 +
  276 + private static class StartNodeConvert implements NodeConvert {
  277 +
  278 + @Override
  279 + public StartEvent convert(BpmSimpleModelNodeVO node) {
  280 + StartEvent startEvent = new StartEvent();
  281 + startEvent.setId(node.getId());
  282 + startEvent.setName(node.getName());
  283 + return startEvent;
  284 + }
  285 +
  286 + @Override
  287 + public BpmSimpleModelNodeType getType() {
  288 + return BpmSimpleModelNodeType.START_NODE;
280 289 }
281 290  
282   - // 如果有“子”节点,则递归处理子节点
283   - traverseNodeToBuildFlowNode(node.getChildNode(), process);
284 291 }
285 292  
286   - public static boolean isValidNode(BpmSimpleModelNodeVO node) {
287   - return node != null && node.getId() != null;
  293 + private static class EndNodeConvert implements NodeConvert {
  294 +
  295 + @Override
  296 + public EndEvent convert(BpmSimpleModelNodeVO node) {
  297 + EndEvent endEvent = new EndEvent();
  298 + endEvent.setId(node.getId());
  299 + endEvent.setName(node.getName());
  300 + // TODO @芋艿 + jason:要不要加一个终止定义?
  301 + return endEvent;
  302 + }
  303 +
  304 + @Override
  305 + public BpmSimpleModelNodeType getType() {
  306 + return BpmSimpleModelNodeType.END_NODE;
  307 + }
  308 +
288 309 }
289 310  
290   - public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
291   - return APPROVE_NODE.getType().equals(node.getType()) && SEQUENTIAL.getMethod().equals(node.getApproveMethod());
  311 + private static class StartUserNodeConvert implements NodeConvert {
  312 +
  313 + @Override
  314 + public UserTask convert(BpmSimpleModelNodeVO node) {
  315 + UserTask userTask = new UserTask();
  316 + userTask.setId(node.getId());
  317 + userTask.setName(node.getName());
  318 +
  319 + // 人工审批
  320 + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
  321 + // 候选人策略为发起人自己
  322 + addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
  323 + // 添加表单字段权限属性元素
  324 + addFormFieldsPermission(node.getFieldsPermission(), userTask);
  325 + // 添加操作按钮配置属性元素
  326 + addButtonsSetting(node.getButtonsSetting(), userTask);
  327 + // 使用自动通过策略
  328 + // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过;
  329 + addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask);
  330 + return userTask;
  331 + }
  332 +
  333 + @Override
  334 + public BpmSimpleModelNodeType getType() {
  335 + return BpmSimpleModelNodeType.START_USER_NODE;
  336 + }
  337 +
292 338 }
293 339  
294   - private static List<FlowElement> buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) {
295   - List<FlowElement> list = new ArrayList<>();
296   - switch (nodeType) {
297   - case START_NODE: { // 开始节点
298   - StartEvent startEvent = convertStartNode(node);
299   - list.add(startEvent);
300   - break;
301   - }
302   - case END_NODE: { // 结束节点
303   - EndEvent endEvent = convertEndNode(node);
304   - list.add(endEvent);
305   - break;
306   - }
307   - case START_USER_NODE: { // 发起人节点
308   - UserTask userTask = convertStartUserNode(node);
309   - list.add(userTask);
310   - break;
311   - }
312   - case APPROVE_NODE: { // 审批节点
313   - List<FlowElement> flowElements = convertApproveNode(node);
314   - list.addAll(flowElements);
315   - break;
  340 + private static class ApproveNodeConvert implements NodeConvert {
  341 +
  342 + @Override
  343 + public List<FlowElement> convertList(BpmSimpleModelNodeVO node) {
  344 + List<FlowElement> flowElements = new ArrayList<>(2);
  345 + // 1. 构建用户任务
  346 + UserTask userTask = buildBpmnUserTask(node);
  347 + flowElements.add(userTask);
  348 +
  349 + // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
  350 + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
  351 + BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
  352 + flowElements.add(boundaryEvent);
316 353 }
317   - case COPY_NODE: { // 抄送节点
318   - ServiceTask serviceTask = convertCopyNode(node);
319   - list.add(serviceTask);
320   - break;
  354 + return flowElements;
  355 + }
  356 +
  357 + @Override
  358 + public BpmSimpleModelNodeType getType() {
  359 + return BpmSimpleModelNodeType.APPROVE_NODE;
  360 + }
  361 +
  362 + /**
  363 + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
  364 + *
  365 + * @param userTask 审批任务
  366 + * @param timeoutHandler 超时处理器
  367 + * @return BoundaryEvent 超时事件
  368 + */
  369 + private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
  370 + BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) {
  371 + // 1.1 定时器边界事件
  372 + BoundaryEvent boundaryEvent = new BoundaryEvent();
  373 + boundaryEvent.setId("Event-" + IdUtil.fastUUID());
  374 + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
  375 + boundaryEvent.setAttachedToRef(userTask);
  376 + // 1.2 定义超时时间、最大提醒次数
  377 + TimerEventDefinition eventDefinition = new TimerEventDefinition();
  378 + eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
  379 + if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
  380 + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
  381 + eventDefinition.setTimeCycle(String.format("R%d/%s",
  382 + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
321 383 }
322   - case CONDITION_BRANCH_NODE: {
323   - ExclusiveGateway exclusiveGateway = convertConditionBranchNode(node);
324   - list.add(exclusiveGateway);
325   - break;
  384 + boundaryEvent.addEventDefinition(eventDefinition);
  385 +
  386 + // 2.1 添加定时器边界事件类型
  387 + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType());
  388 + // 2.2 添加超时执行动作元素
  389 + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
  390 + return boundaryEvent;
  391 + }
  392 +
  393 + private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
  394 + UserTask userTask = new UserTask();
  395 + userTask.setId(node.getId());
  396 + userTask.setName(node.getName());
  397 +
  398 + // 如果不是审批人节点,则直接返回
  399 + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType());
  400 + if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) {
  401 + return userTask;
326 402 }
327   - case PARALLEL_BRANCH_NODE: {
328   - List<ParallelGateway> parallelGateways = convertParallelBranchNode(node);
329   - list.addAll(parallelGateways);
330   - break;
  403 +
  404 + // 添加候选人元素
  405 + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
  406 + // 添加表单字段权限属性元素
  407 + addFormFieldsPermission(node.getFieldsPermission(), userTask);
  408 + // 添加操作按钮配置属性元素
  409 + addButtonsSetting(node.getButtonsSetting(), userTask);
  410 + // 处理多实例(审批方式)
  411 + processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
  412 + // 添加任务被拒绝的处理元素
  413 + addTaskRejectElements(node.getRejectHandler(), userTask);
  414 + // 添加用户任务的审批人与发起人相同时的处理元素
  415 + addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
  416 + // 添加用户任务的空处理元素
  417 + addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
  418 + // 设置审批任务的截止时间
  419 + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
  420 + userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
331 421 }
  422 + return userTask;
  423 + }
332 424  
333   - case INCLUSIVE_BRANCH_NODE: {
334   - // TODO jason 待实现
335   - break;
  425 + private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
  426 + BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
  427 + Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
  428 + // 添加审批方式的扩展属性
  429 + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod);
  430 + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
  431 + // 随机审批,不需要设置多实例属性
  432 + return;
336 433 }
337   - default: {
338   - // TODO 其它节点类型的实现
  434 +
  435 + // 处理多实例审批方式
  436 + MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
  437 + // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错
  438 + multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
  439 + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
  440 + multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
  441 + multiInstanceCharacteristics.setSequential(false);
  442 + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) {
  443 + multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
  444 + multiInstanceCharacteristics.setSequential(true);
  445 + multiInstanceCharacteristics.setLoopCardinality("1");
  446 + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) {
  447 + Assert.notNull(approveRatio, "通过比例不能为空");
  448 + multiInstanceCharacteristics.setCompletionCondition(
  449 + String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D)));
  450 + multiInstanceCharacteristics.setSequential(false);
339 451 }
  452 + userTask.setLoopCharacteristics(multiInstanceCharacteristics);
340 453 }
341   - return list;
342   - }
343 454  
344   - private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) {
345   - return buildBpmnStartUserTask(node);
346 455 }
347 456  
348   - private static List<FlowElement> convertApproveNode(BpmSimpleModelNodeVO node) {
349   - List<FlowElement> flowElements = new ArrayList<>();
350   - UserTask userTask = buildBpmnUserTask(node);
351   - flowElements.add(userTask);
352   -
353   - // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
354   - if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
355   - BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
356   - flowElements.add(boundaryEvent);
  457 + private static class CopyNodeConvert implements NodeConvert {
  458 +
  459 + @Override
  460 + public ServiceTask convert(BpmSimpleModelNodeVO node) {
  461 + ServiceTask serviceTask = new ServiceTask();
  462 + serviceTask.setId(node.getId());
  463 + serviceTask.setName(node.getName());
  464 + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
  465 + serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}");
  466 +
  467 + // 添加抄送候选人元素
  468 + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask);
  469 + // 添加表单字段权限属性元素
  470 + addFormFieldsPermission(node.getFieldsPermission(), serviceTask);
  471 + return serviceTask;
357 472 }
358   - return flowElements;
359   - }
360 473  
361   - /**
362   - * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
363   - *
364   - * @param userTask 审批任务
365   - * @param timeoutHandler 超时处理器
366   - * @return BoundaryEvent 超时事件
367   - */
368   - private static BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
369   - // 1.1 定时器边界事件
370   - BoundaryEvent boundaryEvent = new BoundaryEvent();
371   - boundaryEvent.setId("Event-" + IdUtil.fastUUID());
372   - boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
373   - boundaryEvent.setAttachedToRef(userTask);
374   - // 1.2 定义超时时间、最大提醒次数
375   - TimerEventDefinition eventDefinition = new TimerEventDefinition();
376   - eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
377   - if (Objects.equals(REMINDER.getType(), timeoutHandler.getType()) &&
378   - timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
379   - eventDefinition.setTimeCycle(String.format("R%d/%s",
380   - timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
381   - }
382   - boundaryEvent.addEventDefinition(eventDefinition);
383   -
384   - // 2.1 添加定时器边界事件类型
385   - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString());
386   - // 2.2 添加超时执行动作元素
387   - addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, StrUtil.toStringOrNull(timeoutHandler.getType()));
388   - return boundaryEvent;
389   - }
390   -
391   - private static List<ParallelGateway> convertParallelBranchNode(BpmSimpleModelNodeVO node) {
392   - ParallelGateway parallelGateway = new ParallelGateway();
393   - parallelGateway.setId(node.getId());
394   - // TODO @jason:setName
  474 + @Override
  475 + public BpmSimpleModelNodeType getType() {
  476 + return BpmSimpleModelNodeType.COPY_NODE;
  477 + }
395 478  
396   - // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论
397   - // 并行聚合网关有程序创建。前端不需要传入
398   - ParallelGateway joinParallelGateway = new ParallelGateway();
399   - joinParallelGateway.setId(node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX);
400   - return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
401 479 }
402 480  
403   - private static ServiceTask convertCopyNode(BpmSimpleModelNodeVO node) {
404   - ServiceTask serviceTask = new ServiceTask();
405   - serviceTask.setId(node.getId());
406   - serviceTask.setName(node.getName());
407   - serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
408   - serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}");
409   -
410   - // 添加抄送候选人元素
411   - addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask);
412   - // 添加表单字段权限属性元素
413   - addFormFieldsPermission(node.getFieldsPermission(), serviceTask);
414   - return serviceTask;
415   - }
  481 + private static class ConditionBranchNodeConvert implements NodeConvert {
416 482  
417   - /**
418   - * 给节点添加候选人元素
419   - */
420   - private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
421   - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
422   - candidateStrategy == null ? null : candidateStrategy.toString());
423   - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
424   - }
  483 + @Override
  484 + public ExclusiveGateway convert(BpmSimpleModelNodeVO node) {
  485 + ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
  486 + exclusiveGateway.setId(node.getId());
  487 + // TODO @jason:setName
425 488  
426   - private static ExclusiveGateway convertConditionBranchNode(BpmSimpleModelNodeVO node) {
427   - Assert.notEmpty(node.getConditionNodes(), "条件分支节点不能为空");
428   - ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
429   - exclusiveGateway.setId(node.getId());
430   - // 寻找默认的序列流
431   - BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
432   - item -> BooleanUtil.isTrue(item.getDefaultFlow()));
433   - if (defaultSeqFlow != null) {
  489 + // 设置默认的序列流(条件)
  490 + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
  491 + item -> BooleanUtil.isTrue(item.getDefaultFlow()));
  492 + Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId());
434 493 exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
  494 + return exclusiveGateway;
435 495 }
436   - return exclusiveGateway;
437   - }
438 496  
439   - private static InclusiveGateway convertInclusiveBranchNode(BpmSimpleModelNodeVO node, Boolean isFork) {
440   - InclusiveGateway inclusiveGateway = new InclusiveGateway();
441   - inclusiveGateway.setId(node.getId());
442   - // TODO @jason:这里是不是 setName 哈;
443   -
444   - // TODO @芋艿 + jason:是不是搞个合并网关;这里微信讨论下,有点奇怪;
445   - // @芋艿 isFork 为 false 就是合并网关。由前端传入。这个前端暂时还未实现
446   - if (isFork) {
447   - Assert.notEmpty(node.getConditionNodes(), "条件节点不能为空");
448   - // 寻找默认的序列流
449   - BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(
450   - node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getDefaultFlow()));
451   - if (defaultSeqFlow != null) {
452   - inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
453   - }
  497 + @Override
  498 + public BpmSimpleModelNodeType getType() {
  499 + return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE;
454 500 }
455   - return inclusiveGateway;
  501 +
456 502 }
457 503  
458   - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
459   - UserTask userTask = new UserTask();
460   - userTask.setId(node.getId());
461   - userTask.setName(node.getName());
  504 + private static class ParallelBranchNodeConvert implements NodeConvert {
462 505  
463   - // 如果不是审批人节点,则直接返回
464   - addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType()));
465   - if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) {
466   - return userTask;
  506 + @Override
  507 + public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) {
  508 + ParallelGateway parallelGateway = new ParallelGateway();
  509 + parallelGateway.setId(node.getId());
  510 + // TODO @jason:setName
  511 +
  512 + // 并行聚合网关由程序创建,前端不需要传入
  513 + ParallelGateway joinParallelGateway = new ParallelGateway();
  514 + joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
  515 + // TODO @jason:setName
  516 + return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
  517 + }
  518 +
  519 + @Override
  520 + public BpmSimpleModelNodeType getType() {
  521 + return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE;
467 522 }
468 523  
469   - // 添加候选人元素
470   - addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
471   - // 添加表单字段权限属性元素
472   - addFormFieldsPermission(node.getFieldsPermission(), userTask);
473   - // 添加操作按钮配置属性元素
474   - addButtonsSetting(node.getButtonsSetting(), userTask);
475   - // 处理多实例(审批方式)
476   - processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
477   - // 添加任务被拒绝的处理元素
478   - addTaskRejectElements(node.getRejectHandler(), userTask);
479   - // 添加用户任务的审批人与发起人相同时的处理元素
480   - addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
481   - // 添加用户任务的空处理元素
482   - addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
483   - // 设置审批任务的截止时间
484   - if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
485   - userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
486   - }
487   - return userTask;
488 524 }
489 525  
490   - private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) {
491   - if (rejectHandler == null) {
492   - return;
  526 + private static class InclusiveBranchNodeConvert implements NodeConvert {
  527 +
  528 + @Override
  529 + public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) {
  530 + InclusiveGateway inclusiveGateway = new InclusiveGateway();
  531 + inclusiveGateway.setId(node.getId());
  532 + // 设置默认的序列流(条件)
  533 + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
  534 + item -> BooleanUtil.isTrue(item.getDefaultFlow()));
  535 + Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId());
  536 + inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
  537 + // TODO @jason:setName
  538 +
  539 + // 并行聚合网关由程序创建,前端不需要传入
  540 + InclusiveGateway joinInclusiveGateway = new InclusiveGateway();
  541 + joinInclusiveGateway.setId(buildGatewayJoinId(node.getId()));
  542 + // TODO @jason:setName
  543 + return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway);
493 544 }
494   - addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
495   - addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
496   - }
497 545  
498   - private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
499   - if (assignStartUserHandlerType == null) {
500   - return;
  546 + @Override
  547 + public BpmSimpleModelNodeType getType() {
  548 + return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE;
501 549 }
502   - addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
  550 +
503 551 }
504 552  
505   - private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
506   - if (emptyHandler == null) {
507   - return;
  553 + public static class ConditionNodeConvert implements NodeConvert {
  554 +
  555 + @Override
  556 + public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
  557 + // 原因是:正常情况下,它不会被调用到
  558 + throw new UnsupportedOperationException("条件节点不支持转换");
508 559 }
509   - addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
510   - addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
511   - }
512 560  
513   - private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
514   - BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
515   - // TODO @jason:这种枚举,最终不要去掉哈 BpmUserTaskApproveMethodEnum。因为容易不经意重叠
516   - if (approveMethodEnum == null || approveMethodEnum == RANDOM) {
517   - return;
  561 + @Override
  562 + public BpmSimpleModelNodeType getType() {
  563 + return BpmSimpleModelNodeType.CONDITION_NODE;
518 564 }
519   - // 添加审批方式的扩展属性
520   - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD,
521   - approveMethod == null ? null : approveMethod.toString());
522   - MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
523   - // 设置 collectionVariable。本系统用不到。仅仅为了 Flowable 校验不报错。
524   - multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
525   - if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
526   - multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION);
527   - multiInstanceCharacteristics.setSequential(false);
528   - userTask.setLoopCharacteristics(multiInstanceCharacteristics);
529   - } else if (approveMethodEnum == SEQUENTIAL) {
530   - multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION);
531   - multiInstanceCharacteristics.setSequential(true);
532   - multiInstanceCharacteristics.setLoopCardinality("1");
533   - userTask.setLoopCharacteristics(multiInstanceCharacteristics);
534   - } else if (approveMethodEnum == RATIO) {
535   - Assert.notNull(approveRatio, "通过比例不能为空");
536   - multiInstanceCharacteristics.setCompletionCondition(
537   - String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100)));
538   - multiInstanceCharacteristics.setSequential(false);
  565 +
  566 + public static SequenceFlow buildSequenceFlow(String sourceId, String targetId,
  567 + BpmSimpleModelNodeVO node) {
  568 + String conditionExpression = buildConditionExpression(node);
  569 + return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
539 570 }
540   - userTask.setLoopCharacteristics(multiInstanceCharacteristics);
541   - }
542 571  
543   - /**
544   - * 给节点添加操作按钮设置元素
545   - */
546   - private static void addButtonsSetting(List<OperationButtonSetting> buttonsSetting, UserTask userTask) {
547   - if (CollUtil.isNotEmpty(buttonsSetting)) {
548   - List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> {
549   - Map<String, String> settingMap = MapUtil.newHashMap(16);
550   - settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId()));
551   - settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName());
552   - settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable()));
553   - return settingMap;
554   - });
555   - list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item));
  572 + /**
  573 + * 构造条件表达式
  574 + *
  575 + * @param node 条件节点
  576 + */
  577 + public static String buildConditionExpression(BpmSimpleModelNodeVO node) {
  578 + BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType());
  579 + if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
  580 + return node.getConditionExpression();
  581 + }
  582 + if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
  583 + ConditionGroups conditionGroups = node.getConditionGroups();
  584 + if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
  585 + return null;
  586 + }
  587 + List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> {
  588 + if (CollUtil.isEmpty(item.getRules())) {
  589 + return "";
  590 + }
  591 + // 构造规则表达式
  592 + List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
  593 + String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
  594 + : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
  595 + return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
  596 + });
  597 + // 构造条件组的表达式
  598 + Boolean and = item.getAnd();
  599 + return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
  600 + });
  601 + return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
  602 + }
  603 + return null;
556 604 }
  605 +
557 606 }
558 607  
559   - /**
560   - * 给节点添加表单字段权限元素
561   - */
562   - private static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
563   - if (CollUtil.isNotEmpty(fieldsPermissions)) {
564   - fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
565   - }
  608 + private static String buildGatewayJoinId(String id) {
  609 + return id + "_join";
566 610 }
567 611  
568   - private static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) {
569   - if (attributes == null) {
570   - return;
571   - }
572   - ExtensionElement extensionElement = new ExtensionElement();
573   - extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
574   - extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
575   - extensionElement.setName(name);
576   - attributes.forEach((key, value) -> {
577   - ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
578   - extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
579   - extensionElement.addAttribute(extensionAttribute);
580   - });
581   - element.addExtensionElement(extensionElement);
  612 + // ========== SIMPLE 流程预测相关的方法 ==========
  613 +
  614 + public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
  615 + List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>();
  616 +
  617 + // 从头开始遍历
  618 + simulateNextNode(rootNode, variables, resultNodes);
  619 + return resultNodes;
582 620 }
583 621  
584   - private static void addExtensionElement(FlowElement element, String name, String value) {
585   - if (value == null) {
  622 + private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables,
  623 + List<BpmSimpleModelNodeVO> resultNodes) {
  624 + // 如果不合法(包括为空),则直接结束
  625 + if (!isValidNode(currentNode)) {
586 626 return;
587 627 }
588   - ExtensionElement extensionElement = new ExtensionElement();
589   - extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
590   - extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
591   - extensionElement.setElementText(value);
592   - extensionElement.setName(name);
593   - element.addExtensionElement(extensionElement);
594   - }
  628 + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType());
  629 + Assert.notNull(nodeType, "模型节点类型不支持");
595 630  
596   - // ========== 各种 build 节点的方法 ==========
  631 + // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE
  632 + if (nodeType == BpmSimpleModelNodeType.START_NODE
  633 + || nodeType == BpmSimpleModelNodeType.START_USER_NODE
  634 + || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
  635 + || nodeType == BpmSimpleModelNodeType.COPY_NODE
  636 + || nodeType == BpmSimpleModelNodeType.END_NODE) {
  637 + // 添加元素
  638 + resultNodes.add(currentNode);
  639 + }
597 640  
598   - private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) {
599   - StartEvent startEvent = new StartEvent();
600   - startEvent.setId(node.getId());
601   - startEvent.setName(node.getName());
602   - return startEvent;
603   - }
  641 + // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的
  642 + if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) {
  643 + // 查找满足条件的 BpmSimpleModelNodeVO 节点
  644 + BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
  645 + conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
  646 + && evalConditionExpress(variables, conditionNode));
  647 + if (matchConditionNode == null) {
  648 + matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
  649 + conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
  650 + }
  651 + Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode);
  652 + // 遍历满足条件的 BpmSimpleModelNodeVO 节点
  653 + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes);
  654 + }
604 655  
605   - private static UserTask buildBpmnStartUserTask(BpmSimpleModelNodeVO node) {
606   - UserTask userTask = new UserTask();
607   - userTask.setId(node.getId());
608   - userTask.setName(node.getName());
609   - // 人工审批
610   - addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString());
611   - // 候选人策略为发起人自己
612   - addCandidateElements(START_USER.getStrategy(), null, userTask);
613   - // 添加表单字段权限属性元素
614   - addFormFieldsPermission(node.getFieldsPermission(), userTask);
615   - // 添加操作按钮配置属性元素
616   - addButtonsSetting(node.getButtonsSetting(), userTask);
617   - // 使用自动通过策略 TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过;
618   - addAssignStartUserHandlerType(SKIP.getType(), userTask);
619   - return userTask;
620   - }
  656 + // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的
  657 + if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) {
  658 + // 查找满足条件的 BpmSimpleModelNodeVO 节点
  659 + Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
  660 + conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
  661 + && evalConditionExpress(variables, conditionNode));
  662 + if (CollUtil.isEmpty(matchConditionNodes)) {
  663 + matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
  664 + conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
  665 + }
  666 + Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode);
  667 + // 遍历满足条件的 BpmSimpleModelNodeVO 节点
  668 + matchConditionNodes.forEach(matchConditionNode ->
  669 + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
  670 + }
621 671  
622   - private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) {
623   - EndEvent endEvent = new EndEvent();
624   - endEvent.setId(node.getId());
625   - endEvent.setName(node.getName());
  672 + // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走
  673 + if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) {
  674 + // 遍历所有 BpmSimpleModelNodeVO 节点
  675 + currentNode.getConditionNodes().forEach(matchConditionNode ->
  676 + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
  677 + }
  678 +
  679 + // 遍历子节点
  680 + simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
  681 + }
626 682  
627   - // TODO @芋艿 + jason:要不要加一个终止定义?
628   - return endEvent;
  683 + public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) {
  684 + return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode));
629 685 }
630 686  
631 687 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryService.java
... ... @@ -82,4 +82,11 @@ public interface BpmCategoryService {
82 82 */
83 83 List<BpmCategoryDO> getCategoryListByStatus(Integer status);
84 84  
  85 + /**
  86 + * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增
  87 + *
  88 + * @param ids 分类编号列表
  89 + */
  90 + void updateCategorySortBatch(List<Long> ids);
  91 +
85 92 }
86 93 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryServiceImpl.java
... ... @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.bpm.service.definition;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 7 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
6 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO;
7   -import org.springframework.stereotype.Service;
8   -import jakarta.annotation.Resource;
9   -import org.springframework.validation.annotation.Validated;
10   -
11 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
12   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
13   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
14   -
15 10 import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
  11 +import jakarta.annotation.Resource;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.transaction.annotation.Transactional;
  14 +import org.springframework.validation.annotation.Validated;
16 15  
17 16 import java.util.Collection;
18 17 import java.util.Collections;
19 18 import java.util.List;
  19 +import java.util.stream.Collectors;
  20 +import java.util.stream.IntStream;
20 21  
21 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
22 23 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
... ... @@ -58,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
58 59 private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) {
59 60 BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName());
60 61 if (category == null
61   - || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
  62 + || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
62 63 return;
63 64 }
64 65 throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName());
... ... @@ -67,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
67 68 private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) {
68 69 BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode());
69 70 if (category == null
70   - || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
  71 + || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
71 72 return;
72 73 }
73 74 throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode());
... ... @@ -110,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
110 111 return bpmCategoryMapper.selectListByStatus(status);
111 112 }
112 113  
  114 + @Override
  115 + @Transactional(rollbackFor = Exception.class)
  116 + public void updateCategorySortBatch(List<Long> ids) {
  117 + // 校验分类都存在
  118 + List<BpmCategoryDO> categories = bpmCategoryMapper.selectByIds(ids);
  119 + if (categories.size() != ids.size()) {
  120 + throw exception(CATEGORY_NOT_EXISTS);
  121 + }
  122 +
  123 + // 批量更新排序
  124 + List<BpmCategoryDO> updateList = IntStream.range(0, ids.size())
  125 + .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index))
  126 + .collect(Collectors.toList());
  127 + bpmCategoryMapper.updateBatch(updateList);
  128 + }
  129 +
113 130 }
114 131 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
1 1 package cn.iocoder.yudao.module.bpm.service.definition;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO;
5 3 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
6 4 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
7 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
... ... @@ -9,20 +7,22 @@ import jakarta.validation.Valid;
9 7 import org.flowable.bpmn.model.BpmnModel;
10 8 import org.flowable.engine.repository.Model;
11 9  
  10 +import java.util.List;
  11 +
12 12 /**
13   - * Flowable流程模型接口
  13 + * 流程模型接口
14 14 *
15 15 * @author yunlongn
16 16 */
17 17 public interface BpmModelService {
18 18  
19 19 /**
20   - * 获得流程模型分页
  20 + * 获得流程模型列表
21 21 *
22   - * @param pageVO 分页查询
23   - * @return 流程模型分页
  22 + * @param name 模型名称
  23 + * @return 流程模型列表
24 24 */
25   - PageResult<Model> getModelPage(BpmModelPageReqVO pageVO);
  25 + List<Model> getModelList(String name);
26 26  
27 27 /**
28 28 * 创建流程模型
... ... @@ -65,6 +65,14 @@ public interface BpmModelService {
65 65 void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO);
66 66  
67 67 /**
  68 + * 批量更新模型排序
  69 + *
  70 + * @param userId 用户编号
  71 + * @param ids 编号列表
  72 + */
  73 + void updateModelSortBatch(Long userId, List<String> ids);
  74 +
  75 + /**
68 76 * 将流程模型,部署成一个流程定义
69 77 *
70 78 * @param userId 用户编号
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -3,12 +3,9 @@ package cn.iocoder.yudao.module.bpm.service.definition;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ArrayUtil;
5 5 import cn.hutool.core.util.StrUtil;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8   -import cn.iocoder.yudao.framework.common.util.object.PageUtils;
9 7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
10 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
11   -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO;
12 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
13 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
14 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
... ... @@ -35,14 +32,15 @@ import org.springframework.transaction.annotation.Transactional;
35 32 import org.springframework.validation.annotation.Validated;
36 33  
37 34 import java.util.List;
  35 +import java.util.Map;
38 36 import java.util.Objects;
39 37  
40 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
41 40 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
42 41  
43 42 /**
44   - * Flowable流程模型实现
45   - * 主要进行 Flowable {@link Model} 的维护
  43 + * 流程模型实现:主要进行 Flowable {@link Model} 的维护
46 44 *
47 45 * @author yunlongn
48 46 * @author 芋道源码
... ... @@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService {
64 62 private BpmTaskCandidateInvoker taskCandidateInvoker;
65 63  
66 64 @Override
67   - public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) {
  65 + public List<Model> getModelList(String name) {
68 66 ModelQuery modelQuery = repositoryService.createModelQuery();
69   - modelQuery.modelTenantId(FlowableUtils.getTenantId());
70   - if (StrUtil.isNotBlank(pageVO.getKey())) {
71   - modelQuery.modelKey(pageVO.getKey());
  67 + if (StrUtil.isNotEmpty(name)) {
  68 + modelQuery.modelNameLike(name);
72 69 }
73   - if (StrUtil.isNotBlank(pageVO.getName())) {
74   - modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配
75   - }
76   - if (StrUtil.isNotBlank(pageVO.getCategory())) {
77   - modelQuery.modelCategory(pageVO.getCategory());
78   - }
79   - // 执行查询
80   - long count = modelQuery.count();
81   - if (count == 0) {
82   - return PageResult.empty(count);
83   - }
84   - List<Model> models = modelQuery
85   - .orderByCreateTime().desc()
86   - .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
87   - return new PageResult<>(models, count);
  70 + return modelQuery.list();
88 71 }
89 72  
90 73 @Override
... ... @@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService {
100 83 }
101 84  
102 85 // 2.1 创建流程定义
  86 + createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序
103 87 Model model = repositoryService.newModel();
104 88 BpmModelConvert.INSTANCE.copyToModel(model, createReqVO);
105 89 model.setTenantId(FlowableUtils.getTenantId());
... ... @@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService {
120 104 repositoryService.saveModel(model);
121 105 }
122 106  
  107 + @Override
  108 + @Transactional(rollbackFor = Exception.class)
  109 + public void updateModelSortBatch(Long userId, List<String> ids) {
  110 + // 1.1 校验流程模型存在
  111 + List<Model> models = repositoryService.createModelQuery()
  112 + .modelTenantId(FlowableUtils.getTenantId()).list();
  113 + models.removeIf(model ->!ids.contains(model.getId()));
  114 + if (ids.size() != models.size()) {
  115 + throw exception(MODEL_NOT_EXISTS);
  116 + }
  117 + Map<String, Model> modelMap = convertMap(models, Model::getId);
  118 + // 1.2 校验是否为管理员
  119 + ids.forEach(id -> validateModelManager(id, userId));
  120 +
  121 + // 保存排序
  122 + long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序
  123 + for (int i = ids.size() - 1; i > 0; i--) {
  124 + Model model = modelMap.get(ids.get(i));
  125 + // 更新模型
  126 + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort);
  127 + model.setMetaInfo(JsonUtils.toJsonString(metaInfo));
  128 + repositoryService.saveModel(model);
  129 + // 更新排序
  130 + processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort);
  131 + sort--;
  132 + }
  133 + }
  134 +
123 135 private Model validateModelExists(String id) {
124 136 Model model = repositoryService.getModel(id);
125 137 if (model == null) {
... ... @@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService {
139 151 Model model = validateModelExists(id);
140 152 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
141 153 if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) {
142   - throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER);
  154 + throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName());
143 155 }
144 156 return model;
145 157 }
... ... @@ -158,10 +170,10 @@ public class BpmModelServiceImpl implements BpmModelService {
158 170 // 1.4 校验任务分配规则已配置
159 171 taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
160 172 // 1.5 获取仿钉钉流程设计器模型数据
161   - byte[] simpleBytes = getModelSimpleJson(model.getId());
  173 + String simpleJson = getModelSimpleJson(model.getId());
162 174  
163 175 // 2.1 创建流程定义
164   - String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleBytes, form);
  176 + String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form);
165 177  
166 178 // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
167 179 updateProcessDefinitionSuspended(model.getDeploymentId());
... ... @@ -226,8 +238,8 @@ public class BpmModelServiceImpl implements BpmModelService {
226 238 public BpmSimpleModelNodeVO getSimpleModel(String modelId) {
227 239 Model model = validateModelExists(modelId);
228 240 // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据
229   - byte[] jsonBytes = getModelSimpleJson(model.getId());
230   - return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class);
  241 + String json = getModelSimpleJson(model.getId());
  242 + return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class);
231 243 }
232 244  
233 245 @Override
... ... @@ -240,7 +252,7 @@ public class BpmModelServiceImpl implements BpmModelService {
240 252 // 2.2 保存 Bpmn XML
241 253 updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
242 254 // 2.3 保存 JSON 数据
243   - saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel()));
  255 + updateModelSimpleJson(model.getId(), reqVO.getSimpleModel());
244 256 }
245 257  
246 258 /**
... ... @@ -279,15 +291,21 @@ public class BpmModelServiceImpl implements BpmModelService {
279 291 repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));
280 292 }
281 293  
282   - private byte[] getModelSimpleJson(String id) {
283   - return repositoryService.getModelEditorSourceExtra(id);
  294 + @SuppressWarnings("JavaExistingMethodCanBeUsed")
  295 + private String getModelSimpleJson(String id) {
  296 + byte[] bytes = repositoryService.getModelEditorSourceExtra(id);
  297 + if (ArrayUtil.isEmpty(bytes)) {
  298 + return null;
  299 + }
  300 + return StrUtil.utf8Str(bytes);
284 301 }
285 302  
286   - private void saveModelSimpleJson(String id, byte[] jsonBytes) {
287   - if (ArrayUtil.isEmpty(jsonBytes)) {
  303 + private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) {
  304 + if (node == null) {
288 305 return;
289 306 }
290   - repositoryService.addModelEditorSourceExtra(id, jsonBytes);
  307 + byte[] bytes = JsonUtils.toJsonByte(node);
  308 + repositoryService.addModelEditorSourceExtra(id, bytes);
291 309 }
292 310  
293 311 /**
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
... ... @@ -18,7 +18,7 @@ import java.util.Set;
18 18 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
19 19  
20 20 /**
21   - * Flowable流程定义接口
  21 + * 流程定义接口
22 22 *
23 23 * @author yunlong.li
24 24 * @author ZJQ
... ... @@ -48,12 +48,12 @@ public interface BpmProcessDefinitionService {
48 48 * @param model 流程模型
49 49 * @param modelMetaInfo 流程模型元信息
50 50 * @param bpmnBytes BPMN XML 字节数组
51   - * @param simpleBytes SIMPLE Model JSON 字节数组
  51 + * @param simpleJson SIMPLE Model JSON
52 52 * @param form 表单
53 53 * @return 流程编号
54 54 */
55 55 String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
56   - byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form);
  56 + byte[] bpmnBytes, String simpleJson, BpmFormDO form);
57 57  
58 58 /**
59 59 * 更新流程定义状态
... ... @@ -64,6 +64,14 @@ public interface BpmProcessDefinitionService {
64 64 void updateProcessDefinitionState(String id, Integer state);
65 65  
66 66 /**
  67 + * 更新模型编号
  68 + *
  69 + * @param modelId 流程定义编号
  70 + * @param sort 排序
  71 + */
  72 + void updateProcessDefinitionSortByModelId(String modelId, Long sort);
  73 +
  74 + /**
67 75 * 获得流程定义对应的 BPMN
68 76 *
69 77 * @param id 流程定义编号
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
... ... @@ -24,7 +24,6 @@ import org.flowable.engine.repository.ProcessDefinitionQuery;
24 24 import org.springframework.stereotype.Service;
25 25 import org.springframework.validation.annotation.Validated;
26 26  
27   -import java.nio.charset.StandardCharsets;
28 27 import java.util.*;
29 28  
30 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -120,7 +119,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
120 119  
121 120 @Override
122 121 public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
123   - byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form) {
  122 + byte[] bpmnBytes, String simpleJson, BpmFormDO form) {
124 123 // 创建 Deployment 部署
125 124 Deployment deploy = repositoryService.createDeployment()
126 125 .key(model.getKey()).name(model.getName()).category(model.getCategory())
... ... @@ -145,8 +144,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
145 144  
146 145 // 插入拓展表
147 146 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class)
148   - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()).setModelType(modelMetaInfo.getType())
149   - .setSimpleModel(StrUtil.str(simpleBytes, StandardCharsets.UTF_8));
  147 + .setModelId(model.getId()).setProcessDefinitionId(definition.getId())
  148 + .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson);
150 149  
151 150 if (form != null) {
152 151 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
... ... @@ -173,6 +172,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
173 172 }
174 173  
175 174 @Override
  175 + public void updateProcessDefinitionSortByModelId(String modelId, Long sort) {
  176 + processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort));
  177 + }
  178 +
  179 + @Override
176 180 public BpmnModel getProcessDefinitionBpmnModel(String id) {
177 181 return repositoryService.getBpmnModel(id);
178 182 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java deleted
1   -package cn.iocoder.yudao.module.bpm.service.task;
2   -
3   -import org.flowable.engine.history.HistoricActivityInstance;
4   -
5   -import java.util.List;
6   -
7   -/**
8   - * BPM 活动实例 Service 接口
9   - *
10   - * @author 芋道源码
11   - */
12   -public interface BpmActivityService {
13   -
14   - /**
15   - * 获得指定流程实例的活动实例列表
16   - *
17   - * @param processInstanceId 流程实例的编号
18   - * @return 活动实例列表
19   - */
20   - List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId);
21   -
22   - /**
23   - * 获得执行编号对应的活动实例
24   - *
25   - * @param executionId 执行编号
26   - * @return 活动实例
27   - */
28   - List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId);
29   -
30   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.bpm.service.task;
2   -
3   -import jakarta.annotation.Resource;
4   -import lombok.extern.slf4j.Slf4j;
5   -import org.flowable.engine.HistoryService;
6   -import org.flowable.engine.history.HistoricActivityInstance;
7   -import org.springframework.stereotype.Service;
8   -import org.springframework.validation.annotation.Validated;
9   -
10   -import java.util.List;
11   -
12   -
13   -/**
14   - * BPM 活动实例 Service 实现类
15   - *
16   - * @author 芋道源码
17   - */
18   -@Service
19   -@Slf4j
20   -@Validated
21   -public class BpmActivityServiceImpl implements BpmActivityService {
22   -
23   - @Resource
24   - private HistoryService historyService;
25   -
26   - @Override
27   - public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) {
28   - return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
29   - .orderByHistoricActivityInstanceStartTime().asc().list();
30   - }
31   -
32   - @Override
33   - public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) {
34   - return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list();
35   - }
36   -
37   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
... ... @@ -3,9 +3,10 @@ package cn.iocoder.yudao.module.bpm.service.task;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
5 5 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import org.flowable.bpmn.model.FlowNode;
6 8  
7 9 import java.util.Collection;
8   -import java.util.Set;
9 10  
10 11 /**
11 12 * 流程抄送 Service 接口
... ... @@ -15,24 +16,29 @@ import java.util.Set;
15 16 public interface BpmProcessInstanceCopyService {
16 17  
17 18 /**
18   - * 流程实例的抄送
  19 + * 【管理员】流程实例的抄送
19 20 *
20 21 * @param userIds 抄送的用户编号
  22 + * @param reason 抄送意见
21 23 * @param taskId 流程任务编号
22 24 */
23   - void createProcessInstanceCopy(Collection<Long> userIds, String taskId);
  25 + void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId);
24 26  
25 27 /**
26   - * 流程实例的抄送
  28 + * 【自动抄送】流程实例的抄送
27 29 *
28 30 * @param userIds 抄送的用户编号
  31 + * @param reason 抄送意见
29 32 * @param processInstanceId 流程编号
30   - * @param activityId 流程活动编号 id (对应 BPMN XML 节点 Id)
31   - * // TODO 芋艿这个 taskId 是不是可以不要了
32   - * @param taskId 任务编号
33   - * @param taskName 任务名称
  33 + * @param activityId 流程活动编号(对应 {@link FlowNode#getId()})
  34 + * @param activityName 任务编号(对应 {@link FlowNode#getName()})
  35 + * @param taskId 任务编号,允许空
34 36 */
35   - void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName);
  37 + void createProcessInstanceCopy(Collection<Long> userIds, String reason,
  38 + @NotEmpty(message = "流程实例编号不能为空") String processInstanceId,
  39 + @NotEmpty(message = "流程活动编号不能为空") String activityId,
  40 + @NotEmpty(message = "流程活动名字不能为空") String activityName,
  41 + String taskId);
36 42  
37 43 /**
38 44 * 获得抄送的流程的分页
... ... @@ -43,14 +49,5 @@ public interface BpmProcessInstanceCopyService {
43 49 */
44 50 PageResult<BpmProcessInstanceCopyDO> getProcessInstanceCopyPage(Long userId,
45 51 BpmProcessInstanceCopyPageReqVO pageReqVO);
46   - // TODO @芋艿:重点在 review 下
47   - /**
48   - * 通过流程实例和流程活动编号获取抄送人的 Id
49   - *
50   - * @param processInstanceId 流程实例 Id
51   - * @param activityId 流程活动编号 Id
52   - * @return 抄送人 Ids
53   - */
54   - Set<Long> getCopyUserIds(String processInstanceId, String activityId);
55 52  
56 53 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
7 6 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
8 7 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper;
... ... @@ -19,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
19 18  
20 19 import java.util.Collection;
21 20 import java.util.List;
22   -import java.util.Set;
23 21  
24 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
25 23 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -49,17 +47,19 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
49 47 private BpmProcessDefinitionService processDefinitionService;
50 48  
51 49 @Override
52   - public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) {
  50 + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) {
53 51 Task task = taskService.getTask(taskId);
54 52 if (ObjectUtil.isNull(task)) {
55 53 throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
56 54 }
57   - String processInstanceId = task.getProcessInstanceId();
58   - createProcessInstanceCopy(userIds, processInstanceId, task.getTaskDefinitionKey(), task.getId(), task.getName());
  55 + // 执行抄送
  56 + createProcessInstanceCopy(userIds, reason,
  57 + task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName());
59 58 }
60 59  
61 60 @Override
62   - public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName) {
  61 + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String processInstanceId,
  62 + String activityId, String activityName, String taskId) {
63 63 // 1.1 校验流程实例存在
64 64 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
65 65 if (processInstance == null) {
... ... @@ -74,10 +74,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
74 74  
75 75 // 2. 创建抄送流程
76 76 List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO()
77   - .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
  77 + .setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
78 78 .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
79   - .setCategory(processDefinition.getCategory()).setActivityId(activityId)
80   - .setTaskId(taskId).setTaskName(taskName));
  79 + .setCategory(processDefinition.getCategory()).setTaskId(taskId)
  80 + .setActivityId(activityId).setActivityName(activityName));
81 81 processInstanceCopyMapper.insertBatch(copyList);
82 82 }
83 83  
... ... @@ -87,10 +87,4 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
87 87 return processInstanceCopyMapper.selectPage(userId, pageReqVO);
88 88 }
89 89  
90   - @Override
91   - public Set<Long> getCopyUserIds(String processInstanceId, String activityId) {
92   - return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcessInstanceIdAndActivityId(processInstanceId, activityId),
93   - BpmProcessInstanceCopyDO::getUserId);
94   - }
95   -
96 90 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
... ... @@ -84,14 +84,6 @@ public interface BpmProcessInstanceService {
84 84 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
85 85 @Valid BpmProcessInstancePageReqVO pageReqVO);
86 86  
87   - /**
88   - * 获得表单字段权限
89   - *
90   - * @param reqVO 请求消息
91   - * @return 表单字段权限
92   - */
93   - Map<String, String> getFormFieldsPermission(@Valid BpmFormFieldsPermissionReqVO reqVO);
94   -
95 87 // TODO @芋艿:重点在 review 下
96 88 /**
97 89 * 获取审批详情。
... ... @@ -104,6 +96,14 @@ public interface BpmProcessInstanceService {
104 96 */
105 97 BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
106 98  
  99 + /**
  100 + * 获取流程实例的 BPMN 模型视图
  101 + *
  102 + * @param id 流程实例的编号
  103 + * @return BPMN 模型视图
  104 + */
  105 + BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id);
  106 +
107 107 // ========== Update 写入相关方法 ==========
108 108  
109 109 /**
... ... @@ -157,5 +157,4 @@ public interface BpmProcessInstanceService {
157 157 */
158 158 void processProcessInstanceCompleted(ProcessInstance instance);
159 159  
160   -
161 160 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1   -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map<String, String> getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set<String> runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map<String, ApprovalNodeInfo> runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List<ApprovalNodeInfo> approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List<HistoricActivityInstance> historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<ApprovalNodeInfo> notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO 芋艿:需要把 start 节点加出来 // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 respVO.setApproveNodes(approvalNodes); } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set<String> runNodeIds, Map<String, ApprovalNodeInfo> runningApprovalNodes, List<ApprovalNodeInfo> approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set<String> runNodeIds, Map<String, ApprovalNodeInfo> runningApprovalNodes, List<ApprovalNodeInfo> approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List<User> candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List<HistoricActivityInstance> historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List<HistoricActivityInstance> pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set<String> runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map<String, HistoricTaskInstance> taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map<String, List<HistoricTaskInstance>> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set<Long> userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set<Long> taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List<HistoricTaskInstance> addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map<Long, AdminUserRespDTO> userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息 final Multimap<String, HistoricActivityInstance> runningTask = ArrayListMultimap.create(); // 运行中的任务 List<ApprovalNodeInfo> approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List<ApprovalTaskInfo> approveTasks = CollUtil.newArrayList(approveTask); List<HistoricTaskInstance> addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map<String, ApprovalNodeInfo> runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List<ApprovalTaskInfo> approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List<HistoricTaskInstance> addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map<Long, AdminUserRespDTO> userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List<User> getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set<Long> userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map<Long, AdminUserRespDTO> adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } }
2 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; 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.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, String activityId, String taskId) { // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1.1 从 reqVO 中,读取公共变量 Long startUserId = loginUserId; // 流程发起人 HistoricProcessInstance historicProcessInstance = null; // 流程实例 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 if (reqVO.getProcessInstanceId() != null) { historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (historicProcessInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); processVariables = historicProcessInstance.getProcessVariables(); } // 1.3 读取其它相关数据 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); // 2.1 已结束 + 进行中的活动节点 List<ActivityNode> endActivityNodes = null; // 已结束的审批信息 List<ActivityNode> runActivityNodes = null; // 进行中的审批信息 List<HistoricActivityInstance> activities = null; // 流程实例列表 if (reqVO.getProcessInstanceId() != null) { activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId()); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true); endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo, historicProcessInstance, processInstanceStatus, activities, tasks); runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks); } // 2.2 流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, null, null); } // 3.1 计算当前登录用户的待办任务 // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。 BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); // 3.2 预测未运行节点的审批信息 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo, processVariables, activities); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); } /** * 拼接审批详情的最终数据 * <p> * 主要是,拼接审批人的用户信息、部门信息 */ private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, BpmnModel bpmnModel, ProcessDefinition processDefinition, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance processInstance, Integer processInstanceStatus, List<ActivityNode> endApprovalNodeInfos, List<ActivityNode> runningApprovalNodeInfos, List<ActivityNode> simulateApprovalNodeInfos, BpmTaskRespVO todoTask) { // 1. 获取所有需要读取用户信息的 userIds List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); // 2. 表单权限 Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId()); // 3. 拼接数据 return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance, processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap); } /** * 获得【已结束】的活动节点们 */ private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); List<ActivityNode> approvalNodes = convertList(endTasks, task -> { FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ? BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setStatus(FlowableUtils.getTaskStatus(task)) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); // 如果是取消状态,则跳过 if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { return null; } return activityNode; }); // 遍历 activities,只处理已结束的 StartEvent、EndEvent List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END))); endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID) .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); ActivityNode startNode = new ActivityNode().setId(startTask.getId()) .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType()) .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask)) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); approvalNodes.add(0, startNode); return; } // EndEvent if (ELEMENT_EVENT_END.equals(activity.getActivityType())) { if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) { // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示 return; } ActivityNode endNode = new ActivityNode().setId(activity.getId()) .setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance); if (StrUtil.isNotEmpty(reason)) { endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId()) .setStatus(endNode.getStatus()).setReason(reason))); } approvalNodes.add(endNode); } }); return approvalNodes; } /** * 获得【进行中】的活动节点们 */ private List<ActivityNode> getRunApproveNodeList(Long startUserId, BpmnModel bpmnModel, ProcessDefinition processDefinition, Map<String, Object> processVariables, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 构建运行中的任务,基于 activityId 分组 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId); // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId); return convertList(runningTaskMap.entrySet(), entry -> { String activityId = entry.getKey(); List<HistoricActivityInstance> taskActivities = entry.getValue(); // 构建活动节点 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName()) .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime())) .setTasks(new ArrayList<>()); // 处理每个任务的 tasks 属性 for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); // 加签子任务,需要过滤掉已经完成的加签子任务 List<HistoricTaskInstance> childrenTasks = filterList( taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), childTask -> childTask.getEndTime() == null); if (CollUtil.isNotEmpty(childrenTasks)) { activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); } } // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task if (BpmnModelUtils.isSequentialUserTask(flowNode)) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(), startUserId, processDefinition.getId(), processVariables); // 截取当前审批人位置后面的候选人,不包含当前审批人 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(), approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return activityNode; }); } /** * 获得【预测(未来)】的活动节点们 */ private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, List<HistoricActivityInstance> activities) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); // 情况一:BPMN 设计器 if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, processDefinitionInfo, processVariables, flowElement, runActivityIds)); } // 情况二:SIMPLE 设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, processDefinitionInfo, processVariables, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, BpmSimpleModelNodeVO node, Set<String> runActivityIds) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy()) .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点/审批节点 if (ObjectUtils.equalsAny(node.getType(), BpmSimpleModelNodeType.START_USER_NODE.getType(), BpmSimpleModelNodeType.APPROVE_NODE.getType())) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); activityNode.setCandidateUserIds(candidateUserIds); return activityNode; } // 2. 结束节点 if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) { return activityNode; } // 3. 抄送节点 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) { return activityNode; } return null; } private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, FlowElement node, Set<String> runActivityIds) { if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点 if (node instanceof StartEvent) { return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType()); } // 2. 审批节点 if (node instanceof UserTask) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) .setCandidateUserIds(candidateUserIds); } // 3. 结束节点 if (node instanceof EndEvent) { return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()); } return null; } private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, startUserId, processDefinitionId, processVariables); return new ArrayList<>(userIds); } @Override public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) { // 1.1 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获得流程定义 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); if (bpmnModel == null) { return null; } BpmSimpleModelNodeVO simpleModel = null; BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) { simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds); // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点 Set<String> rejectTaskActivityIds = CollUtil.newHashSet(); if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) { tasks.stream() .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task))) .max(Comparator.comparing(HistoricTaskInstance::getEndTime)) .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey())); finishedTaskActivityIds.removeAll(rejectTaskActivityIds); } // 2.2 拼接基础信息 Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel, unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds, userMap, deptMap); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel); if (CollUtil.isEmpty(tasks)) { return; } // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了 tasks.forEach(task -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } }
3 3 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
... ... @@ -6,7 +6,9 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
6 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum;
7 7 import jakarta.validation.Valid;
8 8 import org.flowable.bpmn.model.UserTask;
  9 +import org.flowable.engine.history.HistoricActivityInstance;
9 10 import org.flowable.task.api.Task;
  11 +import org.flowable.task.api.TaskInfo;
10 12 import org.flowable.task.api.history.HistoricTaskInstance;
11 13  
12 14 import java.util.Collection;
... ... @@ -33,6 +35,15 @@ public interface BpmTaskService {
33 35 PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO);
34 36  
35 37 /**
  38 + * 获得用户在指定流程下,首个需要处理(待办)的任务
  39 + *
  40 + * @param userId 用户编号
  41 + * @param processInstanceId 流程实例编号
  42 + * @return 待办任务
  43 + */
  44 + BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId);
  45 +
  46 + /**
36 47 * 获得已办的流程任务分页
37 48 *
38 49 * @param userId 用户编号
... ... @@ -73,9 +84,10 @@ public interface BpmTaskService {
73 84 * 获得指定流程实例的流程任务列表,包括所有状态的
74 85 *
75 86 * @param processInstanceId 流程实例的编号
  87 + * @param asc 是否升序
76 88 * @return 流程任务列表
77 89 */
78   - List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId);
  90 + List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc);
79 91  
80 92 /**
81 93 * 获取任务
... ... @@ -105,22 +117,31 @@ public interface BpmTaskService {
105 117 * 根据条件查询正在进行中的任务
106 118 *
107 119 * @param processInstanceId 流程实例编号,不允许为空
108   - * @param assigned 是否分配了审批人,允许空
109   - * @param taskDefineKey 任务定义 Key,允许空
  120 + * @param assigned 是否分配了审批人,允许空
  121 + * @param taskDefineKey 任务定义 Key,允许空
110 122 */
111 123 List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId,
112 124 Boolean assigned,
113 125 String taskDefineKey);
114 126  
115 127 /**
116   - * 获取当前任务的可回退的 UserTask 集合
  128 + * 获取当前任务的可退回的 UserTask 集合
117 129 *
118 130 * @param id 当前的任务 ID
119   - * @return 可以回退的节点列表
  131 + * @return 可以退回的节点列表
120 132 */
121 133 List<UserTask> getUserTaskListByReturn(String id);
122 134  
123 135 /**
  136 + * 获取指定任务的子任务列表(多层)
  137 + *
  138 + * @param parentTaskId 父任务 ID
  139 + * @param tasks 任务列表
  140 + * @return 子任务列表
  141 + */
  142 + <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks);
  143 +
  144 + /**
124 145 * 获取指定任务的子任务列表
125 146 *
126 147 * @param parentTaskId 父任务ID
... ... @@ -129,12 +150,20 @@ public interface BpmTaskService {
129 150 List<Task> getTaskListByParentTaskId(String parentTaskId);
130 151  
131 152 /**
132   - * 通过任务 ID,查询任务名 Map
  153 + * 获得指定流程实例的活动实例列表
133 154 *
134   - * @param taskIds 任务 ID
135   - * @return 任务 ID 与名字的 Map
  155 + * @param processInstanceId 流程实例的编号
  156 + * @return 活动实例列表
136 157 */
137   - Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds);
  158 + List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId);
  159 +
  160 + /**
  161 + * 获得执行编号对应的活动实例
  162 + *
  163 + * @param executionId 执行编号
  164 + * @return 活动实例
  165 + */
  166 + List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId);
138 167  
139 168 // ========== Update 写入相关方法 ==========
140 169  
... ... @@ -170,10 +199,10 @@ public interface BpmTaskService {
170 199 void moveTaskToEnd(String processInstanceId);
171 200  
172 201 /**
173   - * 将任务回退到指定的 targetDefinitionKey 位置
  202 + * 将任务退回到指定的 targetDefinitionKey 位置
174 203 *
175 204 * @param userId 用户编号
176   - * @param reqVO 回退的任务key和当前所在的任务ID
  205 + * @param reqVO 退回的任务key和当前所在的任务ID
177 206 */
178 207 void returnTask(Long userId, BpmTaskReturnReqVO reqVO);
179 208  
... ... @@ -201,14 +230,22 @@ public interface BpmTaskService {
201 230 */
202 231 void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO);
203 232  
  233 + /**
  234 + * 抄送任务
  235 + *
  236 + * @param userId 用户编号
  237 + * @param reqVO 通过请求
  238 + */
  239 + void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
  240 +
204 241 // ========== Event 事件相关方法 ==========
205 242  
206 243 /**
207 244 * 处理 Task 创建事件,目前是
208   - *
  245 + * <p>
209 246 * 1. 更新它的状态为审批中
210 247 * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过
211   - *
  248 + * <p>
212 249 * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后
213 250 *
214 251 * @param task 任务实体
... ... @@ -233,8 +270,8 @@ public interface BpmTaskService {
233 270 * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
234 271 *
235 272 * @param processInstanceId 流程示例编号
236   - * @param taskDefineKey 任务 Key
237   - * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum}
  273 + * @param taskDefineKey 任务 Key
  274 + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum}
238 275 */
239 276 void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType);
240 277  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableCon
21 21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
22 22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
23 23 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
  24 +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
24 25 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
25 26 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
26 27 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
... ... @@ -38,9 +39,11 @@ import org.flowable.engine.HistoryService;
38 39 import org.flowable.engine.ManagementService;
39 40 import org.flowable.engine.RuntimeService;
40 41 import org.flowable.engine.TaskService;
  42 +import org.flowable.engine.history.HistoricActivityInstance;
41 43 import org.flowable.engine.runtime.ProcessInstance;
42 44 import org.flowable.task.api.DelegationState;
43 45 import org.flowable.task.api.Task;
  46 +import org.flowable.task.api.TaskInfo;
44 47 import org.flowable.task.api.TaskQuery;
45 48 import org.flowable.task.api.history.HistoricTaskInstance;
46 49 import org.flowable.task.api.history.HistoricTaskInstanceQuery;
... ... @@ -81,6 +84,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
81 84 @Resource
82 85 private BpmProcessInstanceService processInstanceService;
83 86 @Resource
  87 + private BpmProcessDefinitionService bpmProcessDefinitionService;
  88 + @Resource
84 89 private BpmProcessInstanceCopyService processInstanceCopyService;
85 90 @Resource
86 91 private BpmModelService modelService;
... ... @@ -117,6 +122,41 @@ public class BpmTaskServiceImpl implements BpmTaskService {
117 122 }
118 123  
119 124 @Override
  125 + public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) {
  126 + if (processInstanceId == null) {
  127 + return null;
  128 + }
  129 + // 1. 查询所有任务
  130 + List<Task> tasks = taskService.createTaskQuery()
  131 + .active()
  132 + .processInstanceId(processInstanceId)
  133 + .includeTaskLocalVariables()
  134 + .includeProcessVariables()
  135 + .orderByTaskCreateTime().asc() // 按创建时间升序
  136 + .list();
  137 + if (CollUtil.isEmpty(tasks)) {
  138 + return null;
  139 + }
  140 +
  141 + // 2.1 查询我的首个任务
  142 + Task todoTask = CollUtil.findOne(tasks, task -> {
  143 + return isAssignUserTask(userId, task) // 当前用户为审批人
  144 + || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签)
  145 + });
  146 + if (todoTask == null) {
  147 + return null;
  148 + }
  149 + // 2.2 查询该任务的子任务
  150 + List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks);
  151 +
  152 + // 3. 转换返回
  153 + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
  154 + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting(
  155 + bpmnModel, todoTask.getTaskDefinitionKey());
  156 + return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting);
  157 + }
  158 +
  159 + @Override
120 160 public PageResult<HistoricTaskInstance> getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) {
121 161 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
122 162 .finished() // 已完成
... ... @@ -170,16 +210,16 @@ public class BpmTaskServiceImpl implements BpmTaskService {
170 210 }
171 211  
172 212 @Override
173   - public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) {
174   - List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
  213 + public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) {
  214 + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
175 215 .includeTaskLocalVariables()
176   - .processInstanceId(processInstanceId)
177   - .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
178   - .list();
179   - if (CollUtil.isEmpty(tasks)) {
180   - return Collections.emptyList();
  216 + .processInstanceId(processInstanceId);
  217 + if (Boolean.TRUE.equals(asc)) {
  218 + query.orderByHistoricTaskInstanceStartTime().asc();
  219 + } else {
  220 + query.orderByHistoricTaskInstanceStartTime().desc();
181 221 }
182   - return tasks;
  222 + return query.list();
183 223 }
184 224  
185 225 /**
... ... @@ -254,11 +294,43 @@ public class BpmTaskServiceImpl implements BpmTaskService {
254 294 if (CollUtil.isEmpty(previousUserList)) {
255 295 return Collections.emptyList();
256 296 }
257   - // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
  297 + // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回
258 298 previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
259 299 return previousUserList;
260 300 }
261 301  
  302 + @Override
  303 + public <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks) {
  304 + if (CollUtil.isEmpty(tasks)) {
  305 + return Collections.emptyList();
  306 + }
  307 + Map<String, List<T>> parentTaskMap = convertMultiMap(
  308 + filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId);
  309 + if (CollUtil.isEmpty(parentTaskMap)) {
  310 + return Collections.emptyList();
  311 + }
  312 +
  313 + List<T> result = new ArrayList<>();
  314 + // 1. 递归获取子级
  315 + Stack<String> stack = new Stack<>();
  316 + stack.push(parentTaskId);
  317 + // 2. 递归遍历
  318 + for (int i = 0; i < Short.MAX_VALUE; i++) {
  319 + if (stack.isEmpty()) {
  320 + break;
  321 + }
  322 + // 2.1 获取子任务们
  323 + String taskId = stack.pop();
  324 + List<T> childTaskList = filterList(tasks, task -> StrUtil.equals(task.getParentTaskId(), taskId));
  325 + // 2.2 如果非空,则添加到 stack 进一步递归
  326 + if (CollUtil.isNotEmpty(childTaskList)) {
  327 + stack.addAll(convertList(childTaskList, TaskInfo::getId));
  328 + result.addAll(childTaskList);
  329 + }
  330 + }
  331 + return result;
  332 + }
  333 +
262 334 /**
263 335 * 获得所有子任务列表
264 336 *
... ... @@ -331,12 +403,50 @@ public class BpmTaskServiceImpl implements BpmTaskService {
331 403 }
332 404  
333 405 @Override
334   - public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) {
335   - if (CollUtil.isEmpty(taskIds)) {
336   - return Collections.emptyMap();
337   - }
338   - List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list();
339   - return convertMap(tasks, Task::getId, Task::getName);
  406 + public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) {
  407 + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
  408 + .orderByHistoricActivityInstanceStartTime().asc().list();
  409 + }
  410 +
  411 + @Override
  412 + public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) {
  413 + return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list();
  414 + }
  415 +
  416 + /**
  417 + * 判断指定用户,是否是当前任务的审批人
  418 + *
  419 + * @param userId 用户编号
  420 + * @param task 任务
  421 + * @return 是否
  422 + */
  423 + private boolean isAssignUserTask(Long userId, Task task) {
  424 + Long assignee = NumberUtil.parseLong(task.getAssignee(), null);
  425 + return ObjectUtil.equals(userId, assignee);
  426 + }
  427 +
  428 + /**
  429 + * 判断指定用户,是否是当前任务的拥有人
  430 + *
  431 + * @param userId 用户编号
  432 + * @param task 任务
  433 + * @return 是否
  434 + */
  435 + private boolean isOwnerUserTask(Long userId, Task task) {
  436 + Long assignee = NumberUtil.parseLong(task.getOwner(), null);
  437 + return ObjectUtil.equal(userId, assignee);
  438 + }
  439 +
  440 + /**
  441 + * 判断指定用户,是否是当前任务的加签人
  442 + *
  443 + * @param userId 用户 Id
  444 + * @param task 任务
  445 + * @return 是否
  446 + */
  447 + private boolean isAddSignUserTask(Long userId, Task task) {
  448 + return (isAssignUserTask(userId, task) || isOwnerUserTask(userId, task))
  449 + && BpmTaskSignTypeEnum.of(task.getScopeType()) != null;
340 450 }
341 451  
342 452 // ========== Update 写入相关方法 ==========
... ... @@ -352,11 +462,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
352 462 throw exception(PROCESS_INSTANCE_NOT_EXISTS);
353 463 }
354 464  
355   - // 2. 抄送用户
356   - if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
357   - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId());
358   - }
359   -
360 465 // 情况一:被委派的任务,不调用 complete 去完成任务
361 466 if (DelegationState.PENDING.equals(task.getDelegationState())) {
362 467 approveDelegateTask(reqVO, task);
... ... @@ -370,12 +475,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
370 475 }
371 476  
372 477 // 情况三:审批普通的任务。大多数情况下,都是这样
373   - // 3.1 更新 task 状态、原因
  478 + // 2.1 更新 task 状态、原因
374 479 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason());
375   - // 3.2 添加评论
  480 + // 2.2 添加评论
376 481 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
377 482 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
378   - // 3.3 调用 BPM complete 去完成任务
  483 + // 2.3 调用 BPM complete 去完成任务
379 484 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
380 485 if (CollUtil.isNotEmpty(reqVO.getVariables())) {
381 486 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
... ... @@ -519,7 +624,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
519 624 BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement);
520 625 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
521 626 String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement);
522   - Assert.notNull(returnTaskId, "回退的节点不能为空");
  627 + Assert.notNull(returnTaskId, "退回的节点不能为空");
523 628 returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
524 629 .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
525 630 return;
... ... @@ -563,12 +668,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
563 668 FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),
564 669 reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
565 670  
566   - // 2. 调用 Flowable 框架的回退逻辑
  671 + // 2. 调用 Flowable 框架的退回逻辑
567 672 returnTask(task, targetElement, reqVO);
568 673 }
569 674  
570 675 /**
571   - * 回退流程节点时,校验目标任务节点是否可回退
  676 + * 退回流程节点时,校验目标任务节点是否可退回
572 677 *
573 678 * @param sourceKey 当前任务节点 Key
574 679 * @param targetKey 目标任务节点 key
... ... @@ -586,7 +691,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
586 691 throw exception(TASK_TARGET_NODE_NOT_EXISTS);
587 692 }
588 693  
589   - // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
  694 + // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回
590 695 if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
591 696 throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
592 697 }
... ... @@ -594,10 +699,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
594 699 }
595 700  
596 701 /**
597   - * 执行回退逻辑
  702 + * 执行退回逻辑
598 703 *
599   - * @param currentTask 当前回退的任务
600   - * @param targetElement 需要回退到的目标任务
  704 + * @param currentTask 当前退回的任务
  705 + * @param targetElement 需要退回到的目标任务
601 706 * @param reqVO 前端参数封装
602 707 */
603 708 public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
... ... @@ -610,9 +715,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
610 715 List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
611 716 List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
612 717  
613   - // 2. 给当前要被回退的 task 数组,设置回退意见
  718 + // 2. 给当前要被退回的 task 数组,设置退回意见
614 719 taskList.forEach(task -> {
615   - // 需要排除掉,不需要设置回退意见的任务
  720 + // 需要排除掉,不需要设置退回意见的任务
616 721 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
617 722 return;
618 723 }
... ... @@ -659,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
659 764 taskService.setOwner(taskId, task.getAssignee());
660 765 // 3.2 执行委派,将任务委派给 delegateUser
661 766 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
662   - // 3.3 更新 task 状态。
663   - // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用
664   - updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus());
  767 + // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
665 768 }
666 769  
667 770 @Override
... ... @@ -868,6 +971,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
868 971 handleParentTaskIfSign(task.getParentTaskId());
869 972 }
870 973  
  974 + @Override
  975 + public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) {
  976 + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
  977 + }
  978 +
871 979 /**
872 980 * 校验任务是否能被减签
873 981 *
... ... @@ -920,8 +1028,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
920 1028 if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
921 1029 return;
922 1030 }
  1031 + // TODO 芋艿:可以后续优化成 getSelf();
923 1032 // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
924 1033 if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
  1034 + // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝
  1035 + if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) {
  1036 + return;
  1037 + }
925 1038 if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
926 1039 SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
927 1040 .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
... ... @@ -987,7 +1100,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
987 1100 }
988 1101 // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
989 1102 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
990   - // 判断是否为回退或者驳回:如果是回退或者驳回不走这个策略
  1103 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
991 1104 // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
992 1105 Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
993 1106 String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java deleted
1   -package cn.iocoder.yudao.module.bpm.service.task.bo;
2   -
3   -import lombok.Data;
4   -
5   -import java.util.List;
6   -import java.util.Map;
7   -import java.util.Set;
8   -
9   -import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo;
10   -
11   -/**
12   - * 已经进行中的审批节点 Response BO
13   - *
14   - * @author jason
15   - */
16   -@Data
17   -public class AlreadyRunApproveNodeRespBO {
18   -
19   - /**
20   - * 审批节点信息数组
21   - */
22   - private List<ApprovalNodeInfo> approveNodes;
23   -
24   - /**
25   - * 已运行的节点 ID 数组 (对应 Bpmn XML 节点 id)
26   - */
27   - private Set<String> runNodeIds;
28   -
29   - /**
30   - * 正在运行的节点的审批信息(key: activityId, value: 审批信息)
31   - * <p>
32   - * 用于依次审批,需要加上候选人信息
33   - */
34   - private Map<String, ApprovalNodeInfo> runningApprovalNodes;
35   -
36   -}
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
6   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateUserStrategy;
  7 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other.BpmTaskCandidateAssignEmptyStrategy;
  9 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
7 10 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
8 11 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
  12 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  13 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
9 14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
10 15 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import org.flowable.bpmn.model.BpmnModel;
  17 +import org.flowable.bpmn.model.ExtensionElement;
  18 +import org.flowable.bpmn.model.FlowElement;
11 19 import org.flowable.bpmn.model.UserTask;
12 20 import org.flowable.engine.delegate.DelegateExecution;
  21 +import org.flowable.engine.runtime.ProcessInstance;
13 22 import org.junit.jupiter.api.BeforeEach;
14 23 import org.junit.jupiter.api.Test;
15 24 import org.mockito.Mock;
  25 +import org.mockito.MockedStatic;
16 26 import org.mockito.Spy;
  27 +import org.mockito.internal.util.collections.Sets;
17 28  
18   -import java.util.Collections;
19   -import java.util.List;
20   -import java.util.Map;
21   -import java.util.Set;
  29 +import java.util.*;
22 30  
23 31 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
24 32 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  33 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
  34 +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
  35 +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
25 36 import static org.junit.jupiter.api.Assertions.assertEquals;
26 37 import static org.mockito.ArgumentMatchers.eq;
27   -import static org.mockito.Mockito.mock;
28   -import static org.mockito.Mockito.when;
  38 +import static org.mockito.Mockito.*;
29 39  
30 40 /**
31 41 * {@link BpmTaskCandidateInvoker} 的单元测试
... ... @@ -39,44 +49,207 @@ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest {
39 49 @Mock
40 50 private AdminUserApi adminUserApi;
41 51  
  52 + @Mock
  53 + private BpmProcessInstanceService processInstanceService;
  54 +
42 55 @Spy
43   - private BpmTaskCandidateStrategy strategy ;
  56 + private BpmTaskCandidateStrategy userStrategy;
  57 + @Mock
  58 + private BpmTaskCandidateAssignEmptyStrategy emptyStrategy;
44 59  
45 60 @Spy
46   - private List<BpmTaskCandidateStrategy> strategyList ;
  61 + private List<BpmTaskCandidateStrategy> strategyList;
47 62  
48 63 @BeforeEach
49 64 public void setUp() {
50   - strategy = new BpmTaskCandidateUserStrategy(adminUserApi); // 创建strategy实例
51   - strategyList = Collections.singletonList(strategy); // 创建strategyList
  65 + userStrategy = new BpmTaskCandidateUserStrategy(); // 创建 strategy 实例
  66 + when(emptyStrategy.getStrategy()).thenReturn(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY);
  67 + strategyList = List.of(userStrategy, emptyStrategy); // 创建 strategyList
52 68 taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi);
53 69 }
54 70  
  71 + /**
  72 + * 场景:成功计算到候选人,但是移除了发起人的用户
  73 + */
55 74 @Test
56   - public void testCalculateUsers() {
57   - // 准备参数
58   - String param = "1,2";
59   - DelegateExecution execution = mock(DelegateExecution.class);
60   - // mock 方法(DelegateExecution)
61   - UserTask userTask = mock(UserTask.class);
62   - when(execution.getCurrentFlowElement()).thenReturn(userTask);
63   - when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
64   - .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
65   - when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
66   - .thenReturn(param);
67   - // mock 方法(adminUserApi)
68   - AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
69   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
70   - AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
71   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
72   - Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
73   - .put(user2.getId(), user2).build();
74   - when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
  75 + public void testCalculateUsersByTask_some() {
  76 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  77 + // 准备参数
  78 + String param = "1,2";
  79 + DelegateExecution execution = mock(DelegateExecution.class);
  80 + // mock 方法(DelegateExecution)
  81 + UserTask userTask = mock(UserTask.class);
  82 + String processInstanceId = randomString();
  83 + when(execution.getProcessInstanceId()).thenReturn(processInstanceId);
  84 + when(execution.getCurrentFlowElement()).thenReturn(userTask);
  85 + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
  86 + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
  87 + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
  88 + .thenReturn(param);
  89 + // mock 方法(adminUserApi)
  90 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
  91 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  92 + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
  93 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  94 + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
  95 + .put(user2.getId(), user2).build();
  96 + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
  97 + // mock 移除发起人的用户
  98 + springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class))
  99 + .thenReturn(processInstanceService);
  100 + ProcessInstance processInstance = mock(ProcessInstance.class);
  101 + when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance);
  102 + when(processInstance.getStartUserId()).thenReturn("1");
  103 + mockFlowElementExtensionElement(userTask, BpmnModelConstants.USER_TASK_ASSIGN_START_USER_HANDLER_TYPE,
  104 + String.valueOf(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType()));
75 105  
76   - // 调用
77   - Set<Long> results = taskCandidateInvoker.calculateUsers(execution);
78   - // 断言
79   - assertEquals(asSet(1L, 2L), results);
  106 + // 调用
  107 + Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution);
  108 + // 断言
  109 + assertEquals(asSet(2L), results);
  110 + }
  111 + }
  112 +
  113 + /**
  114 + * 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配
  115 + */
  116 + @Test
  117 + public void testCalculateUsersByTask_none() {
  118 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  119 + // 准备参数
  120 + String param = "1,2";
  121 + DelegateExecution execution = mock(DelegateExecution.class);
  122 + // mock 方法(DelegateExecution)
  123 + UserTask userTask = mock(UserTask.class);
  124 + String processInstanceId = randomString();
  125 + when(execution.getProcessInstanceId()).thenReturn(processInstanceId);
  126 + when(execution.getCurrentFlowElement()).thenReturn(userTask);
  127 + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
  128 + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
  129 + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
  130 + .thenReturn(param);
  131 + // mock 方法(adminUserApi)
  132 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
  133 + .setStatus(CommonStatusEnum.DISABLE.getStatus()));
  134 + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
  135 + .setStatus(CommonStatusEnum.DISABLE.getStatus()));
  136 + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
  137 + .put(user2.getId(), user2).build();
  138 + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
  139 + // mock 方法(empty)
  140 + when(emptyStrategy.calculateUsersByTask(same(execution), same(param)))
  141 + .thenReturn(Sets.newSet(2L));
  142 + // mock 移除发起人的用户
  143 + springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class))
  144 + .thenReturn(processInstanceService);
  145 + ProcessInstance processInstance = mock(ProcessInstance.class);
  146 + when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance);
  147 + when(processInstance.getStartUserId()).thenReturn("1");
  148 +
  149 + // 调用
  150 + Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution);
  151 + // 断言
  152 + assertEquals(asSet(2L), results);
  153 + }
  154 + }
  155 +
  156 + /**
  157 + * 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配
  158 + */
  159 + @Test
  160 + public void testCalculateUsersByActivity_some() {
  161 + try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
  162 + // 准备参数
  163 + String param = "1,2";
  164 + BpmnModel bpmnModel = mock(BpmnModel.class);
  165 + String activityId = randomString();
  166 + Long startUserId = 1L;
  167 + String processDefinitionId = randomString();
  168 + Map<String, Object> processVariables = new HashMap<>();
  169 + // mock 方法(DelegateExecution)
  170 + UserTask userTask = mock(UserTask.class);
  171 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask)))
  172 + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy());
  173 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask)))
  174 + .thenReturn(param);
  175 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask);
  176 + // mock 方法(adminUserApi)
  177 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
  178 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  179 + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
  180 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
  181 + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
  182 + .put(user2.getId(), user2).build();
  183 + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
  184 + // mock 移除发起人的用户
  185 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignStartUserHandlerType(same(userTask)))
  186 + .thenReturn(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType());
  187 +
  188 + // 调用
  189 + Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
  190 + startUserId, processDefinitionId, processVariables);
  191 + // 断言
  192 + assertEquals(asSet(2L), results);
  193 + }
  194 + }
  195 +
  196 + /**
  197 + * 场景:成功计算到候选人,但是移除了发起人的用户
  198 + */
  199 + @Test
  200 + public void testCalculateUsersByActivity_none() {
  201 + try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
  202 + // 准备参数
  203 + String param = "1,2";
  204 + BpmnModel bpmnModel = mock(BpmnModel.class);
  205 + String activityId = randomString();
  206 + Long startUserId = 1L;
  207 + String processDefinitionId = randomString();
  208 + Map<String, Object> processVariables = new HashMap<>();
  209 + // mock 方法(DelegateExecution)
  210 + UserTask userTask = mock(UserTask.class);
  211 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask)))
  212 + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy());
  213 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask)))
  214 + .thenReturn(param);
  215 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask);
  216 + // mock 方法(adminUserApi)
  217 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
  218 + .setStatus(CommonStatusEnum.DISABLE.getStatus()));
  219 + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
  220 + .setStatus(CommonStatusEnum.DISABLE.getStatus()));
  221 + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
  222 + .put(user2.getId(), user2).build();
  223 + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
  224 + // mock 方法(empty)
  225 + when(emptyStrategy.calculateUsersByActivity(same(bpmnModel), eq(activityId),
  226 + eq(param), same(startUserId), same(processDefinitionId), same(processVariables)))
  227 + .thenReturn(Sets.newSet(2L));
  228 +
  229 + // 调用
  230 + Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
  231 + startUserId, processDefinitionId, processVariables);
  232 + // 断言
  233 + assertEquals(asSet(2L), results);
  234 + }
  235 + }
  236 +
  237 + private static void mockFlowElementExtensionElement(FlowElement element, String name, String value) {
  238 + if (value == null) {
  239 + return;
  240 + }
  241 + ExtensionElement extensionElement = new ExtensionElement();
  242 + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
  243 + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
  244 + extensionElement.setElementText(value);
  245 + extensionElement.setName(name);
  246 + // mock
  247 + Map<String, List<ExtensionElement>> extensionElements = element.getExtensionElements();
  248 + if (extensionElements == null) {
  249 + extensionElements = new LinkedHashMap<>();
  250 + }
  251 + extensionElements.put(name, Collections.singletonList(extensionElement));
  252 + when(element.getExtensionElements()).thenReturn(extensionElements);
80 253 }
81 254  
82 255 @Test
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  5 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  6 +import org.assertj.core.util.Sets;
  7 +import org.junit.jupiter.api.Test;
  8 +import org.mockito.InjectMocks;
  9 +import org.mockito.Mock;
  10 +import org.mockito.stubbing.Answer;
  11 +
  12 +import java.util.Set;
  13 +
  14 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  15 +import static org.junit.jupiter.api.Assertions.assertEquals;
  16 +import static org.mockito.ArgumentMatchers.any;
  17 +import static org.mockito.Mockito.when;
  18 +
  19 +public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
  20 +
  21 + @InjectMocks
  22 + private BpmTaskCandidateDeptLeaderMultiStrategy strategy;
  23 +
  24 + @Mock
  25 + private DeptApi deptApi;
  26 +
  27 + @Test
  28 + public void testCalculateUsers() {
  29 + // 准备参数
  30 + String param = "10,20|2";
  31 + // mock 方法
  32 + when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> {
  33 + Long deptId = invocationOnMock.getArgument(0);
  34 + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1));
  35 + });
  36 +
  37 + // 调用
  38 + Set<Long> userIds = strategy.calculateUsers(param);
  39 + // 断言结果
  40 + assertEquals(Sets.newLinkedHashSet(11L, 1001L, 21L, 2001L), userIds);
  41 + }
  42 +
  43 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
  3 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
3 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 5 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
5 6 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
6   -import org.junit.jupiter.api.Disabled;
  7 +import org.assertj.core.util.Sets;
7 8 import org.junit.jupiter.api.Test;
8 9 import org.mockito.InjectMocks;
9 10 import org.mockito.Mock;
10 11  
11 12 import java.util.Set;
12 13  
13   -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
14 14 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
15 15 import static java.util.Arrays.asList;
16 16 import static org.junit.jupiter.api.Assertions.assertEquals;
17 17 import static org.mockito.ArgumentMatchers.eq;
18 18 import static org.mockito.Mockito.when;
19 19  
20   -@Disabled // TODO 芋艿:临时注释
21 20 public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest {
22 21  
23 22 @InjectMocks
... ... @@ -29,16 +28,16 @@ public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest
29 28 @Test
30 29 public void testCalculateUsers() {
31 30 // 准备参数
32   - String param = "1,2";
  31 + String param = "10,20";
33 32 // mock 方法
34   - DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L));
35   - DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L));
36   - when(deptApi.getDeptList(eq(asSet(1L, 2L)))).thenReturn(asList(dept1, dept2));
  33 + when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList(
  34 + randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)),
  35 + randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L))));
37 36  
38 37 // 调用
39   - Set<Long> results = strategy.calculateUsers(null, param);
40   - // 断言
41   - assertEquals(asSet(11L, 22L), results);
  38 + Set<Long> userIds = strategy.calculateUsers(param);
  39 + // 断言结果
  40 + assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
42 41 }
43 42  
44 43 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
2 2  
  3 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
3 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
4 6 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
5 7 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
6   -import org.junit.jupiter.api.Disabled;
  8 +import org.assertj.core.util.Sets;
7 9 import org.junit.jupiter.api.Test;
8 10 import org.mockito.InjectMocks;
9 11 import org.mockito.Mock;
10 12  
11   -import java.util.List;
12 13 import java.util.Set;
13 14  
14   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
15   -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
  15 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  16 +import static java.util.Arrays.asList;
16 17 import static org.junit.jupiter.api.Assertions.assertEquals;
17 18 import static org.mockito.ArgumentMatchers.eq;
18 19 import static org.mockito.Mockito.when;
19 20  
20   -@Disabled // TODO 芋艿:临时注释
21 21 public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest {
22 22  
23 23 @InjectMocks
24 24 private BpmTaskCandidateDeptMemberStrategy strategy;
25 25  
26 26 @Mock
  27 + private DeptApi deptApi;
  28 + @Mock
27 29 private AdminUserApi adminUserApi;
28 30  
29 31 @Test
30 32 public void testCalculateUsers() {
31 33 // 准备参数
32   - String param = "11,22";
  34 + String param = "10,20";
33 35 // mock 方法
34   - List<AdminUserRespDTO> users = convertList(asSet(11L, 22L),
35   - id -> new AdminUserRespDTO().setId(id));
36   - when(adminUserApi.getUserListByDeptIds(eq(asSet(11L, 22L)))).thenReturn(users);
  36 + when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList(
  37 + randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)),
  38 + randomPojo(AdminUserRespDTO.class, o -> o.setId(21L))));
37 39  
38 40 // 调用
39   - Set<Long> results = strategy.calculateUsers(null, param);
40   - // 断言
41   - assertEquals(asSet(11L, 22L), results);
  41 + Set<Long> userIds = strategy.calculateUsers(param);
  42 + // 断言结果
  43 + assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
42 44 }
43 45  
44 46 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  5 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  6 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  7 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  8 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  9 +import org.assertj.core.util.Sets;
  10 +import org.flowable.engine.delegate.DelegateExecution;
  11 +import org.flowable.engine.runtime.ProcessInstance;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.mockito.InjectMocks;
  14 +import org.mockito.Mock;
  15 +import org.mockito.stubbing.Answer;
  16 +
  17 +import java.util.Set;
  18 +
  19 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  20 +import static org.junit.jupiter.api.Assertions.assertEquals;
  21 +import static org.mockito.ArgumentMatchers.any;
  22 +import static org.mockito.ArgumentMatchers.eq;
  23 +import static org.mockito.Mockito.mock;
  24 +import static org.mockito.Mockito.when;
  25 +
  26 +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
  27 +
  28 + @InjectMocks
  29 + private BpmTaskCandidateStartUserDeptLeaderMultiStrategy strategy;
  30 +
  31 + @Mock
  32 + private BpmProcessInstanceService processInstanceService;
  33 +
  34 + @Mock
  35 + private AdminUserApi adminUserApi;
  36 + @Mock
  37 + private DeptApi deptApi;
  38 +
  39 + @Test
  40 + public void testCalculateUsersByTask() {
  41 + // 准备参数
  42 + String param = "2";
  43 + // mock 方法(获得流程发起人)
  44 + Long startUserId = 1L;
  45 + ProcessInstance processInstance = mock(ProcessInstance.class);
  46 + DelegateExecution execution = mock(DelegateExecution.class);
  47 + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
  48 + when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
  49 + // mock 方法(获取发起人的 multi 部门负责人)
  50 + mockGetStartUserDept(startUserId);
  51 +
  52 + // 调用
  53 + Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
  54 + // 断言
  55 + assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
  56 + }
  57 +
  58 + @Test
  59 + public void testCalculateUsersByActivity() {
  60 + // 准备参数
  61 + String param = "2";
  62 + // mock 方法
  63 + Long startUserId = 1L;
  64 + mockGetStartUserDept(startUserId);
  65 +
  66 + // 调用
  67 + Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
  68 + startUserId, null, null);
  69 + // 断言
  70 + assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
  71 + }
  72 +
  73 + private void mockGetStartUserDept(Long startUserId) {
  74 + when(adminUserApi.getUser(eq(startUserId))).thenReturn(
  75 + randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L)));
  76 + when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> {
  77 + Long deptId = invocationOnMock.getArgument(0);
  78 + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1));
  79 + });
  80 + }
  81 +
  82 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  5 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  6 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  7 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  8 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  9 +import org.assertj.core.util.Sets;
  10 +import org.flowable.engine.delegate.DelegateExecution;
  11 +import org.flowable.engine.runtime.ProcessInstance;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.mockito.InjectMocks;
  14 +import org.mockito.Mock;
  15 +import org.mockito.stubbing.Answer;
  16 +
  17 +import java.util.Set;
  18 +
  19 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  20 +import static org.junit.jupiter.api.Assertions.assertEquals;
  21 +import static org.mockito.ArgumentMatchers.any;
  22 +import static org.mockito.ArgumentMatchers.eq;
  23 +import static org.mockito.Mockito.mock;
  24 +import static org.mockito.Mockito.when;
  25 +
  26 +public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockitoUnitTest {
  27 +
  28 + @InjectMocks
  29 + private BpmTaskCandidateStartUserDeptLeaderStrategy strategy;
  30 +
  31 + @Mock
  32 + private BpmProcessInstanceService processInstanceService;
  33 +
  34 + @Mock
  35 + private AdminUserApi adminUserApi;
  36 + @Mock
  37 + private DeptApi deptApi;
  38 +
  39 + @Test
  40 + public void testCalculateUsersByTask() {
  41 + // 准备参数
  42 + String param = "2";
  43 + // mock 方法(获得流程发起人)
  44 + Long startUserId = 1L;
  45 + ProcessInstance processInstance = mock(ProcessInstance.class);
  46 + DelegateExecution execution = mock(DelegateExecution.class);
  47 + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
  48 + when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
  49 + // mock 方法(获取发起人的部门负责人)
  50 + mockGetStartUserDeptLeader(startUserId);
  51 +
  52 + // 调用
  53 + Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
  54 + // 断言
  55 + assertEquals(Sets.newLinkedHashSet(1001L), userIds);
  56 + }
  57 +
  58 + @Test
  59 + public void testGetStartUserDeptLeader() {
  60 + // 准备参数
  61 + String param = "2";
  62 + // mock 方法
  63 + Long startUserId = 1L;
  64 + mockGetStartUserDeptLeader(startUserId);
  65 +
  66 + // 调用
  67 + Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
  68 + startUserId, null, null);
  69 + // 断言
  70 + assertEquals(Sets.newLinkedHashSet(1001L), userIds);
  71 + }
  72 +
  73 + private void mockGetStartUserDeptLeader(Long startUserId) {
  74 + when(adminUserApi.getUser(eq(startUserId))).thenReturn(
  75 + randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L)));
  76 + when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> {
  77 + Long deptId = invocationOnMock.getArgument(0);
  78 + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1));
  79 + });
  80 + }
  81 +
  82 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
  6 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  7 +import org.assertj.core.util.Sets;
  8 +import org.flowable.engine.delegate.DelegateExecution;
  9 +import org.flowable.engine.runtime.ProcessInstance;
  10 +import org.junit.jupiter.api.Test;
  11 +import org.mockito.InjectMocks;
  12 +import org.mockito.Mock;
  13 +
  14 +import java.util.HashMap;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +import java.util.Set;
  18 +
  19 +import static org.junit.jupiter.api.Assertions.assertEquals;
  20 +import static org.mockito.ArgumentMatchers.eq;
  21 +import static org.mockito.Mockito.mock;
  22 +import static org.mockito.Mockito.when;
  23 +
  24 +public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnitTest {
  25 +
  26 + @InjectMocks
  27 + private BpmTaskCandidateStartUserSelectStrategy strategy;
  28 +
  29 + @Mock
  30 + private BpmProcessInstanceService processInstanceService;
  31 +
  32 + @Test
  33 + public void testCalculateUsersByTask() {
  34 + // 准备参数
  35 + String param = "2";
  36 + // mock 方法(获得流程发起人)
  37 + ProcessInstance processInstance = mock(ProcessInstance.class);
  38 + DelegateExecution execution = mock(DelegateExecution.class);
  39 + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
  40 + when(execution.getCurrentActivityId()).thenReturn("activity_001");
  41 + // mock 方法(FlowableUtils)
  42 + Map<String, Object> processVariables = new HashMap<>();
  43 + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
  44 + MapUtil.of("activity_001", List.of(1L, 2L)));
  45 + when(processInstance.getProcessVariables()).thenReturn(processVariables);
  46 +
  47 + // 调用
  48 + Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
  49 + // 断言
  50 + assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds);
  51 + }
  52 +
  53 + @Test
  54 + public void testCalculateUsersByActivity() {
  55 + // 准备参数
  56 + String activityId = "activity_001";
  57 + Map<String, Object> processVariables = new HashMap<>();
  58 + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
  59 + MapUtil.of("activity_001", List.of(1L, 2L)));
  60 +
  61 + // 调用
  62 + Set<Long> userIds = strategy.calculateUsersByActivity(null, activityId, null,
  63 + null, null, processVariables);
  64 + // 断言
  65 + assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds);
  66 + }
  67 +
  68 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategyTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
  2 +
  3 +import cn.hutool.core.collection.ListUtil;
  4 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  6 +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  7 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  9 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  10 +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
  11 +import org.flowable.bpmn.model.BpmnModel;
  12 +import org.flowable.bpmn.model.FlowElement;
  13 +import org.flowable.engine.delegate.DelegateExecution;
  14 +import org.junit.jupiter.api.Test;
  15 +import org.mockito.InjectMocks;
  16 +import org.mockito.Mock;
  17 +import org.mockito.MockedStatic;
  18 +
  19 +import java.util.Set;
  20 +
  21 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  22 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
  23 +import static org.junit.jupiter.api.Assertions.assertEquals;
  24 +import static org.mockito.Mockito.*;
  25 +
  26 +public class BpmTaskCandidateAssignEmptyStrategyTest extends BaseMockitoUnitTest {
  27 +
  28 + @InjectMocks
  29 + private BpmTaskCandidateAssignEmptyStrategy strategy;
  30 +
  31 + @Mock
  32 + private BpmProcessDefinitionService processDefinitionService;
  33 +
  34 + @Test
  35 + public void testCalculateUsersByTask() {
  36 + try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class);
  37 + MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
  38 + // 准备参数
  39 + DelegateExecution execution = mock(DelegateExecution.class);
  40 + String param = randomString();
  41 + // mock 方法(execution)
  42 + String processDefinitionId = randomString();
  43 + when(execution.getProcessDefinitionId()).thenReturn(processDefinitionId);
  44 + FlowElement flowElement = mock(FlowElement.class);
  45 + when(execution.getCurrentFlowElement()).thenReturn(flowElement);
  46 + // mock 方法(parseAssignEmptyHandlerType)
  47 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement)))
  48 + .thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType());
  49 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerUserIds(same(flowElement)))
  50 + .thenReturn(ListUtil.of(1L, 2L));
  51 +
  52 + // 调用
  53 + Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
  54 + // 断言
  55 + assertEquals(SetUtils.asSet(1L, 2L), userIds);
  56 + }
  57 +
  58 + }
  59 +
  60 + @Test
  61 + public void testCalculateUsersByActivity() {
  62 + try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
  63 + // 准备参数
  64 + String processDefinitionId = randomString();
  65 + String activityId = randomString();
  66 + String param = randomString();
  67 + // mock 方法(getFlowElementById)
  68 + FlowElement flowElement = mock(FlowElement.class);
  69 + BpmnModel bpmnModel = mock(BpmnModel.class);
  70 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId)))
  71 + .thenReturn(flowElement);
  72 + // mock 方法(parseAssignEmptyHandlerType)
  73 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement)))
  74 + .thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType());
  75 + // mock 方法(getProcessDefinitionInfo)
  76 + BpmProcessDefinitionInfoDO processDefinition = randomPojo(BpmProcessDefinitionInfoDO.class,
  77 + o -> o.setManagerUserIds(ListUtil.of(1L, 2L)));
  78 + when(processDefinitionService.getProcessDefinitionInfo(eq(processDefinitionId))).thenReturn(processDefinition);
  79 +
  80 + // 调用
  81 + Set<Long> userIds = strategy.calculateUsersByActivity(bpmnModel, activityId, param,
  82 + null, processDefinitionId, null);
  83 + // 断言
  84 + assertEquals(SetUtils.asSet(1L, 2L), userIds);
  85 + }
  86 + }
  87 +
  88 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategyTest.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
2 2  
3 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
... ... @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test;
8 8 import org.mockito.InjectMocks;
9 9 import org.mockito.MockedStatic;
10 10  
  11 +import java.util.HashMap;
  12 +import java.util.Map;
11 13 import java.util.Set;
12 14  
13 15 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
... ... @@ -22,7 +24,7 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest
22 24 private BpmTaskCandidateExpressionStrategy strategy;
23 25  
24 26 @Test
25   - public void testCalculateUsers() {
  27 + public void testCalculateUsersByTask() {
26 28 try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
27 29 // 准备参数
28 30 String param = "1,2";
... ... @@ -32,7 +34,25 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest
32 34 .thenReturn(asSet(1L, 2L));
33 35  
34 36 // 调用
35   - Set<Long> results = strategy.calculateUsers(execution, param);
  37 + Set<Long> results = strategy.calculateUsersByTask(execution, param);
  38 + // 断言
  39 + assertEquals(asSet(1L, 2L), results);
  40 + }
  41 + }
  42 +
  43 + @Test
  44 + public void testCalculateUsersByActivity() {
  45 + try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
  46 + // 准备参数
  47 + String param = "1,2";
  48 + Map<String, Object> processVariables = new HashMap<>();
  49 + // mock 方法
  50 + flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(processVariables), eq(param)))
  51 + .thenReturn(asSet(1L, 2L));
  52 +
  53 + // 调用
  54 + Set<Long> results = strategy.calculateUsersByActivity(null, null, param,
  55 + null, null, processVariables);
36 56 // 断言
37 57 assertEquals(asSet(1L, 2L), results);
38 58 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategyTest.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
... ... @@ -36,9 +36,9 @@ public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest {
36 36 when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2));
37 37  
38 38 // 调用
39   - Set<Long> results = strategy.calculateUsers(null, param);
  39 + Set<Long> userIds = strategy.calculateUsersByTask(null, param);
40 40 // 断言
41   - assertEquals(asSet(11L, 12L, 21L, 22L), results);
  41 + assertEquals(asSet(11L, 12L, 21L, 22L), userIds);
42 42 }
43 43  
44 44 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 4 import cn.iocoder.yudao.module.system.api.dept.PostApi;
... ... @@ -39,9 +39,9 @@ public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest {
39 39 when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(users);
40 40  
41 41 // 调用
42   - Set<Long> results = strategy.calculateUsers(null, param);
  42 + Set<Long> userIds = strategy.calculateUsersByTask(null, param);
43 43 // 断言
44   - assertEquals(asSet(11L, 22L), results);
  44 + assertEquals(asSet(11L, 22L), userIds);
45 45 }
46 46  
47 47 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategyTest.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
2 2  
3 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 4 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
... ... @@ -35,9 +35,9 @@ public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest {
35 35 .thenReturn(asSet(11L, 22L));
36 36  
37 37 // 调用
38   - Set<Long> results = strategy.calculateUsers(null, param);
  38 + Set<Long> userIds = strategy.calculateUsersByTask(null, param);
39 39 // 断言
40   - assertEquals(asSet(11L, 22L), results);
  40 + assertEquals(asSet(11L, 22L), userIds);
41 41 }
42 42  
43 43 }
... ...