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
.image/工作流设计器-bpmn.jpg 0 → 100644

177 KB

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

127 KB

README.md
@@ -149,18 +149,22 @@ @@ -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 ![功能图](/.image/common/bpm-feature.png) 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,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.CollectionUtil; 4 import cn.hutool.core.collection.CollectionUtil;
5 import cn.hutool.core.util.ArrayUtil; 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import com.google.common.collect.ImmutableMap; 7 import com.google.common.collect.ImmutableMap;
7 8
8 import java.util.*; 9 import java.util.*;
@@ -73,6 +74,13 @@ public class CollectionUtils { @@ -73,6 +74,13 @@ public class CollectionUtils {
73 return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); 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 public static <T, U> List<U> convertListByFlatMap(Collection<T> from, 84 public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
77 Function<T, ? extends Stream<? extends U>> func) { 85 Function<T, ? extends Stream<? extends U>> func) {
78 if (CollUtil.isEmpty(from)) { 86 if (CollUtil.isEmpty(from)) {
@@ -324,7 +332,7 @@ public class CollectionUtils { @@ -324,7 +332,7 @@ public class CollectionUtils {
324 } 332 }
325 333
326 public static <T> List<T> newArrayList(List<List<T>> list) { 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 \ No newline at end of file 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,8 +64,10 @@ public abstract class AbstractWebSocketMessageSender implements WebSocketMessage
64 sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType); 64 sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);
65 } 65 }
66 if (CollUtil.isEmpty(sessions)) { 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 // 2. 执行发送 72 // 2. 执行发送
71 doSend(sessions, messageType, messageContent); 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,7 +23,7 @@ public interface ErrorCodeConstants {
23 "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); 23 "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); 24 ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
25 ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); 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 // ========== 流程定义 1-009-003-000 ========== 28 // ========== 流程定义 1-009-003-000 ==========
29 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); 29 ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@@ -35,8 +35,8 @@ public interface ErrorCodeConstants { @@ -35,8 +35,8 @@ public interface ErrorCodeConstants {
35 ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在"); 35 ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
36 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中"); 36 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
37 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); 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 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); 40 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
41 41
42 // ========== 流程任务 1-009-005-000 ========== 42 // ========== 流程任务 1-009-005-000 ==========
@@ -44,7 +44,7 @@ public interface ErrorCodeConstants { @@ -44,7 +44,7 @@ public interface ErrorCodeConstants {
44 ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); 44 ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
45 ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作"); 45 ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
46 ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在"); 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 ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人"); 48 ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
49 ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在"); 49 ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
50 ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在"); 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,30 +18,26 @@ import java.util.Objects;
18 public enum BpmSimpleModelNodeType implements IntArrayValuable { 18 public enum BpmSimpleModelNodeType implements IntArrayValuable {
19 19
20 // 0 ~ 1 开始和结束 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 // 10 ~ 49 各种节点 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 // 50 ~ 条件分支 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 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); 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 private final Integer type; 38 private final Integer type;
43 - private final String bpmnType;  
44 private final String name; 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,16 +50,6 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
54 || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type); 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 public static BpmSimpleModelNodeType valueOf(Integer type) { 53 public static BpmSimpleModelNodeType valueOf(Integer type) {
68 return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); 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,20 +16,23 @@ import java.util.Arrays;
16 @AllArgsConstructor 16 @AllArgsConstructor
17 public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable { 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 private final Integer method; 27 private final Integer method;
28 -  
29 /** 28 /**
30 * 名字 29 * 名字
31 */ 30 */
32 private final String name; 31 private final String name;
  32 + /**
  33 + * 完成表达式
  34 + */
  35 + private final String completionCondition;
33 36
34 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray(); 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,6 +38,10 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
38 return ARRAYS; 38 return ARRAYS;
39 } 39 }
40 40
  41 + public static boolean isRejectStatus(Integer status) {
  42 + return REJECT.getStatus().equals(status);
  43 + }
  44 +
41 public static boolean isProcessEndStatus(Integer status) { 45 public static boolean isProcessEndStatus(Integer status) {
42 return ObjectUtils.equalsAny(status, 46 return ObjectUtils.equalsAny(status,
43 APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); 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 package cn.iocoder.yudao.module.bpm.enums.task; 1 package cn.iocoder.yudao.module.bpm.enums.task;
2 2
  3 +import cn.hutool.core.util.ObjUtil;
3 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 4 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
4 import lombok.AllArgsConstructor; 5 import lombok.AllArgsConstructor;
5 import lombok.Getter; 6 import lombok.Getter;
@@ -20,7 +21,6 @@ public enum BpmTaskStatusEnum { @@ -20,7 +21,6 @@ public enum BpmTaskStatusEnum {
20 CANCEL(4, "已取消"), 21 CANCEL(4, "已取消"),
21 22
22 RETURN(5, "已退回"), 23 RETURN(5, "已退回"),
23 - DELEGATE(6, "委派中"),  
24 24
25 /** 25 /**
26 * 使用场景: 26 * 使用场景:
@@ -45,6 +45,10 @@ public enum BpmTaskStatusEnum { @@ -45,6 +45,10 @@ public enum BpmTaskStatusEnum {
45 */ 45 */
46 private final String name; 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 * 判断该状态是否已经处于 End 最终状态 53 * 判断该状态是否已经处于 End 最终状态
50 * <p> 54 * <p>
@@ -59,4 +63,8 @@ public enum BpmTaskStatusEnum { @@ -59,4 +63,8 @@ public enum BpmTaskStatusEnum {
59 RETURN.getStatus(), APPROVING.getStatus()); 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,11 +9,14 @@ public class UserSimpleBaseVO {
9 9
10 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 10 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
11 private Long id; 11 private Long id;
12 -  
13 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") 12 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
14 private String nickname; 13 private String nickname;
15 -  
16 @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") 14 @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
17 private String avatar; 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 \ No newline at end of file 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,6 +48,15 @@ public class BpmCategoryController {
48 return success(true); 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 @DeleteMapping("/delete") 60 @DeleteMapping("/delete")
52 @Operation(summary = "删除流程分类") 61 @Operation(summary = "删除流程分类")
53 @Parameter(name = "id", description = "编号", required = true) 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,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 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 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; 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,7 +26,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
28 import org.springframework.validation.annotation.Validated; 26 import org.springframework.validation.annotation.Validated;
29 import org.springframework.web.bind.annotation.*; 27 import org.springframework.web.bind.annotation.*;
30 28
31 -import java.util.HashSet; 29 +import java.util.Collections;
32 import java.util.List; 30 import java.util.List;
33 import java.util.Map; 31 import java.util.Map;
34 import java.util.Set; 32 import java.util.Set;
@@ -56,38 +54,38 @@ public class BpmModelController { @@ -56,38 +54,38 @@ public class BpmModelController {
56 @Resource 54 @Resource
57 private AdminUserApi adminUserApi; 55 private AdminUserApi adminUserApi;
58 56
59 - @GetMapping("/page") 57 + @GetMapping("/list")
60 @Operation(summary = "获得模型分页") 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 // 获得 Form 表单 66 // 获得 Form 表单
69 - Set<Long> formIds = convertSet(pageResult.getList(), model -> { 67 + Set<Long> formIds = convertSet(list, model -> {
70 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); 68 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
71 return metaInfo != null ? metaInfo.getFormId() : null; 69 return metaInfo != null ? metaInfo.getFormId() : null;
72 }); 70 });
73 Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); 71 Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
74 // 获得 Category Map 72 // 获得 Category Map
75 Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap( 73 Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
76 - convertSet(pageResult.getList(), Model::getCategory)); 74 + convertSet(list, Model::getCategory));
77 // 获得 Deployment Map 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 // 获得 ProcessDefinition Map 78 // 获得 ProcessDefinition Map
82 - List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); 79 + List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
  80 + deploymentMap.keySet());
83 Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); 81 Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
84 // 获得 User Map 82 // 获得 User Map
85 - Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), model -> { 83 + Set<Long> userIds = convertSetByFlatMap(list, model -> {
86 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); 84 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
87 return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); 85 return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
88 }); 86 });
89 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); 87 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
90 - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, 88 + return success(BpmModelConvert.INSTANCE.buildModelList(list,
91 formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); 89 formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
92 } 90 }
93 91
@@ -111,6 +109,7 @@ public class BpmModelController { @@ -111,6 +109,7 @@ public class BpmModelController {
111 return success(modelService.createModel(createRetVO)); 109 return success(modelService.createModel(createRetVO));
112 } 110 }
113 111
  112 +
114 @PutMapping("/update") 113 @PutMapping("/update")
115 @Operation(summary = "修改模型") 114 @Operation(summary = "修改模型")
116 @PreAuthorize("@ss.hasPermission('bpm:model:update')") 115 @PreAuthorize("@ss.hasPermission('bpm:model:update')")
@@ -119,6 +118,14 @@ public class BpmModelController { @@ -119,6 +118,14 @@ public class BpmModelController {
119 return success(true); 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 @PostMapping("/deploy") 129 @PostMapping("/deploy")
123 @Operation(summary = "部署模型") 130 @Operation(summary = "部署模型")
124 @Parameter(name = "id", description = "编号", required = true, example = "1024") 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,7 +9,6 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConver
9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 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 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; 12 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
14 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; 13 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
15 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; 14 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
@@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Parameter; @@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Parameter;
18 import io.swagger.v3.oas.annotations.tags.Tag; 17 import io.swagger.v3.oas.annotations.tags.Tag;
19 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
20 import org.flowable.bpmn.model.BpmnModel; 19 import org.flowable.bpmn.model.BpmnModel;
21 -import org.flowable.bpmn.model.UserTask;  
22 import org.flowable.engine.repository.Deployment; 20 import org.flowable.engine.repository.Deployment;
23 import org.flowable.engine.repository.ProcessDefinition; 21 import org.flowable.engine.repository.ProcessDefinition;
24 import org.springframework.security.access.prepost.PreAuthorize; 22 import org.springframework.security.access.prepost.PreAuthorize;
@@ -115,9 +113,8 @@ public class BpmProcessDefinitionController { @@ -115,9 +113,8 @@ public class BpmProcessDefinitionController {
115 } 113 }
116 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); 114 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
117 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); 115 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
118 - List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);  
119 return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( 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,7 +15,9 @@ import java.util.List;
15 * BPM 流程 MetaInfo Response DTO 15 * BPM 流程 MetaInfo Response DTO
16 * 主要用于 { Model#setMetaInfo(String)} 的存储 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 * @author 芋道源码 22 * @author 芋道源码
21 */ 23 */
@@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO { @@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO {
40 @NotNull(message = "表单类型不能为空") 42 @NotNull(message = "表单类型不能为空")
41 private Integer formType; 43 private Integer formType;
42 @Schema(description = "表单编号", example = "1024") 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 @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") 51 @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
52 @NotNull(message = "是否可见不能为空") 52 @NotNull(message = "是否可见不能为空")
@@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO { @@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO {
59 @NotEmpty(message = "可管理用户编号数组不能为空") 59 @NotEmpty(message = "可管理用户编号数组不能为空")
60 private List<Long> managerUserIds; 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,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import cn.iocoder.yudao.module.bpm.enums.definition.*; 4 import cn.iocoder.yudao.module.bpm.enums.definition.*;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 -import com.fasterxml.jackson.annotation.JsonIgnore;  
7 import com.fasterxml.jackson.annotation.JsonInclude; 6 import com.fasterxml.jackson.annotation.JsonInclude;
8 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
9 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
@@ -31,7 +30,6 @@ public class BpmSimpleModelNodeVO { @@ -31,7 +30,6 @@ public class BpmSimpleModelNodeVO {
31 @Schema(description = "模型节点名称", example = "领导审批") 30 @Schema(description = "模型节点名称", example = "领导审批")
32 private String name; 31 private String name;
33 32
34 - // TODO @jason:和 gpt 大模型对了下这个字段的命名,貌似叫 displayText 合适点。可以等最后我们全局替换下。(优先级:低)  
35 @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") 33 @Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
36 private String showText; 34 private String showText;
37 35
@@ -79,12 +77,6 @@ public class BpmSimpleModelNodeVO { @@ -79,12 +77,6 @@ public class BpmSimpleModelNodeVO {
79 @Schema(description = "操作按钮设置", example = "[]") 77 @Schema(description = "操作按钮设置", example = "[]")
80 private List<OperationButtonSetting> buttonsSetting; // 用于审批节点 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,6 +33,9 @@ public class BpmProcessDefinitionRespVO {
33 @Schema(description = "流程分类名字", example = "请假") 33 @Schema(description = "流程分类名字", example = "请假")
34 private String categoryName; 34 private String categoryName;
35 35
  36 + @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  37 + private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
  38 +
36 @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") 39 @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
37 private Integer formType; 40 private Integer formType;
38 @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024") 41 @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")
@@ -59,9 +62,12 @@ public class BpmProcessDefinitionRespVO { @@ -59,9 +62,12 @@ public class BpmProcessDefinitionRespVO {
59 @Schema(description = "BPMN XML") 62 @Schema(description = "BPMN XML")
60 private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回 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 @Schema(description = "BPMN UserTask 用户任务") 71 @Schema(description = "BPMN UserTask 用户任务")
66 @Data 72 @Data
67 public static class UserTask { 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 \ No newline at end of file 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,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
8 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; 8 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
11 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;  
12 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; 11 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
13 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; 12 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
14 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 13 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
@@ -128,15 +127,13 @@ public class BpmProcessInstanceController { @@ -128,15 +127,13 @@ public class BpmProcessInstanceController {
128 processInstance.getProcessDefinitionId()); 127 processInstance.getProcessDefinitionId());
129 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( 128 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
130 processInstance.getProcessDefinitionId()); 129 processInstance.getProcessDefinitionId());
131 - String bpmnXml = BpmnModelUtils.getBpmnXml(  
132 - processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()));  
133 AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); 130 AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
134 DeptRespDTO dept = null; 131 DeptRespDTO dept = null;
135 if (startUser != null && startUser.getDeptId() != null) { 132 if (startUser != null && startUser.getDeptId() != null) {
136 dept = deptApi.getDept(startUser.getDeptId()); 133 dept = deptApi.getDept(startUser.getDeptId());
137 } 134 }
138 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, 135 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance,
139 - processDefinition, processDefinitionInfo, bpmnXml, startUser, dept)); 136 + processDefinition, processDefinitionInfo, startUser, dept));
140 } 137 }
141 138
142 @DeleteMapping("/cancel-by-start-user") 139 @DeleteMapping("/cancel-by-start-user")
@@ -157,14 +154,6 @@ public class BpmProcessInstanceController { @@ -157,14 +154,6 @@ public class BpmProcessInstanceController {
157 return success(true); 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 @GetMapping("/get-approval-detail") 157 @GetMapping("/get-approval-detail")
169 @Operation(summary = "获得审批详情") 158 @Operation(summary = "获得审批详情")
170 @Parameter(name = "id", description = "流程实例的编号", required = true) 159 @Parameter(name = "id", description = "流程实例的编号", required = true)
@@ -173,4 +162,11 @@ public class BpmProcessInstanceController { @@ -173,4 +162,11 @@ public class BpmProcessInstanceController {
173 return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); 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 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 7 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
9 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO; 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO;
10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; 11 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
11 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; 12 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
@@ -28,8 +29,7 @@ import java.util.Map; @@ -28,8 +29,7 @@ import java.util.Map;
28 import java.util.stream.Stream; 29 import java.util.stream.Stream;
29 30
30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 33 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
34 34
35 @Tag(name = "管理后台 - 流程实例抄送") 35 @Tag(name = "管理后台 - 流程实例抄送")
@@ -62,11 +62,15 @@ public class BpmProcessInstanceCopyController { @@ -62,11 +62,15 @@ public class BpmProcessInstanceCopyController {
62 convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); 62 convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
63 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), 63 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
64 copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator())))); 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 MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), 71 MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
69 processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); 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,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
8 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; 8 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
10 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; 10 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
11 -import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;  
12 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 11 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
13 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; 12 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
14 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 13 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -20,7 +19,6 @@ import io.swagger.v3.oas.annotations.Parameter; @@ -20,7 +19,6 @@ import io.swagger.v3.oas.annotations.Parameter;
20 import io.swagger.v3.oas.annotations.tags.Tag; 19 import io.swagger.v3.oas.annotations.tags.Tag;
21 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
22 import jakarta.validation.Valid; 21 import jakarta.validation.Valid;
23 -import org.flowable.bpmn.model.BpmnModel;  
24 import org.flowable.bpmn.model.UserTask; 22 import org.flowable.bpmn.model.UserTask;
25 import org.flowable.engine.history.HistoricProcessInstance; 23 import org.flowable.engine.history.HistoricProcessInstance;
26 import org.flowable.engine.runtime.ProcessInstance; 24 import org.flowable.engine.runtime.ProcessInstance;
@@ -52,8 +50,6 @@ public class BpmTaskController { @@ -52,8 +50,6 @@ public class BpmTaskController {
52 private BpmProcessInstanceService processInstanceService; 50 private BpmProcessInstanceService processInstanceService;
53 @Resource 51 @Resource
54 private BpmFormService formService; 52 private BpmFormService formService;
55 - @Resource  
56 - private BpmProcessDefinitionService bpmProcessDefinitionService;  
57 53
58 @Resource 54 @Resource
59 private AdminUserApi adminUserApi; 55 private AdminUserApi adminUserApi;
@@ -121,27 +117,22 @@ public class BpmTaskController { @@ -121,27 +117,22 @@ public class BpmTaskController {
121 @PreAuthorize("@ss.hasPermission('bpm:task:query')") 117 @PreAuthorize("@ss.hasPermission('bpm:task:query')")
122 public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId( 118 public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
123 @RequestParam("processInstanceId") String processInstanceId) { 119 @RequestParam("processInstanceId") String processInstanceId) {
124 - List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); 120 + List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true);
125 if (CollUtil.isEmpty(taskList)) { 121 if (CollUtil.isEmpty(taskList)) {
126 return success(Collections.emptyList()); 122 return success(Collections.emptyList());
127 } 123 }
128 124
129 // 拼接数据 125 // 拼接数据
130 - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);  
131 - // 获得 User 和 Dept Map  
132 Set<Long> userIds = convertSetByFlatMap(taskList, task -> 126 Set<Long> userIds = convertSetByFlatMap(taskList, task ->
133 Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); 127 Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
134 - userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));  
135 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); 128 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
136 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( 129 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
137 convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); 130 convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
138 // 获得 Form Map 131 // 获得 Form Map
139 Map<Long, BpmFormDO> formMap = formService.getFormMap( 132 Map<Long, BpmFormDO> formMap = formService.getFormMap(
140 convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); 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 @PutMapping("/approve") 138 @PutMapping("/approve")
@@ -161,7 +152,7 @@ public class BpmTaskController { @@ -161,7 +152,7 @@ public class BpmTaskController {
161 } 152 }
162 153
163 @GetMapping("/list-by-return") 154 @GetMapping("/list-by-return")
164 - @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") 155 + @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮")
165 @Parameter(name = "taskId", description = "当前任务ID", required = true) 156 @Parameter(name = "taskId", description = "当前任务ID", required = true)
166 @PreAuthorize("@ss.hasPermission('bpm:task:update')") 157 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
167 public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) { 158 public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) {
@@ -171,7 +162,7 @@ public class BpmTaskController { @@ -171,7 +162,7 @@ public class BpmTaskController {
171 } 162 }
172 163
173 @PutMapping("/return") 164 @PutMapping("/return")
174 - @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") 165 + @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮")
175 @PreAuthorize("@ss.hasPermission('bpm:task:update')") 166 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
176 public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) { 167 public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
177 taskService.returnTask(getLoginUserId(), reqVO); 168 taskService.returnTask(getLoginUserId(), reqVO);
@@ -210,6 +201,14 @@ public class BpmTaskController { @@ -210,6 +201,14 @@ public class BpmTaskController {
210 return success(true); 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 @GetMapping("/list-by-parent-task-id") 212 @GetMapping("/list-by-parent-task-id")
214 @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 213 @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
215 @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) 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 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc; 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 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 5 import lombok.Data;
5 6
@@ -12,31 +13,31 @@ public class BpmProcessInstanceCopyRespVO { @@ -12,31 +13,31 @@ public class BpmProcessInstanceCopyRespVO {
12 @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 13 @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 private Long id; 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 @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233") 19 @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233")
21 private String processInstanceId; 20 private String processInstanceId;
22 - @Schema(description = "流程实例的名称") 21 + @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
23 private String processInstanceName; 22 private String processInstanceName;
24 - @Schema(description = "流程实例的发起时间") 23 + @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
25 private LocalDateTime processInstanceStartTime; 24 private LocalDateTime processInstanceStartTime;
26 25
27 - @Schema(description = "抄送的节点的活动编号") 26 + @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED)
28 private String activityId; 27 private String activityId;
29 - @Schema(description = "发起抄送的任务编号") 28 + @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + private String activityName;
  30 +
  31 + @Schema(description = "流程活动的编号")
30 private String taskId; 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 private LocalDateTime createTime; 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,16 +6,27 @@ import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.AssertTrue; 6 import jakarta.validation.constraints.AssertTrue;
7 import lombok.Data; 7 import lombok.Data;
8 8
9 -// TODO @jason:这个可以简化下,使用 @RequestParam。嘿嘿,主要 VO 项不要太多 9 +import java.util.Map;
  10 +
10 @Schema(description = "管理后台 - 审批详情 Request VO") 11 @Schema(description = "管理后台 - 审批详情 Request VO")
11 @Data 12 @Data
12 public class BpmApprovalDetailReqVO { 13 public class BpmApprovalDetailReqVO {
13 14
14 @Schema(description = "流程定义的编号", example = "1024") 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 @Schema(description = "流程实例的编号", example = "1024") 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 @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") 31 @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
21 @JsonIgnore 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 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; 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 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 8 import lombok.Data;
5 9
6 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
7 import java.util.List; 11 import java.util.List;
  12 +import java.util.Map;
8 13
9 14
10 @Schema(description = "管理后台 - 审批详情 Response VO") 15 @Schema(description = "管理后台 - 审批详情 Response VO")
@@ -14,12 +19,28 @@ public class BpmApprovalDetailRespVO { @@ -14,12 +19,28 @@ public class BpmApprovalDetailRespVO {
14 @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 19 @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
15 private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 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 @Data 42 @Data
22 - public static class ApprovalNodeInfo { 43 + public static class ActivityNode {
23 44
24 @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") 45 @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
25 private String id; 46 private String id;
@@ -39,42 +60,40 @@ public class BpmApprovalDetailRespVO { @@ -39,42 +60,40 @@ public class BpmApprovalDetailRespVO {
39 private LocalDateTime endTime; 60 private LocalDateTime endTime;
40 61
41 @Schema(description = "审批节点的任务信息") 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 @Data 78 @Data
68 - public static class ApprovalTaskInfo { 79 + public static class ActivityNodeTask {
69 80
70 @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 81 @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
71 private String id; 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 @Schema(description = "任务所属人", example = "1024") 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 @Schema(description = "任务分配人", example = "2048") 95 @Schema(description = "任务分配人", example = "2048")
77 - private User assigneeUser; 96 + private UserSimpleBaseVO assigneeUser;
78 97
79 @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 98 @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
80 private Integer status; // 参见 BpmTaskStatusEnum 枚举 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 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; 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 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; 4 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
4 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 6 import lombok.Data;
@@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO { @@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO {
44 /** 45 /**
45 * 发起流程的用户 46 * 发起流程的用户
46 */ 47 */
47 - private User startUser; 48 + private UserSimpleBaseVO startUser;
48 49
49 @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") 50 @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
50 private String processDefinitionId; 51 private String processDefinitionId;
@@ -58,22 +59,6 @@ public class BpmProcessInstanceRespVO { @@ -58,22 +59,6 @@ public class BpmProcessInstanceRespVO {
58 */ 59 */
59 private List<Task> tasks; // 仅在流程实例分页才返回 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 @Schema(description = "流程任务") 62 @Schema(description = "流程任务")
78 @Data 63 @Data
79 public static class Task { 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,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
5 import lombok.Data; 5 import lombok.Data;
6 6
7 -import java.util.Collection;  
8 import java.util.Map; 7 import java.util.Map;
9 8
10 @Schema(description = "管理后台 - 通过流程任务的 Request VO") 9 @Schema(description = "管理后台 - 通过流程任务的 Request VO")
@@ -19,9 +18,6 @@ public class BpmTaskApproveReqVO { @@ -19,9 +18,6 @@ public class BpmTaskApproveReqVO {
19 @NotEmpty(message = "审批意见不能为空") 18 @NotEmpty(message = "审批意见不能为空")
20 private String reason; 19 private String reason;
21 20
22 - @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2")  
23 - private Collection<Long> copyUserIds;  
24 -  
25 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) 21 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
26 private Map<String, Object> variables; 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 package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; 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 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 6 import lombok.Data;
6 7
@@ -33,14 +34,21 @@ public class BpmTaskRespVO { @@ -33,14 +34,21 @@ public class BpmTaskRespVO {
33 @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 34 @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
34 private String reason; 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 @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") 53 @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
46 private String taskDefinitionKey; 54 private String taskDefinitionKey;
@@ -55,7 +63,7 @@ public class BpmTaskRespVO { @@ -55,7 +63,7 @@ public class BpmTaskRespVO {
55 @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 63 @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
56 private String parentTaskId; 64 private String parentTaskId;
57 @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask") 65 @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask")
58 - private List<BpmTaskRespVO> children; 66 + private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务
59 67
60 @Schema(description = "表单编号", example = "1024") 68 @Schema(description = "表单编号", example = "1024")
61 private Long formId; 69 private Long formId;
@@ -67,9 +75,6 @@ public class BpmTaskRespVO { @@ -67,9 +75,6 @@ public class BpmTaskRespVO {
67 private List<String> formFields; 75 private List<String> formFields;
68 @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) 76 @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
69 private Map<String, Object> formVariables; 77 private Map<String, Object> formVariables;
70 - // @芋艿 都改成了 fieldsPermission。 buttonsSetting。和 BpmSimpleModelNodeVO 统一  
71 - @Schema(description = "表单字段权限值")  
72 - private Map<String, String> fieldsPermission;  
73 @Schema(description = "操作按钮设置值") 78 @Schema(description = "操作按钮设置值")
74 private Map<Integer, OperationButtonSetting> buttonsSetting; 79 private Map<Integer, OperationButtonSetting> buttonsSetting;
75 80
@@ -92,7 +97,7 @@ public class BpmTaskRespVO { @@ -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,7 +5,7 @@ import lombok.Data;
5 5
6 import jakarta.validation.constraints.NotEmpty; 6 import jakarta.validation.constraints.NotEmpty;
7 7
8 -@Schema(description = "管理后台 - 回退流程任务的 Request VO") 8 +@Schema(description = "管理后台 - 退回流程任务的 Request VO")
9 @Data 9 @Data
10 public class BpmTaskReturnReqVO { 10 public class BpmTaskReturnReqVO {
11 11
@@ -13,12 +13,12 @@ public class BpmTaskReturnReqVO { @@ -13,12 +13,12 @@ public class BpmTaskReturnReqVO {
13 @NotEmpty(message = "任务编号不能为空") 13 @NotEmpty(message = "任务编号不能为空")
14 private String id; 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 private String targetTaskDefinitionKey; 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 private String reason; 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 package cn.iocoder.yudao.module.bpm.convert.definition; 1 package cn.iocoder.yudao.module.bpm.convert.definition;
2 2
3 import cn.hutool.core.util.ArrayUtil; 3 import cn.hutool.core.util.ArrayUtil;
4 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
5 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 4 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -22,6 +21,7 @@ import org.mapstruct.Mapper; @@ -22,6 +21,7 @@ import org.mapstruct.Mapper;
22 import org.mapstruct.factory.Mappers; 21 import org.mapstruct.factory.Mappers;
23 22
24 import java.util.Collections; 23 import java.util.Collections;
  24 +import java.util.Comparator;
25 import java.util.List; 25 import java.util.List;
26 import java.util.Map; 26 import java.util.Map;
27 27
@@ -37,25 +37,28 @@ public interface BpmModelConvert { @@ -37,25 +37,28 @@ public interface BpmModelConvert {
37 37
38 BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); 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 BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); 47 BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
47 BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; 48 BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
48 BpmCategoryDO category = categoryMap.get(model.getCategory()); 49 BpmCategoryDO category = categoryMap.get(model.getCategory());
49 Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; 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 List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; 53 List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
52 return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); 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 BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); 62 BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
60 BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); 63 BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
61 if (ArrayUtil.isNotEmpty(bpmnBytes)) { 64 if (ArrayUtil.isNotEmpty(bpmnBytes)) {
@@ -112,6 +115,10 @@ public interface BpmModelConvert { @@ -112,6 +115,10 @@ public interface BpmModelConvert {
112 if (vo.getStartUserIds() == null) { 115 if (vo.getStartUserIds() == null) {
113 vo.setStartUserIds(Collections.emptyList()); 116 vo.setStartUserIds(Collections.emptyList());
114 } 117 }
  118 + // 如果为空,兜底处理,使用 createTime 创建时间
  119 + if (vo.getSort() == null) {
  120 + vo.setSort(model.getCreateTime().getTime());
  121 + }
115 return vo; 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,7 +11,6 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
13 import org.flowable.bpmn.model.BpmnModel; 13 import org.flowable.bpmn.model.BpmnModel;
14 -import org.flowable.bpmn.model.UserTask;  
15 import org.flowable.common.engine.impl.db.SuspensionState; 14 import org.flowable.common.engine.impl.db.SuspensionState;
16 import org.flowable.engine.repository.Deployment; 15 import org.flowable.engine.repository.Deployment;
17 import org.flowable.engine.repository.ProcessDefinition; 16 import org.flowable.engine.repository.ProcessDefinition;
@@ -20,6 +19,7 @@ import org.mapstruct.Mapping; @@ -20,6 +19,7 @@ import org.mapstruct.Mapping;
20 import org.mapstruct.MappingTarget; 19 import org.mapstruct.MappingTarget;
21 import org.mapstruct.factory.Mappers; 20 import org.mapstruct.factory.Mappers;
22 21
  22 +import java.util.Comparator;
23 import java.util.List; 23 import java.util.List;
24 import java.util.Map; 24 import java.util.Map;
25 25
@@ -47,7 +47,7 @@ public interface BpmProcessDefinitionConvert { @@ -47,7 +47,7 @@ public interface BpmProcessDefinitionConvert {
47 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap, 47 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap,
48 Map<Long, BpmFormDO> formMap, 48 Map<Long, BpmFormDO> formMap,
49 Map<String, BpmCategoryDO> categoryMap) { 49 Map<String, BpmCategoryDO> categoryMap) {
50 - return CollectionUtils.convertList(list, definition -> { 50 + List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> {
51 Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class); 51 Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
52 BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class); 52 BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class);
53 BpmFormDO form = null; 53 BpmFormDO form = null;
@@ -55,8 +55,11 @@ public interface BpmProcessDefinitionConvert { @@ -55,8 +55,11 @@ public interface BpmProcessDefinitionConvert {
55 form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class); 55 form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
56 } 56 }
57 BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); 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 default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, 65 default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition,
@@ -64,8 +67,7 @@ public interface BpmProcessDefinitionConvert { @@ -64,8 +67,7 @@ public interface BpmProcessDefinitionConvert {
64 BpmProcessDefinitionInfoDO processDefinitionInfo, 67 BpmProcessDefinitionInfoDO processDefinitionInfo,
65 BpmFormDO form, 68 BpmFormDO form,
66 BpmCategoryDO category, 69 BpmCategoryDO category,
67 - BpmnModel bpmnModel,  
68 - List<UserTask> startUserSelectUserTaskList) { 70 + BpmnModel bpmnModel) {
69 BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class); 71 BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
70 respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); 72 respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
71 // Deployment 73 // Deployment
@@ -87,7 +89,6 @@ public interface BpmProcessDefinitionConvert { @@ -87,7 +89,6 @@ public interface BpmProcessDefinitionConvert {
87 // BpmnModel 89 // BpmnModel
88 if (bpmnModel != null) { 90 if (bpmnModel != null) {
89 respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); 91 respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
90 - respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class));  
91 } 92 }
92 return respVO; 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 package cn.iocoder.yudao.module.bpm.convert.task; 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 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
5 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; 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 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; 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 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; 18 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 19 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
11 import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceStatusEvent; 20 import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceStatusEvent;
  21 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
13 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; 23 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
14 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; 24 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
15 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 25 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
16 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 26 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  27 +import org.flowable.bpmn.model.BpmnModel;
17 import org.flowable.engine.history.HistoricProcessInstance; 28 import org.flowable.engine.history.HistoricProcessInstance;
18 import org.flowable.engine.repository.ProcessDefinition; 29 import org.flowable.engine.repository.ProcessDefinition;
19 import org.flowable.engine.runtime.ProcessInstance; 30 import org.flowable.engine.runtime.ProcessInstance;
20 import org.flowable.task.api.Task; 31 import org.flowable.task.api.Task;
  32 +import org.flowable.task.api.history.HistoricTaskInstance;
21 import org.mapstruct.Mapper; 33 import org.mapstruct.Mapper;
22 import org.mapstruct.Mapping; 34 import org.mapstruct.Mapping;
23 import org.mapstruct.MappingTarget; 35 import org.mapstruct.MappingTarget;
24 import org.mapstruct.factory.Mappers; 36 import org.mapstruct.factory.Mappers;
25 37
  38 +import java.util.HashSet;
26 import java.util.List; 39 import java.util.List;
27 import java.util.Map; 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 * 流程实例 Convert 47 * 流程实例 Convert
@@ -55,7 +72,7 @@ public interface BpmProcessInstanceConvert { @@ -55,7 +72,7 @@ public interface BpmProcessInstanceConvert {
55 if (userMap != null) { 72 if (userMap != null) {
56 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); 73 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId()));
57 if (startUser != null) { 74 if (startUser != null) {
58 - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); 75 + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
59 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); 76 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
60 } 77 }
61 } 78 }
@@ -65,20 +82,18 @@ public interface BpmProcessInstanceConvert { @@ -65,20 +82,18 @@ public interface BpmProcessInstanceConvert {
65 82
66 default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, 83 default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance,
67 ProcessDefinition processDefinition, 84 ProcessDefinition processDefinition,
68 - BpmProcessDefinitionInfoDO processDefinitionExt,  
69 - String bpmnXml, 85 + BpmProcessDefinitionInfoDO processDefinitionInfo,
70 AdminUserRespDTO startUser, 86 AdminUserRespDTO startUser,
71 DeptRespDTO dept) { 87 DeptRespDTO dept) {
72 BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); 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 // definition 91 // definition
76 respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); 92 respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
77 - copyTo(processDefinitionExt, respVO.getProcessDefinition());  
78 - respVO.getProcessDefinition().setBpmnXml(bpmnXml); 93 + copyTo(processDefinitionInfo, respVO.getProcessDefinition());
79 // user 94 // user
80 if (startUser != null) { 95 if (startUser != null) {
81 - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); 96 + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
82 if (dept != null) { 97 if (dept != null) {
83 respVO.getStartUser().setDeptName(dept.getName()); 98 respVO.getStartUser().setDeptName(dept.getName());
84 } 99 }
@@ -89,7 +104,7 @@ public interface BpmProcessInstanceConvert { @@ -89,7 +104,7 @@ public interface BpmProcessInstanceConvert {
89 @Mapping(source = "from.id", target = "to.id", ignore = true) 104 @Mapping(source = "from.id", target = "to.id", ignore = true)
90 void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); 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 return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) 108 return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
94 .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); 109 .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
95 } 110 }
@@ -109,4 +124,156 @@ public interface BpmProcessInstanceConvert { @@ -109,4 +124,156 @@ public interface BpmProcessInstanceConvert {
109 .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); 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 package cn.iocoder.yudao.module.bpm.convert.task; 1 package cn.iocoder.yudao.module.bpm.convert.task;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
4 -import cn.hutool.core.util.StrUtil;  
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
12 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; 12 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
13 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;  
14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 13 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
15 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; 14 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
16 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 15 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
17 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 16 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
18 -import org.flowable.bpmn.model.BpmnModel;  
19 import org.flowable.engine.history.HistoricProcessInstance; 17 import org.flowable.engine.history.HistoricProcessInstance;
20 import org.flowable.engine.runtime.ProcessInstance; 18 import org.flowable.engine.runtime.ProcessInstance;
21 import org.flowable.task.api.Task; 19 import org.flowable.task.api.Task;
@@ -28,7 +26,7 @@ import java.util.Date; @@ -28,7 +26,7 @@ import java.util.Date;
28 import java.util.List; 26 import java.util.List;
29 import java.util.Map; 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 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; 30 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
33 31
34 /** 32 /**
@@ -51,7 +49,7 @@ public interface BpmTaskConvert { @@ -51,7 +49,7 @@ public interface BpmTaskConvert {
51 } 49 }
52 taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); 50 taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
53 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); 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,7 +63,7 @@ public interface BpmTaskConvert {
65 // 用户信息 63 // 用户信息
66 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); 64 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
67 if (assignUser != null) { 65 if (assignUser != null) {
68 - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); 66 + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
69 findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); 67 findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
70 } 68 }
71 // 流程实例 69 // 流程实例
@@ -73,7 +71,7 @@ public interface BpmTaskConvert { @@ -73,7 +71,7 @@ public interface BpmTaskConvert {
73 if (processInstance != null) { 71 if (processInstance != null) {
74 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); 72 AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
75 taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); 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 return taskVO; 76 return taskVO;
79 }); 77 });
@@ -81,19 +79,17 @@ public interface BpmTaskConvert { @@ -81,19 +79,17 @@ public interface BpmTaskConvert {
81 } 79 }
82 80
83 default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList, 81 default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList,
84 - HistoricProcessInstance processInstance,  
85 Map<Long, BpmFormDO> formMap, 82 Map<Long, BpmFormDO> formMap,
86 Map<Long, AdminUserRespDTO> userMap, 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 BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); 87 BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
91 Integer taskStatus = FlowableUtils.getTaskStatus(task); 88 Integer taskStatus = FlowableUtils.getTaskStatus(task);
  89 + if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) {
  90 + return null;
  91 + }
92 taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); 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 BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); 94 BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class);
99 if (form != null) { 95 if (form != null) {
@@ -101,35 +97,10 @@ public interface BpmTaskConvert { @@ -101,35 +97,10 @@ public interface BpmTaskConvert {
101 .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); 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 default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList, 106 default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList,
@@ -138,7 +109,7 @@ public interface BpmTaskConvert { @@ -138,7 +109,7 @@ public interface BpmTaskConvert {
138 return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> { 109 return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> {
139 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); 110 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
140 if (assignUser != null) { 111 if (assignUser != null) {
141 - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); 112 + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
142 DeptRespDTO dept = deptMap.get(assignUser.getDeptId()); 113 DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
143 if (dept != null) { 114 if (dept != null) {
144 taskVO.getAssigneeUser().setDeptName(dept.getName()); 115 taskVO.getAssigneeUser().setDeptName(dept.getName());
@@ -146,12 +117,21 @@ public interface BpmTaskConvert { @@ -146,12 +117,21 @@ public interface BpmTaskConvert {
146 } 117 }
147 AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); 118 AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
148 if (ownerUser != null) { 119 if (ownerUser != null) {
149 - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); 120 + taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
150 findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); 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 default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, 135 default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
156 Task task) { 136 Task task) {
157 BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO(); 137 BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
@@ -162,6 +142,42 @@ public interface BpmTaskConvert { @@ -162,6 +142,42 @@ public interface BpmTaskConvert {
162 return reqDTO; 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 * <p> 183 * <p>
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
1 package cn.iocoder.yudao.module.bpm.dal.dataobject.definition; 1 package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
4 import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler; 5 import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
5 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
@@ -122,6 +123,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @@ -122,6 +123,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
122 * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 123 * 目的:如果 false 不可见,则不展示在“发起流程”的列表里
123 */ 124 */
124 private Boolean visible; 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,7 +139,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
134 * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起 139 * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起
135 * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的 140 * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的
136 */ 141 */
137 - @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 142 + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
138 private List<Long> startUserIds; 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,6 +4,8 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import com.baomidou.mybatisplus.annotation.KeySequence; 4 import com.baomidou.mybatisplus.annotation.KeySequence;
5 import com.baomidou.mybatisplus.annotation.TableId; 5 import com.baomidou.mybatisplus.annotation.TableId;
6 import com.baomidou.mybatisplus.annotation.TableName; 6 import com.baomidou.mybatisplus.annotation.TableName;
  7 +import org.flowable.bpmn.model.FlowNode;
  8 +import org.flowable.task.api.history.HistoricTaskInstance;
7 import lombok.AllArgsConstructor; 9 import lombok.AllArgsConstructor;
8 import lombok.Builder; 10 import lombok.Builder;
9 import lombok.Data; 11 import lombok.Data;
@@ -54,23 +56,25 @@ public class BpmProcessInstanceCopyDO extends BaseDO { @@ -54,23 +56,25 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
54 */ 56 */
55 private String category; 57 private String category;
56 /** 58 /**
57 - * 流程活动编号 59 + * 流程活动的编号
58 * <p/> 60 * <p/>
59 - * 对应 BPMN XML 节点编号,用于查询抄送节点的表单字段权限  
60 - * 这里冗余的原因:如果是钉钉易搭的抄送节点 (ServiceTask),使用 taskId 可能查不到对应的 activityId 61 + *
  62 + * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号
  63 + * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId
61 */ 64 */
62 private String activityId; 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,4 +83,9 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
79 */ 83 */
80 private Long userId; 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 package cn.iocoder.yudao.module.bpm.dal.mysql.definition; 1 package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
2 2
  3 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
3 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import org.apache.ibatis.annotations.Mapper; 6 import org.apache.ibatis.annotations.Mapper;
@@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX&lt;BpmProcessDe @@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX&lt;BpmProcessDe
18 return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); 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,7 +53,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
53 @SuppressWarnings("unchecked") 53 @SuppressWarnings("unchecked")
54 Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); 54 Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
55 if (assigneeUserIds == null) { 55 if (assigneeUserIds == null) {
56 - assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); 56 + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
57 execution.setVariable(super.collectionVariable, assigneeUserIds); 57 execution.setVariable(super.collectionVariable, assigneeUserIds);
58 if (CollUtil.isEmpty(assigneeUserIds)) { 58 if (CollUtil.isEmpty(assigneeUserIds)) {
59 // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! 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,7 +46,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
46 @SuppressWarnings("unchecked") 46 @SuppressWarnings("unchecked")
47 Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); 47 Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
48 if (assigneeUserIds == null) { 48 if (assigneeUserIds == null) {
49 - assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); 49 + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
50 execution.setVariable(super.collectionVariable, assigneeUserIds); 50 execution.setVariable(super.collectionVariable, assigneeUserIds);
51 if (CollUtil.isEmpty(assigneeUserIds)) { 51 if (CollUtil.isEmpty(assigneeUserIds)) {
52 // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! 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,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 if (CollUtil.isEmpty(candidateUserIds)) { 61 if (CollUtil.isEmpty(candidateUserIds)) {
62 return null; 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,6 +18,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
18 import com.google.common.annotations.VisibleForTesting; 18 import com.google.common.annotations.VisibleForTesting;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
20 import org.flowable.bpmn.model.BpmnModel; 20 import org.flowable.bpmn.model.BpmnModel;
  21 +import org.flowable.bpmn.model.FlowElement;
21 import org.flowable.bpmn.model.UserTask; 22 import org.flowable.bpmn.model.UserTask;
22 import org.flowable.engine.delegate.DelegateExecution; 23 import org.flowable.engine.delegate.DelegateExecution;
23 import org.flowable.engine.runtime.ProcessInstance; 24 import org.flowable.engine.runtime.ProcessInstance;
@@ -89,32 +90,66 @@ public class BpmTaskCandidateInvoker { @@ -89,32 +90,66 @@ public class BpmTaskCandidateInvoker {
89 * @return 用户编号集合 90 * @return 用户编号集合
90 */ 91 */
91 @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 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 if (ObjectUtils.equalsAny(approveType, 97 if (ObjectUtils.equalsAny(approveType,
96 BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), 98 BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
97 BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { 99 BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
98 return new HashSet<>(); 100 return new HashSet<>();
99 } 101 }
100 102
101 - Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());  
102 - String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());  
103 // 1.1 计算任务的候选人 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 removeDisableUsers(userIds); 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 // 1.2 移除被禁用的用户 141 // 1.2 移除被禁用的用户
107 removeDisableUsers(userIds); 142 removeDisableUsers(userIds);
108 143
109 // 2. 候选人为空时,根据“审批人为空”的配置补充 144 // 2. 候选人为空时,根据“审批人为空”的配置补充
110 if (CollUtil.isEmpty(userIds)) { 145 if (CollUtil.isEmpty(userIds)) {
111 userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) 146 userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
112 - .calculateUsers(execution, param); 147 + .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables);
113 // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! 148 // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
114 } 149 }
115 150
116 // 3. 移除发起人的用户 151 // 3. 移除发起人的用户
117 - removeStartUserIfSkip(execution, userIds); 152 + removeStartUserIfSkip(userIds, flowElement, startUserId);
118 return userIds; 153 return userIds;
119 } 154 }
120 155
@@ -126,7 +161,7 @@ public class BpmTaskCandidateInvoker { @@ -126,7 +161,7 @@ public class BpmTaskCandidateInvoker {
126 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds); 161 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
127 assigneeUserIds.removeIf(id -> { 162 assigneeUserIds.removeIf(id -> {
128 AdminUserRespDTO user = userMap.get(id); 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,25 +170,23 @@ public class BpmTaskCandidateInvoker {
135 * 170 *
136 * 注意:如果只有一个候选人,则不处理,避免无法审批 171 * 注意:如果只有一个候选人,则不处理,避免无法审批
137 * 172 *
138 - * @param execution 执行中的任务  
139 * @param assigneeUserIds 当前分配的候选人 173 * @param assigneeUserIds 当前分配的候选人
  174 + * @param flowElement 当前节点
  175 + * @param startUserId 发起人
140 */ 176 */
141 @VisibleForTesting 177 @VisibleForTesting
142 - void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) { 178 + void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) {
143 if (CollUtil.size(assigneeUserIds) <= 1) { 179 if (CollUtil.size(assigneeUserIds) <= 1) {
144 return; 180 return;
145 } 181 }
146 - Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); 182 + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement);
147 if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { 183 if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
148 return; 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 BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); 190 BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
158 Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); 191 Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
159 BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum); 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 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
2 2
3 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 3 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  4 +import org.flowable.bpmn.model.BpmnModel;
4 import org.flowable.engine.delegate.DelegateExecution; 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 import java.util.Set; 8 import java.util.Set;
9 9
10 /** 10 /**
@@ -42,47 +42,44 @@ public interface BpmTaskCandidateStrategy { @@ -42,47 +42,44 @@ public interface BpmTaskCandidateStrategy {
42 /** 42 /**
43 * 基于候选人参数,获得任务的候选用户们 43 * 基于候选人参数,获得任务的候选用户们
44 * 44 *
  45 + * 注意:实现 calculateUsers 系列方法时,有两种选择:
  46 + * 1. 只重写 calculateUsers 默认方法
  47 + * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法
  48 + *
45 * @param param 执行任务 49 * @param param 执行任务
46 * @return 用户编号集合 50 * @return 用户编号集合
47 */ 51 */
48 default Set<Long> calculateUsers(String param) { 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 * @param execution 执行任务 59 * @param execution 执行任务
56 * @return 用户编号集合 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 * <p> 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 * @param param 节点的参数 73 * @param param 节点的参数
  74 + * @param startUserId 流程发起人编号
  75 + * @param processDefinitionId 流程定义编号
  76 + * @param processVariables 流程变量
73 * @return 用户编号集合 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 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
6 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 6 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
7 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 7 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
8 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 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 import java.util.*; 12 import java.util.*;
11 13
@@ -14,14 +16,12 @@ import java.util.*; @@ -14,14 +16,12 @@ import java.util.*;
14 * 16 *
15 * @author jason 17 * @author jason
16 */ 18 */
17 -public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { 19 +public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
18 20
  21 + @Resource
19 protected DeptApi deptApi; 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 * 获得指定层级的部门负责人,只有第 level 的负责人 27 * 获得指定层级的部门负责人,只有第 level 的负责人
@@ -75,4 +75,17 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTas @@ -75,4 +75,17 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTas
75 return deptLeaderIds; 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 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 4 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 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 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
10 8
  9 +import java.util.List;
11 import java.util.Set; 10 import java.util.Set;
12 11
13 /** 12 /**
@@ -16,11 +15,7 @@ import java.util.Set; @@ -16,11 +15,7 @@ import java.util.Set;
16 * @author jason 15 * @author jason
17 */ 16 */
18 @Component 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 @Override 20 @Override
26 public BpmTaskCandidateStrategyEnum getStrategy() { 21 public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -32,14 +27,19 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs @@ -32,14 +27,19 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs
32 // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级 27 // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级
33 String[] params = param.split("\\|"); 28 String[] params = param.split("\\|");
34 Assert.isTrue(params.length == 2, "参数格式不匹配"); 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 @Override 37 @Override
40 public Set<Long> calculateUsers(String param) { 38 public Set<Long> calculateUsers(String param) {
41 String[] params = param.split("\\|"); 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 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 6 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
7 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 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 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Component;
10 10
11 import java.util.List; 11 import java.util.List;
@@ -19,14 +19,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -19,14 +19,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
19 * @author kyle 19 * @author kyle
20 */ 20 */
21 @Component 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 @Override 27 @Override
32 public BpmTaskCandidateStrategyEnum getStrategy() { 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 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 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 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
6 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 6 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  9 +import jakarta.annotation.Resource;
9 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
10 11
11 import java.util.List; 12 import java.util.List;
@@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
19 * @author kyle 20 * @author kyle
20 */ 21 */
21 @Component 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 @Override 30 @Override
32 public BpmTaskCandidateStrategyEnum getStrategy() { 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 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 4 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
8 -import cn.iocoder.yudao.module.system.api.dept.DeptApi;  
9 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 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 import jakarta.annotation.Resource; 9 import jakarta.annotation.Resource;
  10 +import org.flowable.bpmn.model.BpmnModel;
13 import org.flowable.engine.delegate.DelegateExecution; 11 import org.flowable.engine.delegate.DelegateExecution;
14 import org.flowable.engine.runtime.ProcessInstance; 12 import org.flowable.engine.runtime.ProcessInstance;
15 import org.springframework.context.annotation.Lazy; 13 import org.springframework.context.annotation.Lazy;
16 import org.springframework.stereotype.Component; 14 import org.springframework.stereotype.Component;
17 15
18 import java.util.HashSet; 16 import java.util.HashSet;
  17 +import java.util.Map;
19 import java.util.Set; 18 import java.util.Set;
20 19
21 import static cn.hutool.core.collection.ListUtil.toList; 20 import static cn.hutool.core.collection.ListUtil.toList;
@@ -26,16 +25,12 @@ import static cn.hutool.core.collection.ListUtil.toList; @@ -26,16 +25,12 @@ import static cn.hutool.core.collection.ListUtil.toList;
26 * @author jason 25 * @author jason
27 */ 26 */
28 @Component 27 @Component
29 -public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { 28 +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
30 29
31 @Resource 30 @Resource
32 @Lazy 31 @Lazy
33 private BpmProcessInstanceService processInstanceService; 32 private BpmProcessInstanceService processInstanceService;
34 33
35 - public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {  
36 - super(adminUserApi, deptApi);  
37 - }  
38 -  
39 @Override 34 @Override
40 public BpmTaskCandidateStrategyEnum getStrategy() { 35 public BpmTaskCandidateStrategyEnum getStrategy() {
41 return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI; 36 return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
@@ -43,48 +38,33 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan @@ -43,48 +38,33 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan
43 38
44 @Override 39 @Override
45 public void validateParam(String param) { 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 @Override 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 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); 49 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
54 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); 50 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
55 // 获取发起人的 multi 部门负责人 51 // 获取发起人的 multi 部门负责人
56 - DeptRespDTO dept = getStartUserDept(startUserId); 52 + DeptRespDTO dept = super.getStartUserDept(startUserId);
57 if (dept == null) { 53 if (dept == null) {
58 return new HashSet<>(); 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 @Override 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 if (dept == null) { 64 if (dept == null) {
70 return new HashSet<>(); 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 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 4 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 7 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
8 -import cn.iocoder.yudao.module.system.api.dept.DeptApi;  
9 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 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 import jakarta.annotation.Resource; 9 import jakarta.annotation.Resource;
  10 +import org.flowable.bpmn.model.BpmnModel;
13 import org.flowable.engine.delegate.DelegateExecution; 11 import org.flowable.engine.delegate.DelegateExecution;
14 import org.flowable.engine.runtime.ProcessInstance; 12 import org.flowable.engine.runtime.ProcessInstance;
15 import org.springframework.context.annotation.Lazy; 13 import org.springframework.context.annotation.Lazy;
16 import org.springframework.stereotype.Component; 14 import org.springframework.stereotype.Component;
17 15
18 import java.util.HashSet; 16 import java.util.HashSet;
  17 +import java.util.Map;
19 import java.util.Set; 18 import java.util.Set;
20 19
21 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; 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,7 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
26 * @author jason 25 * @author jason
27 */ 26 */
28 @Component 27 @Component
29 -public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { 28 +public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
30 29
31 @Resource 30 @Resource
32 @Lazy // 避免循环依赖 31 @Lazy // 避免循环依赖
@@ -37,10 +36,6 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat @@ -37,10 +36,6 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat
37 return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; 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 @Override 39 @Override
45 public void validateParam(String param) { 40 public void validateParam(String param) {
46 // 参数是部门的层级 41 // 参数是部门的层级
@@ -48,44 +43,29 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat @@ -48,44 +43,29 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat
48 } 43 }
49 44
50 @Override 45 @Override
51 - public Set<Long> calculateUsers(DelegateExecution execution, String param) { 46 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
52 // 获得流程发起人 47 // 获得流程发起人
53 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); 48 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
54 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); 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 @Override 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 private Set<Long> getStartUserDeptLeader(Long startUserId, String param) { 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 if (dept == null) { 64 if (dept == null) {
72 return new HashSet<>(); 65 return new HashSet<>();
73 } 66 }
74 - Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 67 + Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level);
75 return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); 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 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 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 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
8 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 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 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
11 import org.flowable.bpmn.model.BpmnModel; 13 import org.flowable.bpmn.model.BpmnModel;
  14 +import org.flowable.bpmn.model.ServiceTask;
  15 +import org.flowable.bpmn.model.Task;
12 import org.flowable.bpmn.model.UserTask; 16 import org.flowable.bpmn.model.UserTask;
13 import org.flowable.engine.delegate.DelegateExecution; 17 import org.flowable.engine.delegate.DelegateExecution;
14 import org.flowable.engine.runtime.ProcessInstance; 18 import org.flowable.engine.runtime.ProcessInstance;
@@ -23,16 +27,12 @@ import java.util.*; @@ -23,16 +27,12 @@ import java.util.*;
23 * @author 芋道源码 27 * @author 芋道源码
24 */ 28 */
25 @Component 29 @Component
26 -public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy { 30 +public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
27 31
28 @Resource 32 @Resource
29 @Lazy // 延迟加载,避免循环依赖 33 @Lazy // 延迟加载,避免循环依赖
30 private BpmProcessInstanceService processInstanceService; 34 private BpmProcessInstanceService processInstanceService;
31 35
32 - public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) {  
33 - super(adminUserApi);  
34 - }  
35 -  
36 @Override 36 @Override
37 public BpmTaskCandidateStrategyEnum getStrategy() { 37 public BpmTaskCandidateStrategyEnum getStrategy() {
38 return BpmTaskCandidateStrategyEnum.START_USER_SELECT; 38 return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
@@ -42,7 +42,12 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs @@ -42,7 +42,12 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs
42 public void validateParam(String param) {} 42 public void validateParam(String param) {}
43 43
44 @Override 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 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); 51 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
47 Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); 52 Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
48 Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); 53 Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
@@ -50,47 +55,43 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs @@ -50,47 +55,43 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs
50 execution.getProcessInstanceId()); 55 execution.getProcessInstanceId());
51 // 获得审批人 56 // 获得审批人
52 List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); 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 @Override 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 List<Long> assignees = startUserSelectAssignees.get(activityId); 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 * @param bpmnModel BPMN 模型 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 if (bpmnModel == null) { 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 BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())); 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 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 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,13 +7,15 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
9 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; 9 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
10 -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;  
11 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
  11 +import org.flowable.bpmn.model.BpmnModel;
  12 +import org.flowable.bpmn.model.FlowElement;
12 import org.flowable.engine.delegate.DelegateExecution; 13 import org.flowable.engine.delegate.DelegateExecution;
13 import org.springframework.context.annotation.Lazy; 14 import org.springframework.context.annotation.Lazy;
14 import org.springframework.stereotype.Component; 15 import org.springframework.stereotype.Component;
15 16
16 import java.util.HashSet; 17 import java.util.HashSet;
  18 +import java.util.Map;
17 import java.util.Objects; 19 import java.util.Objects;
18 import java.util.Set; 20 import java.util.Set;
19 21
@@ -23,16 +25,12 @@ import java.util.Set; @@ -23,16 +25,12 @@ import java.util.Set;
23 * @author kyle 25 * @author kyle
24 */ 26 */
25 @Component 27 @Component
26 -public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy { 28 +public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy {
27 29
28 @Resource 30 @Resource
29 @Lazy // 延迟加载,避免循环依赖 31 @Lazy // 延迟加载,避免循环依赖
30 private BpmProcessDefinitionService processDefinitionService; 32 private BpmProcessDefinitionService processDefinitionService;
31 33
32 - public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) {  
33 - super(adminUserApi);  
34 - }  
35 -  
36 @Override 34 @Override
37 public BpmTaskCandidateStrategyEnum getStrategy() { 35 public BpmTaskCandidateStrategyEnum getStrategy() {
38 return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY; 36 return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
@@ -43,19 +41,28 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac @@ -43,19 +41,28 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac
43 } 41 }
44 42
45 @Override 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 if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { 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 if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { 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 return new HashSet<>(processDefinition.getManagerUserIds()); 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 import cn.hutool.core.convert.Convert; 3 import cn.hutool.core.convert.Convert;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 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 import org.flowable.engine.delegate.DelegateExecution; 8 import org.flowable.engine.delegate.DelegateExecution;
9 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Component;
10 10
  11 +import java.util.Map;
11 import java.util.Set; 12 import java.util.Set;
12 13
13 /** 14 /**
@@ -16,11 +17,7 @@ import java.util.Set; @@ -16,11 +17,7 @@ import java.util.Set;
16 * @author 芋道源码 17 * @author 芋道源码
17 */ 18 */
18 @Component 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 @Override 22 @Override
26 public BpmTaskCandidateStrategyEnum getStrategy() { 23 public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -33,11 +30,16 @@ public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstract @@ -33,11 +30,16 @@ public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstract
33 } 30 }
34 31
35 @Override 32 @Override
36 - public Set<Long> calculateUsers(DelegateExecution execution, String param) { 33 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
37 Object result = FlowableUtils.getExpressionValue(execution, param); 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 \ No newline at end of file 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 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; 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 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Component;
10 10
11 import java.util.Collection; 11 import java.util.Collection;
@@ -20,14 +20,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -20,14 +20,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
20 * @author kyle 20 * @author kyle
21 */ 21 */
22 @Component 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 @Override 28 @Override
33 public BpmTaskCandidateStrategyEnum getStrategy() { 29 public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -37,7 +33,7 @@ public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrat @@ -37,7 +33,7 @@ public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrat
37 @Override 33 @Override
38 public void validateParam(String param) { 34 public void validateParam(String param) {
39 Set<Long> groupIds = StrUtils.splitToLongSet(param); 35 Set<Long> groupIds = StrUtils.splitToLongSet(param);
40 - userGroupService.getUserGroupList(groupIds); 36 + userGroupService.validUserGroups(groupIds);
41 } 37 }
42 38
43 @Override 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 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 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 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
6 import cn.iocoder.yudao.module.system.api.dept.PostApi; 6 import cn.iocoder.yudao.module.system.api.dept.PostApi;
7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  9 +import jakarta.annotation.Resource;
9 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
10 11
11 import java.util.List; 12 import java.util.List;
@@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
19 * @author kyle 20 * @author kyle
20 */ 21 */
21 @Component 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 @Override 30 @Override
32 public BpmTaskCandidateStrategyEnum getStrategy() { 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 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 3 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 import cn.iocoder.yudao.module.system.api.permission.PermissionApi; 6 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
7 import cn.iocoder.yudao.module.system.api.permission.RoleApi; 7 import cn.iocoder.yudao.module.system.api.permission.RoleApi;
8 -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;  
9 import jakarta.annotation.Resource; 8 import jakarta.annotation.Resource;
10 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Component;
11 10
@@ -17,17 +16,13 @@ import java.util.Set; @@ -17,17 +16,13 @@ import java.util.Set;
17 * @author kyle 16 * @author kyle
18 */ 17 */
19 @Component 18 @Component
20 -public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy { 19 +public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
21 20
22 @Resource 21 @Resource
23 private RoleApi roleApi; 22 private RoleApi roleApi;
24 @Resource 23 @Resource
25 private PermissionApi permissionApi; 24 private PermissionApi permissionApi;
26 25
27 - public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) {  
28 - super(adminUserApi);  
29 - }  
30 -  
31 @Override 26 @Override
32 public BpmTaskCandidateStrategyEnum getStrategy() { 27 public BpmTaskCandidateStrategyEnum getStrategy() {
33 return BpmTaskCandidateStrategyEnum.ROLE; 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 import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 3 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  4 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
5 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 6 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
6 -import cn.iocoder.yudao.module.system.api.user.AdminUserApi;  
7 import jakarta.annotation.Resource; 7 import jakarta.annotation.Resource;
  8 +import org.flowable.bpmn.model.BpmnModel;
8 import org.flowable.engine.delegate.DelegateExecution; 9 import org.flowable.engine.delegate.DelegateExecution;
9 import org.flowable.engine.runtime.ProcessInstance; 10 import org.flowable.engine.runtime.ProcessInstance;
10 import org.springframework.context.annotation.Lazy; 11 import org.springframework.context.annotation.Lazy;
11 import org.springframework.stereotype.Component; 12 import org.springframework.stereotype.Component;
12 13
  14 +import java.util.Map;
13 import java.util.Set; 15 import java.util.Set;
14 16
15 /** 17 /**
@@ -20,16 +22,12 @@ import java.util.Set; @@ -20,16 +22,12 @@ import java.util.Set;
20 * @author jason 22 * @author jason
21 */ 23 */
22 @Component 24 @Component
23 -public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy { 25 +public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy {
24 26
25 @Resource 27 @Resource
26 @Lazy // 延迟加载,避免循环依赖 28 @Lazy // 延迟加载,避免循环依赖
27 private BpmProcessInstanceService processInstanceService; 29 private BpmProcessInstanceService processInstanceService;
28 30
29 - public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) {  
30 - super(adminUserApi);  
31 - }  
32 -  
33 @Override 31 @Override
34 public BpmTaskCandidateStrategyEnum getStrategy() { 32 public BpmTaskCandidateStrategyEnum getStrategy() {
35 return BpmTaskCandidateStrategyEnum.START_USER; 33 return BpmTaskCandidateStrategyEnum.START_USER;
@@ -45,18 +43,15 @@ public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractS @@ -45,18 +43,15 @@ public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractS
45 } 43 }
46 44
47 @Override 45 @Override
48 - public Set<Long> calculateUsers(DelegateExecution execution, String param) { 46 + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
49 ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); 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 @Override 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 import cn.hutool.core.text.StrPool; 3 import cn.hutool.core.text.StrPool;
4 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 4 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  8 +import jakarta.annotation.Resource;
8 import org.springframework.stereotype.Component; 9 import org.springframework.stereotype.Component;
9 10
10 import java.util.LinkedHashSet; 11 import java.util.LinkedHashSet;
11 -import java.util.Set;  
12 12
13 /** 13 /**
14 * 用户 {@link BpmTaskCandidateStrategy} 实现类 14 * 用户 {@link BpmTaskCandidateStrategy} 实现类
@@ -16,11 +16,10 @@ import java.util.Set; @@ -16,11 +16,10 @@ import java.util.Set;
16 * @author kyle 16 * @author kyle
17 */ 17 */
18 @Component 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 @Override 24 @Override
26 public BpmTaskCandidateStrategyEnum getStrategy() { 25 public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -33,7 +32,7 @@ public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrate @@ -33,7 +32,7 @@ public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrate
33 } 32 }
34 33
35 @Override 34 @Override
36 - public Set<Long> calculateUsers(String param) { 35 + public LinkedHashSet<Long> calculateUsers(String param) {
37 return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); 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,9 +4,10 @@ import org.flowable.common.engine.api.variable.VariableContainer;
4 import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction; 4 import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
5 import org.springframework.stereotype.Component; 5 import org.springframework.stereotype.Component;
6 6
7 -// TODO @jason:这个自定义转换的原因是啥呀?  
8 /** 7 /**
9 - * 根据流程变量 variable 的类型, 转换参数的值 8 + * 根据流程变量 variable 的类型,转换参数的值
  9 + *
  10 + * 目前用于 ConditionNodeConvert 的 buildConditionExpression 方法中
10 * 11 *
11 * @author jason 12 * @author jason
12 */ 13 */
@@ -20,11 +21,12 @@ public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVar @@ -20,11 +21,12 @@ public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVar
20 public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) { 21 public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) {
21 Object variable = variableContainer.getVariable(variableName); 22 Object variable = variableContainer.getVariable(variableName);
22 if (variable != null && parmaValue != null) { 23 if (variable != null && parmaValue != null) {
23 - // 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串 24 + // 如果值不是字符串类型,流程变量的类型是字符串,把值转成字符串
24 if (!(parmaValue instanceof String) && variable instanceof String ) { 25 if (!(parmaValue instanceof String) && variable instanceof String ) {
25 return parmaValue.toString(); 26 return parmaValue.toString();
26 } 27 }
27 } 28 }
28 return parmaValue; 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,6 +29,8 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable {
29 START_USER_DEPT_LEADER(37, "发起人部门负责人"), 29 START_USER_DEPT_LEADER(37, "发起人部门负责人"),
30 START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), 30 START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
31 USER_GROUP(40, "用户组"), 31 USER_GROUP(40, "用户组"),
  32 + FORM_USER(50, "表单内用户字段"),
  33 + FORM_DEPT_LEADER(51, "表单内部门负责人"),
32 EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager 34 EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
33 ASSIGN_EMPTY(1, "审批人为空"), 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,7 +52,7 @@ public interface BpmnModelConstants {
52 */ 52 */
53 String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; 53 String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
54 /** 54 /**
55 - * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id 55 + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的退回的任务 Id
56 */ 56 */
57 String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; 57 String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
58 58
@@ -104,10 +104,6 @@ public interface BpmnModelConstants { @@ -104,10 +104,6 @@ public interface BpmnModelConstants {
104 * BPMN Start Event Node Id 104 * BPMN Start Event Node Id
105 */ 105 */
106 String START_EVENT_NODE_ID = "StartEvent"; 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 * 发起人节点 ID 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,7 +29,12 @@ public class BpmnVariableConstants {
29 * @see ProcessInstance#getProcessVariables() 29 * @see ProcessInstance#getProcessVariables()
30 */ 30 */
31 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; 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 * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} 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,8 +15,8 @@ import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCo
15 15
16 /** 16 /**
17 * 处理抄送用户的 {@link JavaDelegate} 的实现类 17 * 处理抄送用户的 {@link JavaDelegate} 的实现类
18 - *  
19 - * 目前只有快搭模式的【抄送节点】使用 18 + * <p>
  19 + * 目前只有仿钉钉/飞书模式的【抄送节点】使用
20 * 20 *
21 * @author jason 21 * @author jason
22 */ 22 */
@@ -34,14 +34,14 @@ public class BpmCopyTaskDelegate implements JavaDelegate { @@ -34,14 +34,14 @@ public class BpmCopyTaskDelegate implements JavaDelegate {
34 @Override 34 @Override
35 public void execute(DelegateExecution execution) { 35 public void execute(DelegateExecution execution) {
36 // 1. 获得抄送人 36 // 1. 获得抄送人
37 - Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution); 37 + Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution);
38 if (CollUtil.isEmpty(userIds)) { 38 if (CollUtil.isEmpty(userIds)) {
39 return; 39 return;
40 } 40 }
41 // 2. 执行抄送 41 // 2. 执行抄送
42 FlowElement currentFlowElement = execution.getCurrentFlowElement(); 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,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
10 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; 10 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
11 -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;  
12 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; 11 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
13 import com.google.common.collect.ImmutableSet; 12 import com.google.common.collect.ImmutableSet;
14 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
@@ -44,9 +43,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -44,9 +43,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
44 @Resource 43 @Resource
45 @Lazy // 解决循环依赖 44 @Lazy // 解决循环依赖
46 private BpmTaskService taskService; 45 private BpmTaskService taskService;
47 - @Resource  
48 - @Lazy // 解决循环依赖  
49 - private BpmActivityService activityService;  
50 46
51 public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() 47 public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
52 .add(FlowableEngineEventType.TASK_CREATED) 48 .add(FlowableEngineEventType.TASK_CREATED)
@@ -72,7 +68,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -72,7 +68,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
72 68
73 @Override 69 @Override
74 protected void activityCancelled(FlowableActivityCancelledEvent event) { 70 protected void activityCancelled(FlowableActivityCancelledEvent event) {
75 - List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); 71 + List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId());
76 if (CollUtil.isEmpty(activityList)) { 72 if (CollUtil.isEmpty(activityList)) {
77 log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); 73 log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());
78 return; 74 return;
@@ -87,6 +83,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -87,6 +83,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
87 } 83 }
88 84
89 @Override 85 @Override
  86 + @SuppressWarnings("PatternVariableCanBeUsed")
90 protected void timerFired(FlowableEngineEntityEvent event) { 87 protected void timerFired(FlowableEngineEntityEvent event) {
91 // 1.1 只处理 BoundaryEvent 边界计时时间 88 // 1.1 只处理 BoundaryEvent 边界计时时间
92 String processDefinitionId = event.getProcessDefinitionId(); 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,27 +3,116 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
5 import cn.hutool.core.util.ArrayUtil; 5 import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.ObjUtil;
6 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
  8 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 import cn.iocoder.yudao.framework.common.util.string.StrUtils; 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 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; 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 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
11 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 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 import org.flowable.bpmn.converter.BpmnXMLConverter; 20 import org.flowable.bpmn.converter.BpmnXMLConverter;
13 import org.flowable.bpmn.model.Process; 21 import org.flowable.bpmn.model.Process;
14 import org.flowable.bpmn.model.*; 22 import org.flowable.bpmn.model.*;
  23 +import org.flowable.common.engine.api.FlowableException;
15 import org.flowable.common.engine.impl.util.io.BytesStreamSource; 24 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
16 25
17 import java.util.*; 26 import java.util.*;
18 27
19 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
20 import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; 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 public class BpmnModelUtils { 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 public static Integer parseCandidateStrategy(FlowElement userTask) { 116 public static Integer parseCandidateStrategy(FlowElement userTask) {
28 Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( 117 Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
29 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); 118 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
@@ -35,6 +124,12 @@ public class BpmnModelUtils { @@ -35,6 +124,12 @@ public class BpmnModelUtils {
35 return candidateStrategy; 124 return candidateStrategy;
36 } 125 }
37 126
  127 + /**
  128 + * 解析候选人参数
  129 + *
  130 + * @param userTask 任务节点
  131 + * @return 候选人参数
  132 + */
38 public static String parseCandidateParam(FlowElement userTask) { 133 public static String parseCandidateParam(FlowElement userTask) {
39 String candidateParam = userTask.getAttributeValue( 134 String candidateParam = userTask.getAttributeValue(
40 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); 135 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
@@ -45,39 +140,130 @@ public class BpmnModelUtils { @@ -45,39 +140,130 @@ public class BpmnModelUtils {
45 return candidateParam; 140 return candidateParam;
46 } 141 }
47 142
  143 + /**
  144 + * 解析审批类型
  145 + *
  146 + * @see BpmUserTaskApproveTypeEnum
  147 + * @param userTask 任务节点
  148 + * @return 审批类型
  149 + */
48 public static Integer parseApproveType(FlowElement userTask) { 150 public static Integer parseApproveType(FlowElement userTask) {
49 return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE)); 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 public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { 174 public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
53 Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); 175 Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
54 return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); 176 return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
55 } 177 }
56 178
  179 + /**
  180 + * 解析任务拒绝返回任务节点 ID
  181 + *
  182 + * @param flowElement 任务节点
  183 + * @return 任务拒绝返回任务节点 ID
  184 + */
57 public static String parseReturnTaskId(FlowElement flowElement) { 185 public static String parseReturnTaskId(FlowElement flowElement) {
58 return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); 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 public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { 224 public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
62 return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); 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 public static Integer parseAssignEmptyHandlerType(FlowElement userTask) { 234 public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
66 return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE)); 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 public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) { 244 public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) {
70 return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); 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 public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { 267 public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) {
82 if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { 268 if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) {
83 return null; 269 return null;
@@ -101,6 +287,29 @@ public class BpmnModelUtils { @@ -101,6 +287,29 @@ public class BpmnModelUtils {
101 return fieldsPermission; 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 public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { 313 public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) {
105 FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); 314 FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
106 if (flowElement == null) { 315 if (flowElement == null) {
@@ -110,7 +319,7 @@ public class BpmnModelUtils { @@ -110,7 +319,7 @@ public class BpmnModelUtils {
110 if (CollUtil.isEmpty(extensionElements)) { 319 if (CollUtil.isEmpty(extensionElements)) {
111 return null; 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 extensionElements.forEach(element -> { 323 extensionElements.forEach(element -> {
115 String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); 324 String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
116 String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); 325 String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
@@ -124,6 +333,23 @@ public class BpmnModelUtils { @@ -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 * @param source 起始节点 355 * @param source 起始节点
@@ -168,15 +394,14 @@ public class BpmnModelUtils { @@ -168,15 +394,14 @@ public class BpmnModelUtils {
168 * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 394 * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
169 * @return 元素们 395 * @return 元素们
170 */ 396 */
  397 + @SuppressWarnings("unchecked")
171 public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { 398 public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
172 List<T> result = new ArrayList<>(); 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 return result; 405 return result;
181 } 406 }
182 407
@@ -193,7 +418,7 @@ public class BpmnModelUtils { @@ -193,7 +418,7 @@ public class BpmnModelUtils {
193 418
194 public static EndEvent getEndEvent(BpmnModel model) { 419 public static EndEvent getEndEvent(BpmnModel model) {
195 Process process = model.getMainProcess(); 420 Process process = model.getMainProcess();
196 - // 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题 421 + // 从 flowElementList 找 endEvent
197 return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent); 422 return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
198 } 423 }
199 424
@@ -221,7 +446,7 @@ public class BpmnModelUtils { @@ -221,7 +446,7 @@ public class BpmnModelUtils {
221 return StrUtil.utf8Str(bpmnBytes); 446 return StrUtil.utf8Str(bpmnBytes);
222 } 447 }
223 448
224 - // ========== 遍历相关的方法 ========== 449 + // ========== BPMN 复杂遍历相关的方法 ==========
225 450
226 /** 451 /**
227 * 找到 source 节点之前的所有用户任务节点 452 * 找到 source 节点之前的所有用户任务节点
@@ -316,16 +541,16 @@ public class BpmnModelUtils { @@ -316,16 +541,16 @@ public class BpmnModelUtils {
316 return userTaskList; 541 return userTaskList;
317 } 542 }
318 543
319 -  
320 /** 544 /**
321 * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 545 * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
322 - * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 546 + * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况
323 * 547 *
324 * @param source 起始节点 548 * @param source 起始节点
325 * @param target 目标节点 549 * @param target 目标节点
326 * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 550 * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
327 * @return 结果 551 * @return 结果
328 */ 552 */
  553 + @SuppressWarnings("BooleanMethodIsAlwaysInverted")
329 public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { 554 public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
330 visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; 555 visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
331 // 不能是开始事件和子流程 556 // 不能是开始事件和子流程
@@ -436,11 +661,136 @@ public class BpmnModelUtils { @@ -436,11 +661,136 @@ public class BpmnModelUtils {
436 return userTaskList; 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 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2
3 import cn.hutool.core.util.ObjectUtil; 3 import cn.hutool.core.util.ObjectUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 5 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
5 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 6 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; 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,6 +9,8 @@ import org.flowable.common.engine.api.delegate.Expression;
8 import org.flowable.common.engine.api.variable.VariableContainer; 9 import org.flowable.common.engine.api.variable.VariableContainer;
9 import org.flowable.common.engine.impl.el.ExpressionManager; 10 import org.flowable.common.engine.impl.el.ExpressionManager;
10 import org.flowable.common.engine.impl.identity.Authentication; 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 import org.flowable.engine.ProcessEngineConfiguration; 14 import org.flowable.engine.ProcessEngineConfiguration;
12 import org.flowable.engine.history.HistoricProcessInstance; 15 import org.flowable.engine.history.HistoricProcessInstance;
13 import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; 16 import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
@@ -95,15 +98,35 @@ public class FlowableUtils { @@ -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 * @param processInstance 流程实例 124 * @param processInstance 流程实例
101 * @return 表单 125 * @return 表单
102 */ 126 */
103 public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) { 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,9 +148,22 @@ public class FlowableUtils {
125 * @param processInstance 流程实例 148 * @param processInstance 流程实例
126 * @return 发起用户选择的审批人 Map 149 * @return 发起用户选择的审批人 Map
127 */ 150 */
128 - @SuppressWarnings("unchecked")  
129 public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) { 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 BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); 167 BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
132 } 168 }
133 169
@@ -181,13 +217,30 @@ public class FlowableUtils { @@ -181,13 +217,30 @@ public class FlowableUtils {
181 217
182 // ========== Expression 相关的工具方法 ========== 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 ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); 223 ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
188 - assert expressionManager != null; 224 + assert expressionManager!= null;
189 Expression expression = expressionManager.createExpression(expressionString); 225 Expression expression = expressionManager.createExpression(expressionString);
190 return expression.getValue(variableContainer); 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,64 +7,47 @@ import cn.hutool.core.util.*;
7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; 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 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
16 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; 13 import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
17 import org.flowable.bpmn.BpmnAutoLayout; 14 import org.flowable.bpmn.BpmnAutoLayout;
  15 +import org.flowable.bpmn.constants.BpmnXMLConstants;
18 import org.flowable.bpmn.model.Process; 16 import org.flowable.bpmn.model.Process;
19 import org.flowable.bpmn.model.*; 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 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 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 * @author jason 31 * @author jason
41 */ 32 */
42 public class SimpleModelUtils { 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 * @param processId 流程标识 52 * @param processId 流程标识
70 * @param processName 流程名称 53 * @param processName 流程名称
@@ -72,42 +55,76 @@ public class SimpleModelUtils { @@ -72,42 +55,76 @@ public class SimpleModelUtils {
72 * @return Bpmn Model 55 * @return Bpmn Model
73 */ 56 */
74 public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { 57 public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
  58 + // 1. 创建 BpmnModel
75 BpmnModel bpmnModel = new BpmnModel(); 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 Process process = new Process(); 62 Process process = new Process();
81 process.setId(processId); 63 process.setId(processId);
82 process.setName(processName); 64 process.setName(processName);
83 - process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么? 65 + process.setExecutable(Boolean.TRUE);
84 bpmnModel.addProcess(process); 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 startNode.setChildNode(simpleModelNode); 71 startNode.setChildNode(simpleModelNode);
90 - // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process 72 + // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中
91 traverseNodeToBuildFlowNode(startNode, process); 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 traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); 77 traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
97 - // 自动布局 78 +
  79 + // 4. 自动布局
98 new BpmnAutoLayout(bpmnModel).execute(); 80 new BpmnAutoLayout(bpmnModel).execute();
99 return bpmnModel; 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 private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { 128 private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
112 // 1.1 无效节点返回 129 // 1.1 无效节点返回
113 if (!isValidNode(node)) { 130 if (!isValidNode(node)) {
@@ -116,516 +133,555 @@ public class SimpleModelUtils { @@ -116,516 +133,555 @@ public class SimpleModelUtils {
116 // 1.2 END_NODE 直接返回 133 // 1.2 END_NODE 直接返回
117 BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); 134 BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
118 Assert.notNull(nodeType, "模型节点类型不支持"); 135 Assert.notNull(nodeType, "模型节点类型不支持");
119 - if (nodeType == END_NODE) { 136 + if (nodeType == BpmSimpleModelNodeType.END_NODE) {
120 return; 137 return;
121 } 138 }
  139 +
122 // 2.1 情况一:普通节点 140 // 2.1 情况一:普通节点
123 - BpmSimpleModelNodeVO childNode = node.getChildNode();  
124 if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { 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 } else { 143 } else {
150 // 2.2 情况二:分支节点 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 Assert.notEmpty(sourceId, "sourceId 不能为空"); 234 Assert.notEmpty(sourceId, "sourceId 不能为空");
247 Assert.notEmpty(targetId, "targetId 不能为空"); 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 SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); 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 return sequenceFlow; 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 exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); 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 return; 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,4 +82,11 @@ public interface BpmCategoryService {
82 */ 82 */
83 List<BpmCategoryDO> getCategoryListByStatus(Integer status); 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 \ No newline at end of file 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,21 +2,22 @@ package cn.iocoder.yudao.module.bpm.service.definition;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 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 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; 7 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; 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 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; 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 import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper; 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 import java.util.Collection; 16 import java.util.Collection;
18 import java.util.Collections; 17 import java.util.Collections;
19 import java.util.List; 18 import java.util.List;
  19 +import java.util.stream.Collectors;
  20 +import java.util.stream.IntStream;
20 21
21 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
22 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 23 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
@@ -58,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { @@ -58,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
58 private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) { 59 private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) {
59 BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName()); 60 BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName());
60 if (category == null 61 if (category == null
61 - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { 62 + || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
62 return; 63 return;
63 } 64 }
64 throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName()); 65 throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName());
@@ -67,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { @@ -67,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
67 private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) { 68 private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) {
68 BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode()); 69 BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode());
69 if (category == null 70 if (category == null
70 - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { 71 + || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
71 return; 72 return;
72 } 73 }
73 throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode()); 74 throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode());
@@ -110,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { @@ -110,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
110 return bpmCategoryMapper.selectListByStatus(status); 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 \ No newline at end of file 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 package cn.iocoder.yudao.module.bpm.service.definition; 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 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; 3 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 4 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
7 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
@@ -9,20 +7,22 @@ import jakarta.validation.Valid; @@ -9,20 +7,22 @@ import jakarta.validation.Valid;
9 import org.flowable.bpmn.model.BpmnModel; 7 import org.flowable.bpmn.model.BpmnModel;
10 import org.flowable.engine.repository.Model; 8 import org.flowable.engine.repository.Model;
11 9
  10 +import java.util.List;
  11 +
12 /** 12 /**
13 - * Flowable流程模型接口 13 + * 流程模型接口
14 * 14 *
15 * @author yunlongn 15 * @author yunlongn
16 */ 16 */
17 public interface BpmModelService { 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,6 +65,14 @@ public interface BpmModelService {
65 void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); 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 * @param userId 用户编号 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,12 +3,9 @@ package cn.iocoder.yudao.module.bpm.service.definition;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ArrayUtil; 4 import cn.hutool.core.util.ArrayUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 -import cn.iocoder.yudao.framework.common.util.object.PageUtils;  
9 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; 7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; 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 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
13 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
14 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; 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,14 +32,15 @@ import org.springframework.transaction.annotation.Transactional;
35 import org.springframework.validation.annotation.Validated; 32 import org.springframework.validation.annotation.Validated;
36 33
37 import java.util.List; 34 import java.util.List;
  35 +import java.util.Map;
38 import java.util.Objects; 36 import java.util.Objects;
39 37
40 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 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 * @author yunlongn 45 * @author yunlongn
48 * @author 芋道源码 46 * @author 芋道源码
@@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService {
64 private BpmTaskCandidateInvoker taskCandidateInvoker; 62 private BpmTaskCandidateInvoker taskCandidateInvoker;
65 63
66 @Override 64 @Override
67 - public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) { 65 + public List<Model> getModelList(String name) {
68 ModelQuery modelQuery = repositoryService.createModelQuery(); 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 @Override 73 @Override
@@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService {
100 } 83 }
101 84
102 // 2.1 创建流程定义 85 // 2.1 创建流程定义
  86 + createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序
103 Model model = repositoryService.newModel(); 87 Model model = repositoryService.newModel();
104 BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); 88 BpmModelConvert.INSTANCE.copyToModel(model, createReqVO);
105 model.setTenantId(FlowableUtils.getTenantId()); 89 model.setTenantId(FlowableUtils.getTenantId());
@@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService {
120 repositoryService.saveModel(model); 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 private Model validateModelExists(String id) { 135 private Model validateModelExists(String id) {
124 Model model = repositoryService.getModel(id); 136 Model model = repositoryService.getModel(id);
125 if (model == null) { 137 if (model == null) {
@@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService {
139 Model model = validateModelExists(id); 151 Model model = validateModelExists(id);
140 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); 152 BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
141 if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { 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 return model; 156 return model;
145 } 157 }
@@ -158,10 +170,10 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -158,10 +170,10 @@ public class BpmModelServiceImpl implements BpmModelService {
158 // 1.4 校验任务分配规则已配置 170 // 1.4 校验任务分配规则已配置
159 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); 171 taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
160 // 1.5 获取仿钉钉流程设计器模型数据 172 // 1.5 获取仿钉钉流程设计器模型数据
161 - byte[] simpleBytes = getModelSimpleJson(model.getId()); 173 + String simpleJson = getModelSimpleJson(model.getId());
162 174
163 // 2.1 创建流程定义 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 // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 178 // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
167 updateProcessDefinitionSuspended(model.getDeploymentId()); 179 updateProcessDefinitionSuspended(model.getDeploymentId());
@@ -226,8 +238,8 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -226,8 +238,8 @@ public class BpmModelServiceImpl implements BpmModelService {
226 public BpmSimpleModelNodeVO getSimpleModel(String modelId) { 238 public BpmSimpleModelNodeVO getSimpleModel(String modelId) {
227 Model model = validateModelExists(modelId); 239 Model model = validateModelExists(modelId);
228 // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 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 @Override 245 @Override
@@ -240,7 +252,7 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -240,7 +252,7 @@ public class BpmModelServiceImpl implements BpmModelService {
240 // 2.2 保存 Bpmn XML 252 // 2.2 保存 Bpmn XML
241 updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); 253 updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
242 // 2.3 保存 JSON 数据 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,15 +291,21 @@ public class BpmModelServiceImpl implements BpmModelService {
279 repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); 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 return; 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,7 +18,7 @@ import java.util.Set;
18 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 18 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
19 19
20 /** 20 /**
21 - * Flowable流程定义接口 21 + * 流程定义接口
22 * 22 *
23 * @author yunlong.li 23 * @author yunlong.li
24 * @author ZJQ 24 * @author ZJQ
@@ -48,12 +48,12 @@ public interface BpmProcessDefinitionService { @@ -48,12 +48,12 @@ public interface BpmProcessDefinitionService {
48 * @param model 流程模型 48 * @param model 流程模型
49 * @param modelMetaInfo 流程模型元信息 49 * @param modelMetaInfo 流程模型元信息
50 * @param bpmnBytes BPMN XML 字节数组 50 * @param bpmnBytes BPMN XML 字节数组
51 - * @param simpleBytes SIMPLE Model JSON 字节数组 51 + * @param simpleJson SIMPLE Model JSON
52 * @param form 表单 52 * @param form 表单
53 * @return 流程编号 53 * @return 流程编号
54 */ 54 */
55 String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, 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,6 +64,14 @@ public interface BpmProcessDefinitionService {
64 void updateProcessDefinitionState(String id, Integer state); 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 * 获得流程定义对应的 BPMN 75 * 获得流程定义对应的 BPMN
68 * 76 *
69 * @param id 流程定义编号 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,7 +24,6 @@ import org.flowable.engine.repository.ProcessDefinitionQuery;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
25 import org.springframework.validation.annotation.Validated; 25 import org.springframework.validation.annotation.Validated;
26 26
27 -import java.nio.charset.StandardCharsets;  
28 import java.util.*; 27 import java.util.*;
29 28
30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -120,7 +119,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @@ -120,7 +119,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
120 119
121 @Override 120 @Override
122 public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, 121 public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
123 - byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form) { 122 + byte[] bpmnBytes, String simpleJson, BpmFormDO form) {
124 // 创建 Deployment 部署 123 // 创建 Deployment 部署
125 Deployment deploy = repositoryService.createDeployment() 124 Deployment deploy = repositoryService.createDeployment()
126 .key(model.getKey()).name(model.getName()).category(model.getCategory()) 125 .key(model.getKey()).name(model.getName()).category(model.getCategory())
@@ -145,8 +144,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @@ -145,8 +144,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
145 144
146 // 插入拓展表 145 // 插入拓展表
147 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) 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 if (form != null) { 150 if (form != null) {
152 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); 151 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
@@ -173,6 +172,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @@ -173,6 +172,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
173 } 172 }
174 173
175 @Override 174 @Override
  175 + public void updateProcessDefinitionSortByModelId(String modelId, Long sort) {
  176 + processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort));
  177 + }
  178 +
  179 + @Override
176 public BpmnModel getProcessDefinitionBpmnModel(String id) { 180 public BpmnModel getProcessDefinitionBpmnModel(String id) {
177 return repositoryService.getBpmnModel(id); 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,9 +3,10 @@ package cn.iocoder.yudao.module.bpm.service.task;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; 4 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
5 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; 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 import java.util.Collection; 9 import java.util.Collection;
8 -import java.util.Set;  
9 10
10 /** 11 /**
11 * 流程抄送 Service 接口 12 * 流程抄送 Service 接口
@@ -15,24 +16,29 @@ import java.util.Set; @@ -15,24 +16,29 @@ import java.util.Set;
15 public interface BpmProcessInstanceCopyService { 16 public interface BpmProcessInstanceCopyService {
16 17
17 /** 18 /**
18 - * 流程实例的抄送 19 + * 【管理员】流程实例的抄送
19 * 20 *
20 * @param userIds 抄送的用户编号 21 * @param userIds 抄送的用户编号
  22 + * @param reason 抄送意见
21 * @param taskId 流程任务编号 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 * @param userIds 抄送的用户编号 30 * @param userIds 抄送的用户编号
  31 + * @param reason 抄送意见
29 * @param processInstanceId 流程编号 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,14 +49,5 @@ public interface BpmProcessInstanceCopyService {
43 */ 49 */
44 PageResult<BpmProcessInstanceCopyDO> getProcessInstanceCopyPage(Long userId, 50 PageResult<BpmProcessInstanceCopyDO> getProcessInstanceCopyPage(Long userId,
45 BpmProcessInstanceCopyPageReqVO pageReqVO); 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,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.service.task;
2 2
3 import cn.hutool.core.util.ObjectUtil; 3 import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
6 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
7 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; 6 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
8 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper; 7 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper;
@@ -19,7 +18,6 @@ import org.springframework.validation.annotation.Validated; @@ -19,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
19 18
20 import java.util.Collection; 19 import java.util.Collection;
21 import java.util.List; 20 import java.util.List;
22 -import java.util.Set;  
23 21
24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 23 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -49,17 +47,19 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @@ -49,17 +47,19 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
49 private BpmProcessDefinitionService processDefinitionService; 47 private BpmProcessDefinitionService processDefinitionService;
50 48
51 @Override 49 @Override
52 - public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) { 50 + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) {
53 Task task = taskService.getTask(taskId); 51 Task task = taskService.getTask(taskId);
54 if (ObjectUtil.isNull(task)) { 52 if (ObjectUtil.isNull(task)) {
55 throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); 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 @Override 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 // 1.1 校验流程实例存在 63 // 1.1 校验流程实例存在
64 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); 64 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
65 if (processInstance == null) { 65 if (processInstance == null) {
@@ -74,10 +74,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @@ -74,10 +74,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
74 74
75 // 2. 创建抄送流程 75 // 2. 创建抄送流程
76 List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() 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 .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) 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 processInstanceCopyMapper.insertBatch(copyList); 81 processInstanceCopyMapper.insertBatch(copyList);
82 } 82 }
83 83
@@ -87,10 +87,4 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @@ -87,10 +87,4 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
87 return processInstanceCopyMapper.selectPage(userId, pageReqVO); 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,14 +84,6 @@ public interface BpmProcessInstanceService {
84 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, 84 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
85 @Valid BpmProcessInstancePageReqVO pageReqVO); 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 // TODO @芋艿:重点在 review 下 87 // TODO @芋艿:重点在 review 下
96 /** 88 /**
97 * 获取审批详情。 89 * 获取审批详情。
@@ -104,6 +96,14 @@ public interface BpmProcessInstanceService { @@ -104,6 +96,14 @@ public interface BpmProcessInstanceService {
104 */ 96 */
105 BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); 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 // ========== Update 写入相关方法 ========== 107 // ========== Update 写入相关方法 ==========
108 108
109 /** 109 /**
@@ -157,5 +157,4 @@ public interface BpmProcessInstanceService { @@ -157,5 +157,4 @@ public interface BpmProcessInstanceService {
157 */ 157 */
158 void processProcessInstanceCompleted(ProcessInstance instance); 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 \ No newline at end of file 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 \ No newline at end of file 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,7 +6,9 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 import org.flowable.bpmn.model.UserTask; 8 import org.flowable.bpmn.model.UserTask;
  9 +import org.flowable.engine.history.HistoricActivityInstance;
9 import org.flowable.task.api.Task; 10 import org.flowable.task.api.Task;
  11 +import org.flowable.task.api.TaskInfo;
10 import org.flowable.task.api.history.HistoricTaskInstance; 12 import org.flowable.task.api.history.HistoricTaskInstance;
11 13
12 import java.util.Collection; 14 import java.util.Collection;
@@ -33,6 +35,15 @@ public interface BpmTaskService { @@ -33,6 +35,15 @@ public interface BpmTaskService {
33 PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO); 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 * @param userId 用户编号 49 * @param userId 用户编号
@@ -73,9 +84,10 @@ public interface BpmTaskService { @@ -73,9 +84,10 @@ public interface BpmTaskService {
73 * 获得指定流程实例的流程任务列表,包括所有状态的 84 * 获得指定流程实例的流程任务列表,包括所有状态的
74 * 85 *
75 * @param processInstanceId 流程实例的编号 86 * @param processInstanceId 流程实例的编号
  87 + * @param asc 是否升序
76 * @return 流程任务列表 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,22 +117,31 @@ public interface BpmTaskService {
105 * 根据条件查询正在进行中的任务 117 * 根据条件查询正在进行中的任务
106 * 118 *
107 * @param processInstanceId 流程实例编号,不允许为空 119 * @param processInstanceId 流程实例编号,不允许为空
108 - * @param assigned 是否分配了审批人,允许空  
109 - * @param taskDefineKey 任务定义 Key,允许空 120 + * @param assigned 是否分配了审批人,允许空
  121 + * @param taskDefineKey 任务定义 Key,允许空
110 */ 122 */
111 List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, 123 List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId,
112 Boolean assigned, 124 Boolean assigned,
113 String taskDefineKey); 125 String taskDefineKey);
114 126
115 /** 127 /**
116 - * 获取当前任务的可回退的 UserTask 集合 128 + * 获取当前任务的可退回的 UserTask 集合
117 * 129 *
118 * @param id 当前的任务 ID 130 * @param id 当前的任务 ID
119 - * @return 可以回退的节点列表 131 + * @return 可以退回的节点列表
120 */ 132 */
121 List<UserTask> getUserTaskListByReturn(String id); 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 * @param parentTaskId 父任务ID 147 * @param parentTaskId 父任务ID
@@ -129,12 +150,20 @@ public interface BpmTaskService { @@ -129,12 +150,20 @@ public interface BpmTaskService {
129 List<Task> getTaskListByParentTaskId(String parentTaskId); 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 // ========== Update 写入相关方法 ========== 168 // ========== Update 写入相关方法 ==========
140 169
@@ -170,10 +199,10 @@ public interface BpmTaskService { @@ -170,10 +199,10 @@ public interface BpmTaskService {
170 void moveTaskToEnd(String processInstanceId); 199 void moveTaskToEnd(String processInstanceId);
171 200
172 /** 201 /**
173 - * 将任务回退到指定的 targetDefinitionKey 位置 202 + * 将任务退回到指定的 targetDefinitionKey 位置
174 * 203 *
175 * @param userId 用户编号 204 * @param userId 用户编号
176 - * @param reqVO 回退的任务key和当前所在的任务ID 205 + * @param reqVO 退回的任务key和当前所在的任务ID
177 */ 206 */
178 void returnTask(Long userId, BpmTaskReturnReqVO reqVO); 207 void returnTask(Long userId, BpmTaskReturnReqVO reqVO);
179 208
@@ -201,14 +230,22 @@ public interface BpmTaskService { @@ -201,14 +230,22 @@ public interface BpmTaskService {
201 */ 230 */
202 void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); 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 // ========== Event 事件相关方法 ========== 241 // ========== Event 事件相关方法 ==========
205 242
206 /** 243 /**
207 * 处理 Task 创建事件,目前是 244 * 处理 Task 创建事件,目前是
208 - * 245 + * <p>
209 * 1. 更新它的状态为审批中 246 * 1. 更新它的状态为审批中
210 * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 247 * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过
211 - * 248 + * <p>
212 * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 249 * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后
213 * 250 *
214 * @param task 任务实体 251 * @param task 任务实体
@@ -233,8 +270,8 @@ public interface BpmTaskService { @@ -233,8 +270,8 @@ public interface BpmTaskService {
233 * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 270 * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
234 * 271 *
235 * @param processInstanceId 流程示例编号 272 * @param processInstanceId 流程示例编号
236 - * @param taskDefineKey 任务 Key  
237 - * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} 273 + * @param taskDefineKey 任务 Key
  274 + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum}
238 */ 275 */
239 void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); 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,6 +21,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableCon
21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 21 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 22 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
23 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; 23 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
  24 +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
24 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; 25 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
25 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; 26 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
26 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 27 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -38,9 +39,11 @@ import org.flowable.engine.HistoryService; @@ -38,9 +39,11 @@ import org.flowable.engine.HistoryService;
38 import org.flowable.engine.ManagementService; 39 import org.flowable.engine.ManagementService;
39 import org.flowable.engine.RuntimeService; 40 import org.flowable.engine.RuntimeService;
40 import org.flowable.engine.TaskService; 41 import org.flowable.engine.TaskService;
  42 +import org.flowable.engine.history.HistoricActivityInstance;
41 import org.flowable.engine.runtime.ProcessInstance; 43 import org.flowable.engine.runtime.ProcessInstance;
42 import org.flowable.task.api.DelegationState; 44 import org.flowable.task.api.DelegationState;
43 import org.flowable.task.api.Task; 45 import org.flowable.task.api.Task;
  46 +import org.flowable.task.api.TaskInfo;
44 import org.flowable.task.api.TaskQuery; 47 import org.flowable.task.api.TaskQuery;
45 import org.flowable.task.api.history.HistoricTaskInstance; 48 import org.flowable.task.api.history.HistoricTaskInstance;
46 import org.flowable.task.api.history.HistoricTaskInstanceQuery; 49 import org.flowable.task.api.history.HistoricTaskInstanceQuery;
@@ -81,6 +84,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -81,6 +84,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
81 @Resource 84 @Resource
82 private BpmProcessInstanceService processInstanceService; 85 private BpmProcessInstanceService processInstanceService;
83 @Resource 86 @Resource
  87 + private BpmProcessDefinitionService bpmProcessDefinitionService;
  88 + @Resource
84 private BpmProcessInstanceCopyService processInstanceCopyService; 89 private BpmProcessInstanceCopyService processInstanceCopyService;
85 @Resource 90 @Resource
86 private BpmModelService modelService; 91 private BpmModelService modelService;
@@ -117,6 +122,41 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -117,6 +122,41 @@ public class BpmTaskServiceImpl implements BpmTaskService {
117 } 122 }
118 123
119 @Override 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 public PageResult<HistoricTaskInstance> getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) { 160 public PageResult<HistoricTaskInstance> getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) {
121 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() 161 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
122 .finished() // 已完成 162 .finished() // 已完成
@@ -170,16 +210,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -170,16 +210,16 @@ public class BpmTaskServiceImpl implements BpmTaskService {
170 } 210 }
171 211
172 @Override 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 .includeTaskLocalVariables() 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,11 +294,43 @@ public class BpmTaskServiceImpl implements BpmTaskService {
254 if (CollUtil.isEmpty(previousUserList)) { 294 if (CollUtil.isEmpty(previousUserList)) {
255 return Collections.emptyList(); 295 return Collections.emptyList();
256 } 296 }
257 - // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 297 + // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回
258 previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); 298 previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
259 return previousUserList; 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,12 +403,50 @@ public class BpmTaskServiceImpl implements BpmTaskService {
331 } 403 }
332 404
333 @Override 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 // ========== Update 写入相关方法 ========== 452 // ========== Update 写入相关方法 ==========
@@ -352,11 +462,6 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -352,11 +462,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
352 throw exception(PROCESS_INSTANCE_NOT_EXISTS); 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 // 情况一:被委派的任务,不调用 complete 去完成任务 465 // 情况一:被委派的任务,不调用 complete 去完成任务
361 if (DelegationState.PENDING.equals(task.getDelegationState())) { 466 if (DelegationState.PENDING.equals(task.getDelegationState())) {
362 approveDelegateTask(reqVO, task); 467 approveDelegateTask(reqVO, task);
@@ -370,12 +475,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -370,12 +475,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
370 } 475 }
371 476
372 // 情况三:审批普通的任务。大多数情况下,都是这样 477 // 情况三:审批普通的任务。大多数情况下,都是这样
373 - // 3.1 更新 task 状态、原因 478 + // 2.1 更新 task 状态、原因
374 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason()); 479 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason());
375 - // 3.2 添加评论 480 + // 2.2 添加评论
376 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), 481 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
377 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); 482 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
378 - // 3.3 调用 BPM complete 去完成任务 483 + // 2.3 调用 BPM complete 去完成任务
379 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 484 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
380 if (CollUtil.isNotEmpty(reqVO.getVariables())) { 485 if (CollUtil.isNotEmpty(reqVO.getVariables())) {
381 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); 486 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
@@ -519,7 +624,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -519,7 +624,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
519 BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); 624 BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement);
520 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { 625 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
521 String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); 626 String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement);
522 - Assert.notNull(returnTaskId, "回退的节点不能为空"); 627 + Assert.notNull(returnTaskId, "退回的节点不能为空");
523 returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) 628 returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
524 .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); 629 .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
525 return; 630 return;
@@ -563,12 +668,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -563,12 +668,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
563 FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), 668 FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),
564 reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); 669 reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
565 670
566 - // 2. 调用 Flowable 框架的回退逻辑 671 + // 2. 调用 Flowable 框架的退回逻辑
567 returnTask(task, targetElement, reqVO); 672 returnTask(task, targetElement, reqVO);
568 } 673 }
569 674
570 /** 675 /**
571 - * 回退流程节点时,校验目标任务节点是否可回退 676 + * 退回流程节点时,校验目标任务节点是否可退回
572 * 677 *
573 * @param sourceKey 当前任务节点 Key 678 * @param sourceKey 当前任务节点 Key
574 * @param targetKey 目标任务节点 key 679 * @param targetKey 目标任务节点 key
@@ -586,7 +691,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -586,7 +691,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
586 throw exception(TASK_TARGET_NODE_NOT_EXISTS); 691 throw exception(TASK_TARGET_NODE_NOT_EXISTS);
587 } 692 }
588 693
589 - // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 694 + // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回
590 if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { 695 if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
591 throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); 696 throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
592 } 697 }
@@ -594,10 +699,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -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 * @param reqVO 前端参数封装 706 * @param reqVO 前端参数封装
602 */ 707 */
603 public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { 708 public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
@@ -610,9 +715,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -610,9 +715,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
610 List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); 715 List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
611 List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); 716 List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
612 717
613 - // 2. 给当前要被回退的 task 数组,设置回退意见 718 + // 2. 给当前要被退回的 task 数组,设置退回意见
614 taskList.forEach(task -> { 719 taskList.forEach(task -> {
615 - // 需要排除掉,不需要设置回退意见的任务 720 + // 需要排除掉,不需要设置退回意见的任务
616 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { 721 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
617 return; 722 return;
618 } 723 }
@@ -659,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -659,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
659 taskService.setOwner(taskId, task.getAssignee()); 764 taskService.setOwner(taskId, task.getAssignee());
660 // 3.2 执行委派,将任务委派给 delegateUser 765 // 3.2 执行委派,将任务委派给 delegateUser
661 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); 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 @Override 770 @Override
@@ -868,6 +971,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -868,6 +971,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
868 handleParentTaskIfSign(task.getParentTaskId()); 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,8 +1028,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
920 if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) { 1028 if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
921 return; 1029 return;
922 } 1030 }
  1031 + // TODO 芋艿:可以后续优化成 getSelf();
923 // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 1032 // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
924 if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { 1033 if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
  1034 + // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝
  1035 + if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) {
  1036 + return;
  1037 + }
925 if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { 1038 if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
926 SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() 1039 SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
927 .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); 1040 .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
@@ -987,7 +1100,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -987,7 +1100,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
987 } 1100 }
988 // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 1101 // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
989 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { 1102 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
990 - // 判断是否为回退或者驳回:如果是回退或者驳回不走这个策略 1103 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
991 // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 1104 // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
992 Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), 1105 Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
993 String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); 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 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 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 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 10 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 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 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
10 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 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 import org.flowable.bpmn.model.UserTask; 19 import org.flowable.bpmn.model.UserTask;
12 import org.flowable.engine.delegate.DelegateExecution; 20 import org.flowable.engine.delegate.DelegateExecution;
  21 +import org.flowable.engine.runtime.ProcessInstance;
13 import org.junit.jupiter.api.BeforeEach; 22 import org.junit.jupiter.api.BeforeEach;
14 import org.junit.jupiter.api.Test; 23 import org.junit.jupiter.api.Test;
15 import org.mockito.Mock; 24 import org.mockito.Mock;
  25 +import org.mockito.MockedStatic;
16 import org.mockito.Spy; 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 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; 31 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
24 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 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 import static org.junit.jupiter.api.Assertions.assertEquals; 36 import static org.junit.jupiter.api.Assertions.assertEquals;
26 import static org.mockito.ArgumentMatchers.eq; 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 * {@link BpmTaskCandidateInvoker} 的单元测试 41 * {@link BpmTaskCandidateInvoker} 的单元测试
@@ -39,44 +49,207 @@ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest { @@ -39,44 +49,207 @@ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest {
39 @Mock 49 @Mock
40 private AdminUserApi adminUserApi; 50 private AdminUserApi adminUserApi;
41 51
  52 + @Mock
  53 + private BpmProcessInstanceService processInstanceService;
  54 +
42 @Spy 55 @Spy
43 - private BpmTaskCandidateStrategy strategy ; 56 + private BpmTaskCandidateStrategy userStrategy;
  57 + @Mock
  58 + private BpmTaskCandidateAssignEmptyStrategy emptyStrategy;
44 59
45 @Spy 60 @Spy
46 - private List<BpmTaskCandidateStrategy> strategyList ; 61 + private List<BpmTaskCandidateStrategy> strategyList;
47 62
48 @BeforeEach 63 @BeforeEach
49 public void setUp() { 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 taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi); 68 taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi);
53 } 69 }
54 70
  71 + /**
  72 + * 场景:成功计算到候选人,但是移除了发起人的用户
  73 + */
55 @Test 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 @Test 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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 4 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.DeptApi;
5 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 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 import org.junit.jupiter.api.Test; 8 import org.junit.jupiter.api.Test;
8 import org.mockito.InjectMocks; 9 import org.mockito.InjectMocks;
9 import org.mockito.Mock; 10 import org.mockito.Mock;
10 11
11 import java.util.Set; 12 import java.util.Set;
12 13
13 -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;  
14 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 14 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
15 import static java.util.Arrays.asList; 15 import static java.util.Arrays.asList;
16 import static org.junit.jupiter.api.Assertions.assertEquals; 16 import static org.junit.jupiter.api.Assertions.assertEquals;
17 import static org.mockito.ArgumentMatchers.eq; 17 import static org.mockito.ArgumentMatchers.eq;
18 import static org.mockito.Mockito.when; 18 import static org.mockito.Mockito.when;
19 19
20 -@Disabled // TODO 芋艿:临时注释  
21 public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest { 20 public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest {
22 21
23 @InjectMocks 22 @InjectMocks
@@ -29,16 +28,16 @@ public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest @@ -29,16 +28,16 @@ public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest
29 @Test 28 @Test
30 public void testCalculateUsers() { 29 public void testCalculateUsers() {
31 // 准备参数 30 // 准备参数
32 - String param = "1,2"; 31 + String param = "10,20";
33 // mock 方法 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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  5 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
4 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 6 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
5 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 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 import org.junit.jupiter.api.Test; 9 import org.junit.jupiter.api.Test;
8 import org.mockito.InjectMocks; 10 import org.mockito.InjectMocks;
9 import org.mockito.Mock; 11 import org.mockito.Mock;
10 12
11 -import java.util.List;  
12 import java.util.Set; 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 import static org.junit.jupiter.api.Assertions.assertEquals; 17 import static org.junit.jupiter.api.Assertions.assertEquals;
17 import static org.mockito.ArgumentMatchers.eq; 18 import static org.mockito.ArgumentMatchers.eq;
18 import static org.mockito.Mockito.when; 19 import static org.mockito.Mockito.when;
19 20
20 -@Disabled // TODO 芋艿:临时注释  
21 public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest { 21 public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest {
22 22
23 @InjectMocks 23 @InjectMocks
24 private BpmTaskCandidateDeptMemberStrategy strategy; 24 private BpmTaskCandidateDeptMemberStrategy strategy;
25 25
26 @Mock 26 @Mock
  27 + private DeptApi deptApi;
  28 + @Mock
27 private AdminUserApi adminUserApi; 29 private AdminUserApi adminUserApi;
28 30
29 @Test 31 @Test
30 public void testCalculateUsers() { 32 public void testCalculateUsers() {
31 // 准备参数 33 // 准备参数
32 - String param = "11,22"; 34 + String param = "10,20";
33 // mock 方法 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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test;
8 import org.mockito.InjectMocks; 8 import org.mockito.InjectMocks;
9 import org.mockito.MockedStatic; 9 import org.mockito.MockedStatic;
10 10
  11 +import java.util.HashMap;
  12 +import java.util.Map;
11 import java.util.Set; 13 import java.util.Set;
12 14
13 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; 15 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
@@ -22,7 +24,7 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest @@ -22,7 +24,7 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest
22 private BpmTaskCandidateExpressionStrategy strategy; 24 private BpmTaskCandidateExpressionStrategy strategy;
23 25
24 @Test 26 @Test
25 - public void testCalculateUsers() { 27 + public void testCalculateUsersByTask() {
26 try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) { 28 try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
27 // 准备参数 29 // 准备参数
28 String param = "1,2"; 30 String param = "1,2";
@@ -32,7 +34,25 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest @@ -32,7 +34,25 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest
32 .thenReturn(asSet(1L, 2L)); 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 assertEquals(asSet(1L, 2L), results); 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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; 4 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
@@ -36,9 +36,9 @@ public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest { @@ -36,9 +36,9 @@ public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest {
36 when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2)); 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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import cn.iocoder.yudao.module.system.api.dept.PostApi; 4 import cn.iocoder.yudao.module.system.api.dept.PostApi;
@@ -39,9 +39,9 @@ public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest { @@ -39,9 +39,9 @@ public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest {
39 when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(users); 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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import cn.iocoder.yudao.module.system.api.permission.PermissionApi; 4 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
@@ -35,9 +35,9 @@ public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest { @@ -35,9 +35,9 @@ public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest {
35 .thenReturn(asSet(11L, 22L)); 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 }
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategyTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  5 +import org.assertj.core.util.Sets;
  6 +import org.flowable.engine.delegate.DelegateExecution;
  7 +import org.flowable.engine.runtime.ProcessInstance;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.mockito.InjectMocks;
  10 +import org.mockito.Mock;
  11 +
  12 +import java.util.Set;
  13 +
  14 +import static org.junit.jupiter.api.Assertions.assertEquals;
  15 +import static org.mockito.ArgumentMatchers.eq;
  16 +import static org.mockito.Mockito.mock;
  17 +import static org.mockito.Mockito.when;
  18 +
  19 +public class BpmTaskCandidateStartUserStrategyTest extends BaseMockitoUnitTest {
  20 +
  21 + @InjectMocks
  22 + private BpmTaskCandidateStartUserStrategy strategy;
  23 +
  24 + @Mock
  25 + private BpmProcessInstanceService processInstanceService;
  26 +
  27 + @Test
  28 + public void testCalculateUsersByTask() {
  29 + // 准备参数
  30 + String param = "2";
  31 + // mock 方法(获得流程发起人)
  32 + Long startUserId = 1L;
  33 + ProcessInstance processInstance = mock(ProcessInstance.class);
  34 + DelegateExecution execution = mock(DelegateExecution.class);
  35 + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
  36 + when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
  37 +
  38 + // 调用
  39 + Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
  40 + // 断言
  41 + assertEquals(Sets.newLinkedHashSet(startUserId), userIds);
  42 + }
  43 +
  44 + @Test
  45 + public void testCalculateUsersByActivity() {
  46 + // 准备参数
  47 + Long startUserId = 1L;
  48 +
  49 + // 调用
  50 + Set<Long> userIds = strategy.calculateUsersByActivity(null, null, null,
  51 + startUserId, null, null);
  52 + // 断言
  53 + assertEquals(Sets.newLinkedHashSet(startUserId), userIds);
  54 + }
  55 +
  56 +}
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategyTest.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 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import org.junit.jupiter.api.Disabled; 4 import org.junit.jupiter.api.Disabled;
@@ -17,14 +17,15 @@ public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest { @@ -17,14 +17,15 @@ public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest {
17 private BpmTaskCandidateUserStrategy strategy; 17 private BpmTaskCandidateUserStrategy strategy;
18 18
19 @Test 19 @Test
20 - public void testCalculateUsers() { 20 + public void test() {
21 // 准备参数 21 // 准备参数
22 String param = "1,2"; 22 String param = "1,2";
23 23
24 // 调用 24 // 调用
25 - Set<Long> results = strategy.calculateUsers(null, param); 25 + Set<Long> userIds = strategy.calculateUsersByTask(null, param);
26 // 断言 26 // 断言
27 - assertEquals(asSet(1L, 2L), results); 27 + assertEquals(asSet(1L, 2L), userIds);
28 } 28 }
29 29
  30 +
30 } 31 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
@@ -89,7 +89,7 @@ public class UserController { @@ -89,7 +89,7 @@ public class UserController {
89 89
90 @GetMapping("/page") 90 @GetMapping("/page")
91 @Operation(summary = "获得用户分页列表") 91 @Operation(summary = "获得用户分页列表")
92 - @PreAuthorize("@ss.hasPermission('system:user:list')") 92 + @PreAuthorize("@ss.hasPermission('system:user:query')")
93 public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO pageReqVO) { 93 public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO pageReqVO) {
94 // 获得用户分页列表 94 // 获得用户分页列表
95 PageResult<AdminUserDO> pageResult = userService.getUserPage(pageReqVO); 95 PageResult<AdminUserDO> pageResult = userService.getUserPage(pageReqVO);