Commit 62379641fa58a400219bf93bc68468cfdeb22c18
Committed by
Gitee
!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 | 149 | |
| 150 | 150 | ### 工作流程 |
| 151 | 151 | |
| 152 | -| | 功能 | 描述 | | |
| 153 | -|-----|-------|----------------------------------------| | |
| 154 | -| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 | | |
| 155 | -| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 | | |
| 156 | -| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 | | |
| 157 | -| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 | | |
| 158 | -| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 | | |
| 159 | -| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 | | |
| 160 | -| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 | | |
| 152 | +| | 功能 | 描述 | | |
| 153 | +|----|-------|-----------------------------------------| | |
| 154 | +| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 | | |
| 155 | +| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 | | |
| 156 | +| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 | | |
| 157 | +| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 | | |
| 158 | +| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 | | |
| 159 | +| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 | | |
| 160 | +| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 | | |
| 161 | 161 | |
| 162 | 162 |  |
| 163 | 163 | |
| 164 | +| BPMN 设计器 | 钉钉/飞书设计器 | | |
| 165 | +|------------------------------|--------------------------------| | |
| 166 | +|  |  | | |
| 167 | + | |
| 164 | 168 | ### 支付系统 |
| 165 | 169 | |
| 166 | 170 | | | 功能 | 描述 | | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.collection; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.collection.CollectionUtil; |
| 5 | 5 | import cn.hutool.core.util.ArrayUtil; |
| 6 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 6 | 7 | import com.google.common.collect.ImmutableMap; |
| 7 | 8 | |
| 8 | 9 | import java.util.*; |
| ... | ... | @@ -73,6 +74,13 @@ public class CollectionUtils { |
| 73 | 74 | return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); |
| 74 | 75 | } |
| 75 | 76 | |
| 77 | + public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) { | |
| 78 | + if (ArrayUtil.isEmpty(from)) { | |
| 79 | + return new PageResult<>(from.getTotal()); | |
| 80 | + } | |
| 81 | + return new PageResult<>(convertList(from.getList(), func), from.getTotal()); | |
| 82 | + } | |
| 83 | + | |
| 76 | 84 | public static <T, U> List<U> convertListByFlatMap(Collection<T> from, |
| 77 | 85 | Function<T, ? extends Stream<? extends U>> func) { |
| 78 | 86 | if (CollUtil.isEmpty(from)) { |
| ... | ... | @@ -324,7 +332,7 @@ public class CollectionUtils { |
| 324 | 332 | } |
| 325 | 333 | |
| 326 | 334 | public static <T> List<T> newArrayList(List<List<T>> list) { |
| 327 | - return list.stream().flatMap(Collection::stream).collect(Collectors.toList()); | |
| 335 | + return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); | |
| 328 | 336 | } |
| 329 | 337 | |
| 330 | 338 | } |
| 331 | 339 | \ No newline at end of file | ... | ... |
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
| ... | ... | @@ -64,8 +64,10 @@ public abstract class AbstractWebSocketMessageSender implements WebSocketMessage |
| 64 | 64 | sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType); |
| 65 | 65 | } |
| 66 | 66 | if (CollUtil.isEmpty(sessions)) { |
| 67 | - log.info("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]", | |
| 68 | - sessionId, userType, userId, messageType, messageContent); | |
| 67 | + if (log.isDebugEnabled()) { | |
| 68 | + log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]", | |
| 69 | + sessionId, userType, userId, messageType, messageContent); | |
| 70 | + } | |
| 69 | 71 | } |
| 70 | 72 | // 2. 执行发送 |
| 71 | 73 | doSend(sessions, messageType, messageContent); | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
| ... | ... | @@ -23,7 +23,7 @@ public interface ErrorCodeConstants { |
| 23 | 23 | "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); |
| 24 | 24 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); |
| 25 | 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); |
| 26 | - ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程的管理员"); | |
| 26 | + ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); | |
| 27 | 27 | |
| 28 | 28 | // ========== 流程定义 1-009-003-000 ========== |
| 29 | 29 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); |
| ... | ... | @@ -35,8 +35,8 @@ public interface ErrorCodeConstants { |
| 35 | 35 | ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在"); |
| 36 | 36 | ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中"); |
| 37 | 37 | ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); |
| 38 | - ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置"); | |
| 39 | - ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在"); | |
| 38 | + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置"); | |
| 39 | + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在"); | |
| 40 | 40 | ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); |
| 41 | 41 | |
| 42 | 42 | // ========== 流程任务 1-009-005-000 ========== |
| ... | ... | @@ -44,7 +44,7 @@ public interface ErrorCodeConstants { |
| 44 | 44 | ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); |
| 45 | 45 | ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作"); |
| 46 | 46 | ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在"); |
| 47 | - ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); | |
| 47 | + ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退回任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); | |
| 48 | 48 | ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人"); |
| 49 | 49 | ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在"); |
| 50 | 50 | ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在"); | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java
| ... | ... | @@ -18,30 +18,26 @@ import java.util.Objects; |
| 18 | 18 | public enum BpmSimpleModelNodeType implements IntArrayValuable { |
| 19 | 19 | |
| 20 | 20 | // 0 ~ 1 开始和结束 |
| 21 | - START_NODE(0, "startEvent", "开始节点"), | |
| 22 | - END_NODE(1, "endEvent", "结束节点"), | |
| 21 | + START_NODE(0, "开始", "startEvent"), | |
| 22 | + END_NODE(1, "结束", "endEvent"), | |
| 23 | 23 | |
| 24 | 24 | // 10 ~ 49 各种节点 |
| 25 | - START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点。前端的开始节点,Id 固定 | |
| 26 | - APPROVE_NODE(11, "userTask", "审批人节点"), | |
| 27 | - COPY_NODE(12, "serviceTask", "抄送人节点"), | |
| 25 | + START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定 | |
| 26 | + APPROVE_NODE(11, "审批人", "userTask"), | |
| 27 | + COPY_NODE(12, "抄送人", "serviceTask"), | |
| 28 | 28 | |
| 29 | 29 | // 50 ~ 条件分支 |
| 30 | - CONDITION_NODE(50, "sequenceFlow", "条件节点"), // 用于构建流转条件的表达式 | |
| 31 | - CONDITION_BRANCH_NODE(51, " “parallelGateway”", "条件分支节点"), // TODO @jason:是不是改成叫 条件分支? | |
| 32 | - PARALLEL_BRANCH_NODE(52, "exclusiveGateway", "并行分支节点"), // TODO @jason:是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关 | |
| 33 | - INCLUSIVE_BRANCH_NODE(53, "inclusiveGateway", "包容分支节点"), | |
| 34 | - // TODO @jason:建议整合 join,最终只有 条件分支、并行分支、包容分支,三种~ | |
| 35 | - // TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。 | |
| 30 | + CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 | |
| 31 | + CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), | |
| 32 | + PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"), | |
| 33 | + INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"), | |
| 36 | 34 | ; |
| 37 | 35 | |
| 38 | 36 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); |
| 39 | 37 | |
| 40 | - public static final String BPMN_USER_TASK_TYPE = "userTask"; | |
| 41 | - | |
| 42 | 38 | private final Integer type; |
| 43 | - private final String bpmnType; | |
| 44 | 39 | private final String name; |
| 40 | + private final String bpmnType; | |
| 45 | 41 | |
| 46 | 42 | /** |
| 47 | 43 | * 判断是否为分支节点 |
| ... | ... | @@ -54,16 +50,6 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { |
| 54 | 50 | || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type); |
| 55 | 51 | } |
| 56 | 52 | |
| 57 | - /** | |
| 58 | - * 判断是否需要记录的节点 | |
| 59 | - * | |
| 60 | - * @param bpmnType bpmn节点类型 | |
| 61 | - */ | |
| 62 | - public static boolean isRecordNode(String bpmnType) { | |
| 63 | - return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType) | |
| 64 | - || Objects.equals(END_NODE.getBpmnType(), bpmnType); | |
| 65 | - } | |
| 66 | - | |
| 67 | 53 | public static BpmSimpleModelNodeType valueOf(Integer type) { |
| 68 | 54 | return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); |
| 69 | 55 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java
| ... | ... | @@ -16,20 +16,23 @@ import java.util.Arrays; |
| 16 | 16 | @AllArgsConstructor |
| 17 | 17 | public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable { |
| 18 | 18 | |
| 19 | - RANDOM(1, "随机挑选一人审批"), | |
| 20 | - RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) | |
| 21 | - ANY(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人) | |
| 22 | - SEQUENTIAL(4, "依次审批"); // 依次审批 | |
| 19 | + RANDOM(1, "随机挑选一人审批", null), | |
| 20 | + RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例) | |
| 21 | + ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人) | |
| 22 | + SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批 | |
| 23 | 23 | |
| 24 | 24 | /** |
| 25 | 25 | * 审批方式 |
| 26 | 26 | */ |
| 27 | 27 | private final Integer method; |
| 28 | - | |
| 29 | 28 | /** |
| 30 | 29 | * 名字 |
| 31 | 30 | */ |
| 32 | 31 | private final String name; |
| 32 | + /** | |
| 33 | + * 完成表达式 | |
| 34 | + */ | |
| 35 | + private final String completionCondition; | |
| 33 | 36 | |
| 34 | 37 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray(); |
| 35 | 38 | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
| ... | ... | @@ -38,6 +38,10 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { |
| 38 | 38 | return ARRAYS; |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | + public static boolean isRejectStatus(Integer status) { | |
| 42 | + return REJECT.getStatus().equals(status); | |
| 43 | + } | |
| 44 | + | |
| 41 | 45 | public static boolean isProcessEndStatus(Integer status) { |
| 42 | 46 | return ObjectUtils.equalsAny(status, |
| 43 | 47 | APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.enums.task; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ObjUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 4 | 5 | import lombok.AllArgsConstructor; |
| 5 | 6 | import lombok.Getter; |
| ... | ... | @@ -20,7 +21,6 @@ public enum BpmTaskStatusEnum { |
| 20 | 21 | CANCEL(4, "已取消"), |
| 21 | 22 | |
| 22 | 23 | RETURN(5, "已退回"), |
| 23 | - DELEGATE(6, "委派中"), | |
| 24 | 24 | |
| 25 | 25 | /** |
| 26 | 26 | * 使用场景: |
| ... | ... | @@ -45,6 +45,10 @@ public enum BpmTaskStatusEnum { |
| 45 | 45 | */ |
| 46 | 46 | private final String name; |
| 47 | 47 | |
| 48 | + public static boolean isRejectStatus(Integer status) { | |
| 49 | + return REJECT.getStatus().equals(status); | |
| 50 | + } | |
| 51 | + | |
| 48 | 52 | /** |
| 49 | 53 | * 判断该状态是否已经处于 End 最终状态 |
| 50 | 54 | * <p> |
| ... | ... | @@ -59,4 +63,8 @@ public enum BpmTaskStatusEnum { |
| 59 | 63 | RETURN.getStatus(), APPROVING.getStatus()); |
| 60 | 64 | } |
| 61 | 65 | |
| 66 | + public static boolean isCancelStatus(Integer status) { | |
| 67 | + return ObjUtil.equal(status, CANCEL.getStatus()); | |
| 68 | + } | |
| 69 | + | |
| 62 | 70 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
| ... | ... | @@ -9,11 +9,14 @@ public class UserSimpleBaseVO { |
| 9 | 9 | |
| 10 | 10 | @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 11 | 11 | private Long id; |
| 12 | - | |
| 13 | 12 | @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") |
| 14 | 13 | private String nickname; |
| 15 | - | |
| 16 | 14 | @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") |
| 17 | 15 | private String avatar; |
| 18 | 16 | |
| 17 | + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 18 | + private Long deptId; | |
| 19 | + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") | |
| 20 | + private String deptName; | |
| 21 | + | |
| 19 | 22 | } |
| 20 | 23 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmCategoryController.java
| ... | ... | @@ -48,6 +48,15 @@ public class BpmCategoryController { |
| 48 | 48 | return success(true); |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | + @PutMapping("/update-sort-batch") | |
| 52 | + @Operation(summary = "批量更新流程分类的排序") | |
| 53 | + @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3") | |
| 54 | + @PreAuthorize("@ss.hasPermission('bpm:category:update')") | |
| 55 | + public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) { | |
| 56 | + categoryService.updateCategorySortBatch(ids); | |
| 57 | + return success(true); | |
| 58 | + } | |
| 59 | + | |
| 51 | 60 | @DeleteMapping("/delete") |
| 52 | 61 | @Operation(summary = "删除流程分类") |
| 53 | 62 | @Parameter(name = "id", description = "编号", required = true) | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
| ... | ... | @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 6 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 7 | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; |
| 8 | 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 9 | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| ... | ... | @@ -28,7 +26,7 @@ import org.springframework.security.access.prepost.PreAuthorize; |
| 28 | 26 | import org.springframework.validation.annotation.Validated; |
| 29 | 27 | import org.springframework.web.bind.annotation.*; |
| 30 | 28 | |
| 31 | -import java.util.HashSet; | |
| 29 | +import java.util.Collections; | |
| 32 | 30 | import java.util.List; |
| 33 | 31 | import java.util.Map; |
| 34 | 32 | import java.util.Set; |
| ... | ... | @@ -56,38 +54,38 @@ public class BpmModelController { |
| 56 | 54 | @Resource |
| 57 | 55 | private AdminUserApi adminUserApi; |
| 58 | 56 | |
| 59 | - @GetMapping("/page") | |
| 57 | + @GetMapping("/list") | |
| 60 | 58 | @Operation(summary = "获得模型分页") |
| 61 | - public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) { | |
| 62 | - PageResult<Model> pageResult = modelService.getModelPage(pageVO); | |
| 63 | - if (CollUtil.isEmpty(pageResult.getList())) { | |
| 64 | - return success(PageResult.empty(pageResult.getTotal())); | |
| 59 | + @Parameter(name = "name", description = "模型名称", example = "芋艿") | |
| 60 | + public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) { | |
| 61 | + List<Model> list = modelService.getModelList(name); | |
| 62 | + if (CollUtil.isEmpty(list)) { | |
| 63 | + return success(Collections.emptyList()); | |
| 65 | 64 | } |
| 66 | 65 | |
| 67 | - // 拼接数据 | |
| 68 | 66 | // 获得 Form 表单 |
| 69 | - Set<Long> formIds = convertSet(pageResult.getList(), model -> { | |
| 67 | + Set<Long> formIds = convertSet(list, model -> { | |
| 70 | 68 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| 71 | 69 | return metaInfo != null ? metaInfo.getFormId() : null; |
| 72 | 70 | }); |
| 73 | 71 | Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); |
| 74 | 72 | // 获得 Category Map |
| 75 | 73 | Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap( |
| 76 | - convertSet(pageResult.getList(), Model::getCategory)); | |
| 74 | + convertSet(list, Model::getCategory)); | |
| 77 | 75 | // 获得 Deployment Map |
| 78 | - Set<String> deploymentIds = new HashSet<>(); | |
| 79 | - pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId())); | |
| 80 | - Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds); | |
| 76 | + Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap( | |
| 77 | + convertSet(list, Model::getDeploymentId)); | |
| 81 | 78 | // 获得 ProcessDefinition Map |
| 82 | - List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); | |
| 79 | + List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds( | |
| 80 | + deploymentMap.keySet()); | |
| 83 | 81 | Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); |
| 84 | 82 | // 获得 User Map |
| 85 | - Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), model -> { | |
| 83 | + Set<Long> userIds = convertSetByFlatMap(list, model -> { | |
| 86 | 84 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| 87 | 85 | return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); |
| 88 | 86 | }); |
| 89 | 87 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| 90 | - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, | |
| 88 | + return success(BpmModelConvert.INSTANCE.buildModelList(list, | |
| 91 | 89 | formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); |
| 92 | 90 | } |
| 93 | 91 | |
| ... | ... | @@ -111,6 +109,7 @@ public class BpmModelController { |
| 111 | 109 | return success(modelService.createModel(createRetVO)); |
| 112 | 110 | } |
| 113 | 111 | |
| 112 | + | |
| 114 | 113 | @PutMapping("/update") |
| 115 | 114 | @Operation(summary = "修改模型") |
| 116 | 115 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| ... | ... | @@ -119,6 +118,14 @@ public class BpmModelController { |
| 119 | 118 | return success(true); |
| 120 | 119 | } |
| 121 | 120 | |
| 121 | + @PutMapping("/update-sort-batch") | |
| 122 | + @Operation(summary = "批量修改模型排序") | |
| 123 | + @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") | |
| 124 | + public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) { | |
| 125 | + modelService.updateModelSortBatch(getLoginUserId(), ids); | |
| 126 | + return success(true); | |
| 127 | + } | |
| 128 | + | |
| 122 | 129 | @PostMapping("/deploy") |
| 123 | 130 | @Operation(summary = "部署模型") |
| 124 | 131 | @Parameter(name = "id", description = "编号", required = true, example = "1024") | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
| ... | ... | @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConver |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 10 | 10 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 11 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 12 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; | |
| 13 | 12 | import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; |
| 14 | 13 | import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; |
| 15 | 14 | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| ... | ... | @@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 18 | 17 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 19 | 18 | import jakarta.annotation.Resource; |
| 20 | 19 | import org.flowable.bpmn.model.BpmnModel; |
| 21 | -import org.flowable.bpmn.model.UserTask; | |
| 22 | 20 | import org.flowable.engine.repository.Deployment; |
| 23 | 21 | import org.flowable.engine.repository.ProcessDefinition; |
| 24 | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
| ... | ... | @@ -115,9 +113,8 @@ public class BpmProcessDefinitionController { |
| 115 | 113 | } |
| 116 | 114 | BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); |
| 117 | 115 | BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); |
| 118 | - List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); | |
| 119 | 116 | return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( |
| 120 | - processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList)); | |
| 117 | + processDefinition, null, processDefinitionInfo, null, null, bpmnModel)); | |
| 121 | 118 | } |
| 122 | 119 | |
| 123 | 120 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
| ... | ... | @@ -15,7 +15,9 @@ import java.util.List; |
| 15 | 15 | * BPM 流程 MetaInfo Response DTO |
| 16 | 16 | * 主要用于 { Model#setMetaInfo(String)} 的存储 |
| 17 | 17 | * |
| 18 | - * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 | |
| 18 | + * 最终,它的字段和 | |
| 19 | + * {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} | |
| 20 | + * 是一致的 | |
| 19 | 21 | * |
| 20 | 22 | * @author 芋道源码 |
| 21 | 23 | */ |
| ... | ... | @@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO { |
| 40 | 42 | @NotNull(message = "表单类型不能为空") |
| 41 | 43 | private Integer formType; |
| 42 | 44 | @Schema(description = "表单编号", example = "1024") |
| 43 | - private Long formId; // formType 为 NORMAL 使用,必须非空 | |
| 44 | - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", | |
| 45 | - example = "/bpm/oa/leave/create") | |
| 46 | - private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 | |
| 47 | - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", | |
| 48 | - example = "/bpm/oa/leave/view") | |
| 49 | - private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 | |
| 45 | + private Long formId; // formType 为 NORMAL 使用,必须非空 | |
| 46 | + @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create") | |
| 47 | + private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 | |
| 48 | + @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view") | |
| 49 | + private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 | |
| 50 | 50 | |
| 51 | 51 | @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| 52 | 52 | @NotNull(message = "是否可见不能为空") |
| ... | ... | @@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO { |
| 59 | 59 | @NotEmpty(message = "可管理用户编号数组不能为空") |
| 60 | 60 | private List<Long> managerUserIds; |
| 61 | 61 | |
| 62 | + @Schema(description = "排序", example = "1") | |
| 63 | + private Long sort; // 创建时,后端自动生成 | |
| 64 | + | |
| 62 | 65 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | -import lombok.Data; | |
| 6 | -import lombok.EqualsAndHashCode; | |
| 7 | -import lombok.ToString; | |
| 8 | - | |
| 9 | - | |
| 10 | -@Schema(description = "管理后台 - 流程模型分页 Request VO") | |
| 11 | -@Data | |
| 12 | -public class BpmModelPageReqVO extends PageParam { | |
| 13 | - | |
| 14 | - @Schema(description = "标识,精准匹配", example = "process1641042089407") | |
| 15 | - private String key; | |
| 16 | - | |
| 17 | - @Schema(description = "名字,模糊匹配", example = "芋道") | |
| 18 | - private String name; | |
| 19 | - | |
| 20 | - @Schema(description = "流程分类", example = "1") | |
| 21 | - private String category; | |
| 22 | - | |
| 23 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
| ... | ... | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.enums.definition.*; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 6 | -import com.fasterxml.jackson.annotation.JsonIgnore; | |
| 7 | 6 | import com.fasterxml.jackson.annotation.JsonInclude; |
| 8 | 7 | import io.swagger.v3.oas.annotations.media.Schema; |
| 9 | 8 | import jakarta.validation.Valid; |
| ... | ... | @@ -31,7 +30,6 @@ public class BpmSimpleModelNodeVO { |
| 31 | 30 | @Schema(description = "模型节点名称", example = "领导审批") |
| 32 | 31 | private String name; |
| 33 | 32 | |
| 34 | - // TODO @jason:和 gpt 大模型对了下这个字段的命名,貌似叫 displayText 合适点。可以等最后我们全局替换下。(优先级:低) | |
| 35 | 33 | @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") |
| 36 | 34 | private String showText; |
| 37 | 35 | |
| ... | ... | @@ -79,12 +77,6 @@ public class BpmSimpleModelNodeVO { |
| 79 | 77 | @Schema(description = "操作按钮设置", example = "[]") |
| 80 | 78 | private List<OperationButtonSetting> buttonsSetting; // 用于审批节点 |
| 81 | 79 | |
| 82 | - // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到 | |
| 83 | - /** | |
| 84 | - * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 | |
| 85 | - */ | |
| 86 | - @JsonIgnore | |
| 87 | - private String attachNodeId; | |
| 88 | 80 | /** |
| 89 | 81 | * 审批节点拒绝处理 |
| 90 | 82 | */ | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
| ... | ... | @@ -33,6 +33,9 @@ public class BpmProcessDefinitionRespVO { |
| 33 | 33 | @Schema(description = "流程分类名字", example = "请假") |
| 34 | 34 | private String categoryName; |
| 35 | 35 | |
| 36 | + @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") | |
| 37 | + private Integer modelType; // 参见 BpmModelTypeEnum 枚举类 | |
| 38 | + | |
| 36 | 39 | @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") |
| 37 | 40 | private Integer formType; |
| 38 | 41 | @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024") |
| ... | ... | @@ -59,9 +62,12 @@ public class BpmProcessDefinitionRespVO { |
| 59 | 62 | @Schema(description = "BPMN XML") |
| 60 | 63 | private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回 |
| 61 | 64 | |
| 62 | - @Schema(description = "发起用户需要选择审批人的任务数组") | |
| 63 | - private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回 | |
| 65 | + @Schema(description = "SIMPLE 设计器模型数据 json 格式") | |
| 66 | + private String simpleModel; // 非必须返回 | |
| 64 | 67 | |
| 68 | + @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 69 | + private Long sort; | |
| 70 | + | |
| 65 | 71 | @Schema(description = "BPMN UserTask 用户任务") |
| 66 | 72 | @Data |
| 67 | 73 | public static class UserTask { | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.controller.admin.task; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; | |
| 5 | -import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert; | |
| 6 | -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; | |
| 7 | -import io.swagger.v3.oas.annotations.tags.Tag; | |
| 8 | -import io.swagger.v3.oas.annotations.Parameter; | |
| 9 | -import io.swagger.v3.oas.annotations.Operation; | |
| 10 | -import org.springframework.security.access.prepost.PreAuthorize; | |
| 11 | -import org.springframework.validation.annotation.Validated; | |
| 12 | -import org.springframework.web.bind.annotation.GetMapping; | |
| 13 | -import org.springframework.web.bind.annotation.RequestMapping; | |
| 14 | -import org.springframework.web.bind.annotation.RequestParam; | |
| 15 | -import org.springframework.web.bind.annotation.RestController; | |
| 16 | - | |
| 17 | -import jakarta.annotation.Resource; | |
| 18 | -import java.util.List; | |
| 19 | - | |
| 20 | -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 21 | - | |
| 22 | -@Tag(name = "管理后台 - 流程活动实例") | |
| 23 | -@RestController | |
| 24 | -@RequestMapping("/bpm/activity") | |
| 25 | -@Validated | |
| 26 | -public class BpmActivityController { | |
| 27 | - | |
| 28 | - @Resource | |
| 29 | - private BpmActivityService activityService; | |
| 30 | - | |
| 31 | - @GetMapping("/list") | |
| 32 | - @Operation(summary = "生成指定流程实例的高亮流程图", | |
| 33 | - description = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成") | |
| 34 | - @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true) | |
| 35 | - @PreAuthorize("@ss.hasPermission('bpm:task:query')") | |
| 36 | - public CommonResult<List<BpmActivityRespVO>> getActivityList( | |
| 37 | - @RequestParam("processInstanceId") String processInstanceId) { | |
| 38 | - return success(BpmActivityConvert.INSTANCE.convertList(activityService.getActivityListByProcessInstanceId(processInstanceId))); | |
| 39 | - } | |
| 40 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http
0 → 100644
| 1 | +### 请求 /bpm/process-instance/get-bpmn 接口 => 成功 | |
| 2 | +GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 | |
| 3 | +Content-Type: application/json | |
| 4 | +tenant-id: 1 | |
| 5 | +Authorization: Bearer {{token}} | |
| 6 | + | |
| 7 | +### 请求 /bpm/process-instance/get-bpmn 接口 => 失败 | |
| 8 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 | |
| 9 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911 | |
| 10 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4 | |
| 11 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932 | |
| 12 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b | |
| 13 | +GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b | |
| 14 | +Content-Type: application/json | |
| 15 | +tenant-id: 1 | |
| 16 | +Authorization: Bearer {{token}} | |
| 0 | 17 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
| ... | ... | @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 10 | 10 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 11 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 12 | 11 | import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; |
| 13 | 12 | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| 14 | 13 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| ... | ... | @@ -128,15 +127,13 @@ public class BpmProcessInstanceController { |
| 128 | 127 | processInstance.getProcessDefinitionId()); |
| 129 | 128 | BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( |
| 130 | 129 | processInstance.getProcessDefinitionId()); |
| 131 | - String bpmnXml = BpmnModelUtils.getBpmnXml( | |
| 132 | - processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId())); | |
| 133 | 130 | AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 134 | 131 | DeptRespDTO dept = null; |
| 135 | 132 | if (startUser != null && startUser.getDeptId() != null) { |
| 136 | 133 | dept = deptApi.getDept(startUser.getDeptId()); |
| 137 | 134 | } |
| 138 | 135 | return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, |
| 139 | - processDefinition, processDefinitionInfo, bpmnXml, startUser, dept)); | |
| 136 | + processDefinition, processDefinitionInfo, startUser, dept)); | |
| 140 | 137 | } |
| 141 | 138 | |
| 142 | 139 | @DeleteMapping("/cancel-by-start-user") |
| ... | ... | @@ -157,14 +154,6 @@ public class BpmProcessInstanceController { |
| 157 | 154 | return success(true); |
| 158 | 155 | } |
| 159 | 156 | |
| 160 | - @GetMapping("/get-form-fields-permission") | |
| 161 | - @Operation(summary = "获得表单字段权限") | |
| 162 | - @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") | |
| 163 | - public CommonResult<Map<String, String>> getFormFieldsPermission( | |
| 164 | - @Valid BpmFormFieldsPermissionReqVO reqVO) { | |
| 165 | - return success(processInstanceService.getFormFieldsPermission(reqVO)); | |
| 166 | - } | |
| 167 | - | |
| 168 | 157 | @GetMapping("/get-approval-detail") |
| 169 | 158 | @Operation(summary = "获得审批详情") |
| 170 | 159 | @Parameter(name = "id", description = "流程实例的编号", required = true) |
| ... | ... | @@ -173,4 +162,11 @@ public class BpmProcessInstanceController { |
| 173 | 162 | return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); |
| 174 | 163 | } |
| 175 | 164 | |
| 165 | + @GetMapping("/get-bpmn-model-view") | |
| 166 | + @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") | |
| 167 | + @Parameter(name = "id", description = "流程实例的编号", required = true) | |
| 168 | + public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) { | |
| 169 | + return success(processInstanceService.getProcessInstanceBpmnModelView(id)); | |
| 170 | + } | |
| 171 | + | |
| 176 | 172 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 9 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO; |
| 10 | 11 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; |
| 11 | 12 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| ... | ... | @@ -28,8 +29,7 @@ import java.util.Map; |
| 28 | 29 | import java.util.stream.Stream; |
| 29 | 30 | |
| 30 | 31 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 31 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; | |
| 32 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 32 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | |
| 33 | 33 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 34 | 34 | |
| 35 | 35 | @Tag(name = "管理后台 - 流程实例抄送") |
| ... | ... | @@ -62,11 +62,15 @@ public class BpmProcessInstanceCopyController { |
| 62 | 62 | convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); |
| 63 | 63 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), |
| 64 | 64 | copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator())))); |
| 65 | - return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> { | |
| 66 | - MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname())); | |
| 67 | - MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname())); | |
| 65 | + return success(convertPage(pageResult, copy -> { | |
| 66 | + BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class); | |
| 67 | + MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()), | |
| 68 | + user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class))); | |
| 69 | + MapUtils.findAndThen(userMap, copy.getStartUserId(), | |
| 70 | + user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class))); | |
| 68 | 71 | MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), |
| 69 | 72 | processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); |
| 73 | + return copyVO; | |
| 70 | 74 | })); |
| 71 | 75 | } |
| 72 | 76 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
| ... | ... | @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 10 | 10 | import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; |
| 11 | -import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; | |
| 12 | 11 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 13 | 12 | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| 14 | 13 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| ... | ... | @@ -20,7 +19,6 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 20 | 19 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 21 | 20 | import jakarta.annotation.Resource; |
| 22 | 21 | import jakarta.validation.Valid; |
| 23 | -import org.flowable.bpmn.model.BpmnModel; | |
| 24 | 22 | import org.flowable.bpmn.model.UserTask; |
| 25 | 23 | import org.flowable.engine.history.HistoricProcessInstance; |
| 26 | 24 | import org.flowable.engine.runtime.ProcessInstance; |
| ... | ... | @@ -52,8 +50,6 @@ public class BpmTaskController { |
| 52 | 50 | private BpmProcessInstanceService processInstanceService; |
| 53 | 51 | @Resource |
| 54 | 52 | private BpmFormService formService; |
| 55 | - @Resource | |
| 56 | - private BpmProcessDefinitionService bpmProcessDefinitionService; | |
| 57 | 53 | |
| 58 | 54 | @Resource |
| 59 | 55 | private AdminUserApi adminUserApi; |
| ... | ... | @@ -121,27 +117,22 @@ public class BpmTaskController { |
| 121 | 117 | @PreAuthorize("@ss.hasPermission('bpm:task:query')") |
| 122 | 118 | public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId( |
| 123 | 119 | @RequestParam("processInstanceId") String processInstanceId) { |
| 124 | - List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); | |
| 120 | + List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true); | |
| 125 | 121 | if (CollUtil.isEmpty(taskList)) { |
| 126 | 122 | return success(Collections.emptyList()); |
| 127 | 123 | } |
| 128 | 124 | |
| 129 | 125 | // 拼接数据 |
| 130 | - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId); | |
| 131 | - // 获得 User 和 Dept Map | |
| 132 | 126 | Set<Long> userIds = convertSetByFlatMap(taskList, task -> |
| 133 | 127 | Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); |
| 134 | - userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); | |
| 135 | 128 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| 136 | 129 | Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( |
| 137 | 130 | convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); |
| 138 | 131 | // 获得 Form Map |
| 139 | 132 | Map<Long, BpmFormDO> formMap = formService.getFormMap( |
| 140 | 133 | convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); |
| 141 | - // 获得 BpmnModel | |
| 142 | - BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); | |
| 143 | - return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, | |
| 144 | - formMap, userMap, deptMap, bpmnModel)); | |
| 134 | + return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, | |
| 135 | + formMap, userMap, deptMap)); | |
| 145 | 136 | } |
| 146 | 137 | |
| 147 | 138 | @PutMapping("/approve") |
| ... | ... | @@ -161,7 +152,7 @@ public class BpmTaskController { |
| 161 | 152 | } |
| 162 | 153 | |
| 163 | 154 | @GetMapping("/list-by-return") |
| 164 | - @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") | |
| 155 | + @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮") | |
| 165 | 156 | @Parameter(name = "taskId", description = "当前任务ID", required = true) |
| 166 | 157 | @PreAuthorize("@ss.hasPermission('bpm:task:update')") |
| 167 | 158 | public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) { |
| ... | ... | @@ -171,7 +162,7 @@ public class BpmTaskController { |
| 171 | 162 | } |
| 172 | 163 | |
| 173 | 164 | @PutMapping("/return") |
| 174 | - @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") | |
| 165 | + @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮") | |
| 175 | 166 | @PreAuthorize("@ss.hasPermission('bpm:task:update')") |
| 176 | 167 | public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) { |
| 177 | 168 | taskService.returnTask(getLoginUserId(), reqVO); |
| ... | ... | @@ -210,6 +201,14 @@ public class BpmTaskController { |
| 210 | 201 | return success(true); |
| 211 | 202 | } |
| 212 | 203 | |
| 204 | + @PutMapping("/copy") | |
| 205 | + @Operation(summary = "抄送任务") | |
| 206 | + @PreAuthorize("@ss.hasPermission('bpm:task:update')") | |
| 207 | + public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) { | |
| 208 | + taskService.copyTask(getLoginUserId(), reqVO); | |
| 209 | + return success(true); | |
| 210 | + } | |
| 211 | + | |
| 213 | 212 | @GetMapping("/list-by-parent-task-id") |
| 214 | 213 | @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 |
| 215 | 214 | @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 5 | import lombok.Data; |
| 5 | 6 | |
| ... | ... | @@ -12,31 +13,31 @@ public class BpmProcessInstanceCopyRespVO { |
| 12 | 13 | @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 13 | 14 | private Long id; |
| 14 | 15 | |
| 15 | - @Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") | |
| 16 | - private Long startUserId; | |
| 17 | - @Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") | |
| 18 | - private String startUserName; | |
| 16 | + @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 17 | + private UserSimpleBaseVO startUser; | |
| 19 | 18 | |
| 20 | 19 | @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233") |
| 21 | 20 | private String processInstanceId; |
| 22 | - @Schema(description = "流程实例的名称") | |
| 21 | + @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试") | |
| 23 | 22 | private String processInstanceName; |
| 24 | - @Schema(description = "流程实例的发起时间") | |
| 23 | + @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 25 | 24 | private LocalDateTime processInstanceStartTime; |
| 26 | 25 | |
| 27 | - @Schema(description = "抄送的节点的活动编号") | |
| 26 | + @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 28 | 27 | private String activityId; |
| 29 | - @Schema(description = "发起抄送的任务编号") | |
| 28 | + @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 29 | + private String activityName; | |
| 30 | + | |
| 31 | + @Schema(description = "流程活动的编号") | |
| 30 | 32 | private String taskId; |
| 31 | - @Schema(description = "发起抄送的任务名称") | |
| 32 | - private String taskName; | |
| 33 | 33 | |
| 34 | - @Schema(description = "抄送人") | |
| 35 | - private String creator; | |
| 36 | - @Schema(description = "抄送人昵称") | |
| 37 | - private String creatorName; | |
| 34 | + @Schema(description = "抄送人意见") | |
| 35 | + private String reason; | |
| 36 | + | |
| 37 | + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 38 | + private UserSimpleBaseVO createUser; | |
| 38 | 39 | |
| 39 | - @Schema(description = "抄送时间") | |
| 40 | + @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 40 | 41 | private LocalDateTime createTime; |
| 41 | 42 | |
| 42 | 43 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
| ... | ... | @@ -6,16 +6,27 @@ import io.swagger.v3.oas.annotations.media.Schema; |
| 6 | 6 | import jakarta.validation.constraints.AssertTrue; |
| 7 | 7 | import lombok.Data; |
| 8 | 8 | |
| 9 | -// TODO @jason:这个可以简化下,使用 @RequestParam。嘿嘿,主要 VO 项不要太多 | |
| 9 | +import java.util.Map; | |
| 10 | + | |
| 10 | 11 | @Schema(description = "管理后台 - 审批详情 Request VO") |
| 11 | 12 | @Data |
| 12 | 13 | public class BpmApprovalDetailReqVO { |
| 13 | 14 | |
| 14 | 15 | @Schema(description = "流程定义的编号", example = "1024") |
| 15 | - private String processDefinitionId; | |
| 16 | + private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID | |
| 17 | + | |
| 18 | + @Schema(description = "流程变量") | |
| 19 | + private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测 | |
| 16 | 20 | |
| 17 | 21 | @Schema(description = "流程实例的编号", example = "1024") |
| 18 | - private String processInstanceId; | |
| 22 | + private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID | |
| 23 | + | |
| 24 | + // TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。 | |
| 25 | + @Schema(description = "流程活动编号", example = "StartUserNode") | |
| 26 | + private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限; | |
| 27 | + | |
| 28 | + @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") | |
| 29 | + private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限 | |
| 19 | 30 | |
| 20 | 31 | @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") |
| 21 | 32 | @JsonIgnore | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 4 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; | |
| 5 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | |
| 6 | +import com.fasterxml.jackson.annotation.JsonIgnore; | |
| 3 | 7 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 8 | import lombok.Data; |
| 5 | 9 | |
| 6 | 10 | import java.time.LocalDateTime; |
| 7 | 11 | import java.util.List; |
| 12 | +import java.util.Map; | |
| 8 | 13 | |
| 9 | 14 | |
| 10 | 15 | @Schema(description = "管理后台 - 审批详情 Response VO") |
| ... | ... | @@ -14,12 +19,28 @@ public class BpmApprovalDetailRespVO { |
| 14 | 19 | @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 15 | 20 | private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 |
| 16 | 21 | |
| 17 | - @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 18 | - private List<ApprovalNodeInfo> approveNodes; | |
| 22 | + @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 23 | + private List<ActivityNode> activityNodes; | |
| 19 | 24 | |
| 20 | - @Schema(description = "审批节点信息") | |
| 25 | + @Schema(description = "表单字段权限") | |
| 26 | + private Map<String, String> formFieldsPermission; | |
| 27 | + | |
| 28 | + @Schema(description = "待办任务") | |
| 29 | + private BpmTaskRespVO todoTask; | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 所属流程定义信息 | |
| 33 | + */ | |
| 34 | + private BpmProcessDefinitionRespVO processDefinition; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 所属流程实例信息 | |
| 38 | + */ | |
| 39 | + private BpmProcessInstanceRespVO processInstance; | |
| 40 | + | |
| 41 | + @Schema(description = "活动节点信息") | |
| 21 | 42 | @Data |
| 22 | - public static class ApprovalNodeInfo { | |
| 43 | + public static class ActivityNode { | |
| 23 | 44 | |
| 24 | 45 | @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") |
| 25 | 46 | private String id; |
| ... | ... | @@ -39,42 +60,40 @@ public class BpmApprovalDetailRespVO { |
| 39 | 60 | private LocalDateTime endTime; |
| 40 | 61 | |
| 41 | 62 | @Schema(description = "审批节点的任务信息") |
| 42 | - private List<ApprovalTaskInfo> tasks; | |
| 63 | + private List<ActivityNodeTask> tasks; | |
| 43 | 64 | |
| 44 | - @Schema(description = "候选人用户列表") | |
| 45 | - // TODO @jason:candidateUserList => candidateUsers,保持和 tasks 的命名风格一致哈 | |
| 46 | - private List<User> candidateUserList; // 用于未运行任务节点 | |
| 65 | + @Schema(description = "候选人策略", example = "35") | |
| 66 | + private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选 | |
| 47 | 67 | |
| 48 | - } | |
| 68 | + @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") | |
| 69 | + @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers | |
| 70 | + private List<Long> candidateUserIds; | |
| 49 | 71 | |
| 50 | - // TODO @jason:可以替换成 UserSimpleBaseVO。简化下 | |
| 51 | - @Schema(description = "用户信息") | |
| 52 | - @Data | |
| 53 | - public static class User { | |
| 54 | - | |
| 55 | - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 56 | - private Long id; | |
| 57 | - | |
| 58 | - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") | |
| 59 | - private String nickname; | |
| 60 | - | |
| 61 | - @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") | |
| 62 | - private String avatar; | |
| 72 | + @Schema(description = "候选人用户列表") | |
| 73 | + private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 | |
| 63 | 74 | |
| 64 | 75 | } |
| 65 | 76 | |
| 66 | - @Schema(description = "审批任务信息") | |
| 77 | + @Schema(description = "活动节点的任务信息") | |
| 67 | 78 | @Data |
| 68 | - public static class ApprovalTaskInfo { | |
| 79 | + public static class ActivityNodeTask { | |
| 69 | 80 | |
| 70 | 81 | @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 71 | 82 | private String id; |
| 72 | 83 | |
| 84 | + @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") | |
| 85 | + @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser | |
| 86 | + private Long owner; | |
| 87 | + | |
| 73 | 88 | @Schema(description = "任务所属人", example = "1024") |
| 74 | - private User ownerUser; | |
| 89 | + private UserSimpleBaseVO ownerUser; | |
| 90 | + | |
| 91 | + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") | |
| 92 | + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser | |
| 93 | + private Long assignee; | |
| 75 | 94 | |
| 76 | 95 | @Schema(description = "任务分配人", example = "2048") |
| 77 | - private User assigneeUser; | |
| 96 | + private UserSimpleBaseVO assigneeUser; | |
| 78 | 97 | |
| 79 | 98 | @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 80 | 99 | private Integer status; // 参见 BpmTaskStatusEnum 枚举 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; | |
| 2 | - | |
| 3 | -import cn.hutool.core.util.StrUtil; | |
| 4 | -import com.fasterxml.jackson.annotation.JsonIgnore; | |
| 5 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 6 | -import jakarta.validation.constraints.AssertTrue; | |
| 7 | -import lombok.Data; | |
| 8 | - | |
| 9 | -@Schema(description = "管理后台 - 表单字段权限 Request VO") | |
| 10 | -@Data | |
| 11 | -public class BpmFormFieldsPermissionReqVO { | |
| 12 | - | |
| 13 | - @Schema(description = "流程定义的编号", example = "1024") | |
| 14 | - private String processDefinitionId; | |
| 15 | - | |
| 16 | - @Schema(description = "流程实例的编号", example = "1024") | |
| 17 | - private String processInstanceId; | |
| 18 | - | |
| 19 | - @Schema(description = "流程活动编号", example = "StartUserNode") | |
| 20 | - private String activityId; // 对应 BPMN XML 节点 Id | |
| 21 | - | |
| 22 | - @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") | |
| 23 | - private String taskId; // UserTask 对应的Id | |
| 24 | - | |
| 25 | - @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") | |
| 26 | - @JsonIgnore | |
| 27 | - public boolean isValidProcessParam() { | |
| 28 | - return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId); | |
| 29 | - } | |
| 30 | - | |
| 31 | - @AssertTrue(message = "流程活动编号和流程任务编号编号不能同时为空") | |
| 32 | - @JsonIgnore | |
| 33 | - public boolean isValidActivityParam() { | |
| 34 | - return StrUtil.isNotEmpty(activityId) || StrUtil.isNotEmpty(taskId); | |
| 35 | - } | |
| 36 | - | |
| 37 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 4 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | |
| 5 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | +import java.util.Set; | |
| 10 | + | |
| 11 | +@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO") | |
| 12 | +@Data | |
| 13 | +public class BpmProcessInstanceBpmnModelViewRespVO { | |
| 14 | + | |
| 15 | + // ========== 基本信息 ========== | |
| 16 | + | |
| 17 | + @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 18 | + private BpmProcessInstanceRespVO processInstance; | |
| 19 | + | |
| 20 | + @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 21 | + private List<BpmTaskRespVO> tasks; | |
| 22 | + | |
| 23 | + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 24 | + private String bpmnXml; | |
| 25 | + | |
| 26 | + @Schema(description = "SIMPLE 模型") | |
| 27 | + private BpmSimpleModelNodeVO simpleModel; | |
| 28 | + | |
| 29 | + // ========== 进度信息 ========== | |
| 30 | + | |
| 31 | + @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 32 | + private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask | |
| 33 | + | |
| 34 | + @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 35 | + private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow | |
| 36 | + | |
| 37 | + @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 38 | + private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow | |
| 39 | + | |
| 40 | + @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 41 | + private Set<String> rejectedTaskActivityIds; // 只包括 UserTask | |
| 42 | + | |
| 43 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 3 | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 6 | import lombok.Data; |
| ... | ... | @@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO { |
| 44 | 45 | /** |
| 45 | 46 | * 发起流程的用户 |
| 46 | 47 | */ |
| 47 | - private User startUser; | |
| 48 | + private UserSimpleBaseVO startUser; | |
| 48 | 49 | |
| 49 | 50 | @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") |
| 50 | 51 | private String processDefinitionId; |
| ... | ... | @@ -58,22 +59,6 @@ public class BpmProcessInstanceRespVO { |
| 58 | 59 | */ |
| 59 | 60 | private List<Task> tasks; // 仅在流程实例分页才返回 |
| 60 | 61 | |
| 61 | - @Schema(description = "用户信息") | |
| 62 | - @Data | |
| 63 | - public static class User { | |
| 64 | - | |
| 65 | - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 66 | - private Long id; | |
| 67 | - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") | |
| 68 | - private String nickname; | |
| 69 | - | |
| 70 | - @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 71 | - private Long deptId; | |
| 72 | - @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") | |
| 73 | - private String deptName; | |
| 74 | - | |
| 75 | - } | |
| 76 | - | |
| 77 | 62 | @Schema(description = "流程任务") |
| 78 | 63 | @Data |
| 79 | 64 | public static class Task { | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
| ... | ... | @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 4 | import jakarta.validation.constraints.NotEmpty; |
| 5 | 5 | import lombok.Data; |
| 6 | 6 | |
| 7 | -import java.util.Collection; | |
| 8 | 7 | import java.util.Map; |
| 9 | 8 | |
| 10 | 9 | @Schema(description = "管理后台 - 通过流程任务的 Request VO") |
| ... | ... | @@ -19,9 +18,6 @@ public class BpmTaskApproveReqVO { |
| 19 | 18 | @NotEmpty(message = "审批意见不能为空") |
| 20 | 19 | private String reason; |
| 21 | 20 | |
| 22 | - @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2") | |
| 23 | - private Collection<Long> copyUserIds; | |
| 24 | - | |
| 25 | 21 | @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) |
| 26 | 22 | private Map<String, Object> variables; |
| 27 | 23 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotEmpty; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +import java.util.Collection; | |
| 8 | + | |
| 9 | +@Schema(description = "管理后台 - 抄送流程任务的 Request VO") | |
| 10 | +@Data | |
| 11 | +public class BpmTaskCopyReqVO { | |
| 12 | + | |
| 13 | + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 14 | + @NotEmpty(message = "任务编号不能为空") | |
| 15 | + private String id; | |
| 16 | + | |
| 17 | + @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]") | |
| 18 | + @NotEmpty(message = "抄送用户不能为空") | |
| 19 | + private Collection<Long> copyUserIds; | |
| 20 | + | |
| 21 | + @Schema(description = "抄送意见", example = "帮忙看看!") | |
| 22 | + private String reason; | |
| 23 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | |
| 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 4 | +import com.fasterxml.jackson.annotation.JsonIgnore; | |
| 4 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 6 | import lombok.Data; |
| 6 | 7 | |
| ... | ... | @@ -33,14 +34,21 @@ public class BpmTaskRespVO { |
| 33 | 34 | @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 34 | 35 | private String reason; |
| 35 | 36 | |
| 37 | + @Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") | |
| 38 | + @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser | |
| 39 | + private Long owner; | |
| 36 | 40 | /** |
| 37 | 41 | * 负责人的用户信息 |
| 38 | 42 | */ |
| 39 | - private BpmProcessInstanceRespVO.User ownerUser; | |
| 43 | + private UserSimpleBaseVO ownerUser; | |
| 44 | + | |
| 45 | + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") | |
| 46 | + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser | |
| 47 | + private Long assignee; | |
| 40 | 48 | /** |
| 41 | 49 | * 审核的用户信息 |
| 42 | 50 | */ |
| 43 | - private BpmProcessInstanceRespVO.User assigneeUser; | |
| 51 | + private UserSimpleBaseVO assigneeUser; | |
| 44 | 52 | |
| 45 | 53 | @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") |
| 46 | 54 | private String taskDefinitionKey; |
| ... | ... | @@ -55,7 +63,7 @@ public class BpmTaskRespVO { |
| 55 | 63 | @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 56 | 64 | private String parentTaskId; |
| 57 | 65 | @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask") |
| 58 | - private List<BpmTaskRespVO> children; | |
| 66 | + private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务 | |
| 59 | 67 | |
| 60 | 68 | @Schema(description = "表单编号", example = "1024") |
| 61 | 69 | private Long formId; |
| ... | ... | @@ -67,9 +75,6 @@ public class BpmTaskRespVO { |
| 67 | 75 | private List<String> formFields; |
| 68 | 76 | @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) |
| 69 | 77 | private Map<String, Object> formVariables; |
| 70 | - // @芋艿 都改成了 fieldsPermission。 buttonsSetting。和 BpmSimpleModelNodeVO 统一 | |
| 71 | - @Schema(description = "表单字段权限值") | |
| 72 | - private Map<String, String> fieldsPermission; | |
| 73 | 78 | @Schema(description = "操作按钮设置值") |
| 74 | 79 | private Map<Integer, OperationButtonSetting> buttonsSetting; |
| 75 | 80 | |
| ... | ... | @@ -92,7 +97,7 @@ public class BpmTaskRespVO { |
| 92 | 97 | /** |
| 93 | 98 | * 发起人的用户信息 |
| 94 | 99 | */ |
| 95 | - private BpmProcessInstanceRespVO.User startUser; | |
| 100 | + private UserSimpleBaseVO startUser; | |
| 96 | 101 | |
| 97 | 102 | } |
| 98 | 103 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java
| ... | ... | @@ -5,7 +5,7 @@ import lombok.Data; |
| 5 | 5 | |
| 6 | 6 | import jakarta.validation.constraints.NotEmpty; |
| 7 | 7 | |
| 8 | -@Schema(description = "管理后台 - 回退流程任务的 Request VO") | |
| 8 | +@Schema(description = "管理后台 - 退回流程任务的 Request VO") | |
| 9 | 9 | @Data |
| 10 | 10 | public class BpmTaskReturnReqVO { |
| 11 | 11 | |
| ... | ... | @@ -13,12 +13,12 @@ public class BpmTaskReturnReqVO { |
| 13 | 13 | @NotEmpty(message = "任务编号不能为空") |
| 14 | 14 | private String id; |
| 15 | 15 | |
| 16 | - @Schema(description = "回退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 17 | - @NotEmpty(message = "回退到的任务 Key 不能为空") | |
| 16 | + @Schema(description = "退回到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 17 | + @NotEmpty(message = "退回到的任务 Key 不能为空") | |
| 18 | 18 | private String targetTaskDefinitionKey; |
| 19 | 19 | |
| 20 | - @Schema(description = "回退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回") | |
| 21 | - @NotEmpty(message = "回退意见不能为空") | |
| 20 | + @Schema(description = "退回意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回") | |
| 21 | + @NotEmpty(message = "退回意见不能为空") | |
| 22 | 22 | private String reason; |
| 23 | 23 | |
| 24 | 24 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.convert.definition; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.ArrayUtil; |
| 4 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | 4 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 6 | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| ... | ... | @@ -22,6 +21,7 @@ import org.mapstruct.Mapper; |
| 22 | 21 | import org.mapstruct.factory.Mappers; |
| 23 | 22 | |
| 24 | 23 | import java.util.Collections; |
| 24 | +import java.util.Comparator; | |
| 25 | 25 | import java.util.List; |
| 26 | 26 | import java.util.Map; |
| 27 | 27 | |
| ... | ... | @@ -37,25 +37,28 @@ public interface BpmModelConvert { |
| 37 | 37 | |
| 38 | 38 | BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); |
| 39 | 39 | |
| 40 | - default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult, | |
| 41 | - Map<Long, BpmFormDO> formMap, | |
| 42 | - Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap, | |
| 43 | - Map<String, ProcessDefinition> processDefinitionMap, | |
| 44 | - Map<Long, AdminUserRespDTO> userMap) { | |
| 45 | - List<BpmModelRespVO> list = convertList(pageResult.getList(), model -> { | |
| 40 | + default List<BpmModelRespVO> buildModelList(List<Model> list, | |
| 41 | + Map<Long, BpmFormDO> formMap, | |
| 42 | + Map<String, BpmCategoryDO> categoryMap, | |
| 43 | + Map<String, Deployment> deploymentMap, | |
| 44 | + Map<String, ProcessDefinition> processDefinitionMap, | |
| 45 | + Map<Long, AdminUserRespDTO> userMap) { | |
| 46 | + List<BpmModelRespVO> result = convertList(list, model -> { | |
| 46 | 47 | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); |
| 47 | 48 | BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; |
| 48 | 49 | BpmCategoryDO category = categoryMap.get(model.getCategory()); |
| 49 | 50 | Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; |
| 50 | - ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; | |
| 51 | + ProcessDefinition processDefinition = model.getDeploymentId() != null ? | |
| 52 | + processDefinitionMap.get(model.getDeploymentId()) : null; | |
| 51 | 53 | List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; |
| 52 | 54 | return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); |
| 53 | 55 | }); |
| 54 | - return new PageResult<>(list, pageResult.getTotal()); | |
| 56 | + // 排序 | |
| 57 | + result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort)); | |
| 58 | + return result; | |
| 55 | 59 | } |
| 56 | 60 | |
| 57 | - default BpmModelRespVO buildModel(Model model, | |
| 58 | - byte[] bpmnBytes) { | |
| 61 | + default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) { | |
| 59 | 62 | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); |
| 60 | 63 | BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); |
| 61 | 64 | if (ArrayUtil.isNotEmpty(bpmnBytes)) { |
| ... | ... | @@ -112,6 +115,10 @@ public interface BpmModelConvert { |
| 112 | 115 | if (vo.getStartUserIds() == null) { |
| 113 | 116 | vo.setStartUserIds(Collections.emptyList()); |
| 114 | 117 | } |
| 118 | + // 如果为空,兜底处理,使用 createTime 创建时间 | |
| 119 | + if (vo.getSort() == null) { | |
| 120 | + vo.setSort(model.getCreateTime().getTime()); | |
| 121 | + } | |
| 115 | 122 | return vo; |
| 116 | 123 | } |
| 117 | 124 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
| ... | ... | @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 11 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 12 | 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 13 | 13 | import org.flowable.bpmn.model.BpmnModel; |
| 14 | -import org.flowable.bpmn.model.UserTask; | |
| 15 | 14 | import org.flowable.common.engine.impl.db.SuspensionState; |
| 16 | 15 | import org.flowable.engine.repository.Deployment; |
| 17 | 16 | import org.flowable.engine.repository.ProcessDefinition; |
| ... | ... | @@ -20,6 +19,7 @@ import org.mapstruct.Mapping; |
| 20 | 19 | import org.mapstruct.MappingTarget; |
| 21 | 20 | import org.mapstruct.factory.Mappers; |
| 22 | 21 | |
| 22 | +import java.util.Comparator; | |
| 23 | 23 | import java.util.List; |
| 24 | 24 | import java.util.Map; |
| 25 | 25 | |
| ... | ... | @@ -47,7 +47,7 @@ public interface BpmProcessDefinitionConvert { |
| 47 | 47 | Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap, |
| 48 | 48 | Map<Long, BpmFormDO> formMap, |
| 49 | 49 | Map<String, BpmCategoryDO> categoryMap) { |
| 50 | - return CollectionUtils.convertList(list, definition -> { | |
| 50 | + List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> { | |
| 51 | 51 | Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class); |
| 52 | 52 | BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class); |
| 53 | 53 | BpmFormDO form = null; |
| ... | ... | @@ -55,8 +55,11 @@ public interface BpmProcessDefinitionConvert { |
| 55 | 55 | form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class); |
| 56 | 56 | } |
| 57 | 57 | BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); |
| 58 | - return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null); | |
| 58 | + return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null); | |
| 59 | 59 | }); |
| 60 | + // 排序 | |
| 61 | + result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort)); | |
| 62 | + return result; | |
| 60 | 63 | } |
| 61 | 64 | |
| 62 | 65 | default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, |
| ... | ... | @@ -64,8 +67,7 @@ public interface BpmProcessDefinitionConvert { |
| 64 | 67 | BpmProcessDefinitionInfoDO processDefinitionInfo, |
| 65 | 68 | BpmFormDO form, |
| 66 | 69 | BpmCategoryDO category, |
| 67 | - BpmnModel bpmnModel, | |
| 68 | - List<UserTask> startUserSelectUserTaskList) { | |
| 70 | + BpmnModel bpmnModel) { | |
| 69 | 71 | BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class); |
| 70 | 72 | respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); |
| 71 | 73 | // Deployment |
| ... | ... | @@ -87,7 +89,6 @@ public interface BpmProcessDefinitionConvert { |
| 87 | 89 | // BpmnModel |
| 88 | 90 | if (bpmnModel != null) { |
| 89 | 91 | respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); |
| 90 | - respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class)); | |
| 91 | 92 | } |
| 92 | 93 | return respVO; |
| 93 | 94 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.convert.task; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.util.date.DateUtils; | |
| 4 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; | |
| 5 | -import org.flowable.engine.history.HistoricActivityInstance; | |
| 6 | -import org.mapstruct.Mapper; | |
| 7 | -import org.mapstruct.Mapping; | |
| 8 | -import org.mapstruct.Mappings; | |
| 9 | -import org.mapstruct.factory.Mappers; | |
| 10 | - | |
| 11 | -import java.util.List; | |
| 12 | - | |
| 13 | -/** | |
| 14 | - * BPM 活动 Convert | |
| 15 | - * | |
| 16 | - * @author 芋道源码 | |
| 17 | - */ | |
| 18 | -@Mapper(uses = DateUtils.class) | |
| 19 | -public interface BpmActivityConvert { | |
| 20 | - | |
| 21 | - BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class); | |
| 22 | - | |
| 23 | - List<BpmActivityRespVO> convertList(List<HistoricActivityInstance> list); | |
| 24 | - | |
| 25 | - @Mappings({ | |
| 26 | - @Mapping(source = "activityId", target = "key"), | |
| 27 | - @Mapping(source = "activityType", target = "type") | |
| 28 | - }) | |
| 29 | - BpmActivityRespVO convert(HistoricActivityInstance bean); | |
| 30 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.convert.task; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 6 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 5 | 8 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 6 | 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 11 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 7 | 12 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| 13 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; | |
| 14 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; | |
| 8 | 15 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| 16 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | |
| 17 | +import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; | |
| 9 | 18 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 10 | 19 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 11 | 20 | import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceStatusEvent; |
| 21 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 12 | 22 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 13 | 23 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| 14 | 24 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| 15 | 25 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 16 | 26 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 27 | +import org.flowable.bpmn.model.BpmnModel; | |
| 17 | 28 | import org.flowable.engine.history.HistoricProcessInstance; |
| 18 | 29 | import org.flowable.engine.repository.ProcessDefinition; |
| 19 | 30 | import org.flowable.engine.runtime.ProcessInstance; |
| 20 | 31 | import org.flowable.task.api.Task; |
| 32 | +import org.flowable.task.api.history.HistoricTaskInstance; | |
| 21 | 33 | import org.mapstruct.Mapper; |
| 22 | 34 | import org.mapstruct.Mapping; |
| 23 | 35 | import org.mapstruct.MappingTarget; |
| 24 | 36 | import org.mapstruct.factory.Mappers; |
| 25 | 37 | |
| 38 | +import java.util.HashSet; | |
| 26 | 39 | import java.util.List; |
| 27 | 40 | import java.util.Map; |
| 41 | +import java.util.Set; | |
| 42 | + | |
| 43 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 44 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 28 | 45 | |
| 29 | 46 | /** |
| 30 | 47 | * 流程实例 Convert |
| ... | ... | @@ -55,7 +72,7 @@ public interface BpmProcessInstanceConvert { |
| 55 | 72 | if (userMap != null) { |
| 56 | 73 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); |
| 57 | 74 | if (startUser != null) { |
| 58 | - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | |
| 75 | + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); | |
| 59 | 76 | MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); |
| 60 | 77 | } |
| 61 | 78 | } |
| ... | ... | @@ -65,20 +82,18 @@ public interface BpmProcessInstanceConvert { |
| 65 | 82 | |
| 66 | 83 | default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, |
| 67 | 84 | ProcessDefinition processDefinition, |
| 68 | - BpmProcessDefinitionInfoDO processDefinitionExt, | |
| 69 | - String bpmnXml, | |
| 85 | + BpmProcessDefinitionInfoDO processDefinitionInfo, | |
| 70 | 86 | AdminUserRespDTO startUser, |
| 71 | 87 | DeptRespDTO dept) { |
| 72 | 88 | BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); |
| 73 | - respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)); | |
| 74 | - respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); | |
| 89 | + respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)) | |
| 90 | + .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); | |
| 75 | 91 | // definition |
| 76 | 92 | respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); |
| 77 | - copyTo(processDefinitionExt, respVO.getProcessDefinition()); | |
| 78 | - respVO.getProcessDefinition().setBpmnXml(bpmnXml); | |
| 93 | + copyTo(processDefinitionInfo, respVO.getProcessDefinition()); | |
| 79 | 94 | // user |
| 80 | 95 | if (startUser != null) { |
| 81 | - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | |
| 96 | + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); | |
| 82 | 97 | if (dept != null) { |
| 83 | 98 | respVO.getStartUser().setDeptName(dept.getName()); |
| 84 | 99 | } |
| ... | ... | @@ -89,7 +104,7 @@ public interface BpmProcessInstanceConvert { |
| 89 | 104 | @Mapping(source = "from.id", target = "to.id", ignore = true) |
| 90 | 105 | void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); |
| 91 | 106 | |
| 92 | - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {; | |
| 107 | + default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) { | |
| 93 | 108 | return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) |
| 94 | 109 | .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); |
| 95 | 110 | } |
| ... | ... | @@ -109,4 +124,156 @@ public interface BpmProcessInstanceConvert { |
| 109 | 124 | .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); |
| 110 | 125 | } |
| 111 | 126 | |
| 127 | + default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, | |
| 128 | + List<HistoricTaskInstance> taskInstances, | |
| 129 | + BpmnModel bpmnModel, | |
| 130 | + BpmSimpleModelNodeVO simpleModel, | |
| 131 | + Set<String> unfinishedTaskActivityIds, | |
| 132 | + Set<String> finishedTaskActivityIds, | |
| 133 | + Set<String> finishedSequenceFlowActivityIds, | |
| 134 | + Set<String> rejectTaskActivityIds, | |
| 135 | + Map<Long, AdminUserRespDTO> userMap, | |
| 136 | + Map<Long, DeptRespDTO> deptMap) { | |
| 137 | + BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO(); | |
| 138 | + // 基本信息 | |
| 139 | + respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o | |
| 140 | + .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) | |
| 141 | + .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap))); | |
| 142 | + respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class) | |
| 143 | + .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) | |
| 144 | + .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)) | |
| 145 | + .setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)))); | |
| 146 | + respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); | |
| 147 | + respVO.setSimpleModel(simpleModel); | |
| 148 | + // 进度信息 | |
| 149 | + respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds) | |
| 150 | + .setFinishedTaskActivityIds(finishedTaskActivityIds) | |
| 151 | + .setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds) | |
| 152 | + .setRejectedTaskActivityIds(rejectTaskActivityIds); | |
| 153 | + return respVO; | |
| 154 | + } | |
| 155 | + | |
| 156 | + default UserSimpleBaseVO buildUser(String userIdStr, | |
| 157 | + Map<Long, AdminUserRespDTO> userMap, | |
| 158 | + Map<Long, DeptRespDTO> deptMap) { | |
| 159 | + if (StrUtil.isEmpty(userIdStr)) { | |
| 160 | + return null; | |
| 161 | + } | |
| 162 | + Long userId = NumberUtils.parseLong(userIdStr); | |
| 163 | + return buildUser(userId, userMap, deptMap); | |
| 164 | + } | |
| 165 | + | |
| 166 | + default UserSimpleBaseVO buildUser(Long userId, | |
| 167 | + Map<Long, AdminUserRespDTO> userMap, | |
| 168 | + Map<Long, DeptRespDTO> deptMap) { | |
| 169 | + if (userId == null) { | |
| 170 | + return null; | |
| 171 | + } | |
| 172 | + AdminUserRespDTO user = userMap.get(userId); | |
| 173 | + if (user == null) { | |
| 174 | + return null; | |
| 175 | + } | |
| 176 | + UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class); | |
| 177 | + DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null; | |
| 178 | + if (dept != null) { | |
| 179 | + userVO.setDeptName(dept.getName()); | |
| 180 | + } | |
| 181 | + return userVO; | |
| 182 | + } | |
| 183 | + | |
| 184 | + default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) { | |
| 185 | + if (task == null) { | |
| 186 | + return null; | |
| 187 | + } | |
| 188 | + return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) | |
| 189 | + .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); | |
| 190 | + } | |
| 191 | + | |
| 192 | + default Set<Long> parseUserIds(HistoricProcessInstance processInstance, | |
| 193 | + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, | |
| 194 | + BpmTaskRespVO todoTask) { | |
| 195 | + Set<Long> userIds = new HashSet<>(); | |
| 196 | + if (processInstance != null) { | |
| 197 | + userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); | |
| 198 | + } | |
| 199 | + for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) { | |
| 200 | + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee)); | |
| 201 | + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner)); | |
| 202 | + CollUtil.addAll(userIds, activityNode.getCandidateUserIds()); | |
| 203 | + } | |
| 204 | + if (todoTask != null) { | |
| 205 | + CollUtil.addIfAbsent(userIds, todoTask.getAssignee()); | |
| 206 | + CollUtil.addIfAbsent(userIds, todoTask.getOwner()); | |
| 207 | + if (CollUtil.isNotEmpty(todoTask.getChildren())) { | |
| 208 | + CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee)); | |
| 209 | + CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner)); | |
| 210 | + } | |
| 211 | + } | |
| 212 | + return userIds; | |
| 213 | + } | |
| 214 | + | |
| 215 | + default Set<Long> parseUserIds02(HistoricProcessInstance processInstance, | |
| 216 | + List<HistoricTaskInstance> tasks) { | |
| 217 | + Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); | |
| 218 | + tasks.forEach(task -> { | |
| 219 | + CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee()))); | |
| 220 | + CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner()))); | |
| 221 | + }); | |
| 222 | + return userIds; | |
| 223 | + } | |
| 224 | + | |
| 225 | + default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel, | |
| 226 | + ProcessDefinition processDefinition, | |
| 227 | + BpmProcessDefinitionInfoDO processDefinitionInfo, | |
| 228 | + HistoricProcessInstance processInstance, | |
| 229 | + Integer processInstanceStatus, | |
| 230 | + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, | |
| 231 | + BpmTaskRespVO todoTask, | |
| 232 | + Map<String, String> formFieldsPermission, | |
| 233 | + Map<Long, AdminUserRespDTO> userMap, | |
| 234 | + Map<Long, DeptRespDTO> deptMap) { | |
| 235 | + // 1.1 流程实例 | |
| 236 | + BpmProcessInstanceRespVO processInstanceResp = null; | |
| 237 | + if (processInstance != null) { | |
| 238 | + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | |
| 239 | + DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null; | |
| 240 | + processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept); | |
| 241 | + } | |
| 242 | + | |
| 243 | + // 1.2 流程定义 | |
| 244 | + BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( | |
| 245 | + processDefinition, null, processDefinitionInfo, null, null, bpmnModel); | |
| 246 | + | |
| 247 | + // 1.3 流程节点 | |
| 248 | + activityNodes.forEach(approveNode -> { | |
| 249 | + if (approveNode.getTasks() != null) { | |
| 250 | + approveNode.getTasks().forEach(task -> { | |
| 251 | + task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)); | |
| 252 | + task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)); | |
| 253 | + }); | |
| 254 | + } | |
| 255 | + approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap))); | |
| 256 | + }); | |
| 257 | + | |
| 258 | + // 1.4 待办任务 | |
| 259 | + if (todoTask != null) { | |
| 260 | + todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap)); | |
| 261 | + todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap)); | |
| 262 | + if (CollUtil.isNotEmpty(todoTask.getChildren())) { | |
| 263 | + todoTask.getChildren().forEach(childTask -> { | |
| 264 | + childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap)); | |
| 265 | + childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap)); | |
| 266 | + }); | |
| 267 | + } | |
| 268 | + } | |
| 269 | + | |
| 270 | + // 2. 拼接起来 | |
| 271 | + return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus) | |
| 272 | + .setProcessDefinition(definitionResp) | |
| 273 | + .setProcessInstance(processInstanceResp) | |
| 274 | + .setFormFieldsPermission(formFieldsPermission) | |
| 275 | + .setTodoTask(todoTask) | |
| 276 | + .setActivityNodes(activityNodes); | |
| 277 | + } | |
| 278 | + | |
| 112 | 279 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.convert.task; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.map.MapUtil; |
| 4 | -import cn.hutool.core.util.StrUtil; | |
| 5 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 10 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| 11 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 12 | 12 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| 13 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 14 | 13 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 15 | 14 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 16 | 15 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 17 | 16 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 18 | -import org.flowable.bpmn.model.BpmnModel; | |
| 19 | 17 | import org.flowable.engine.history.HistoricProcessInstance; |
| 20 | 18 | import org.flowable.engine.runtime.ProcessInstance; |
| 21 | 19 | import org.flowable.task.api.Task; |
| ... | ... | @@ -28,7 +26,7 @@ import java.util.Date; |
| 28 | 26 | import java.util.List; |
| 29 | 27 | import java.util.Map; |
| 30 | 28 | |
| 31 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | |
| 29 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 32 | 30 | import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; |
| 33 | 31 | |
| 34 | 32 | /** |
| ... | ... | @@ -51,7 +49,7 @@ public interface BpmTaskConvert { |
| 51 | 49 | } |
| 52 | 50 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| 53 | 51 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 54 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | |
| 52 | + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); | |
| 55 | 53 | }); |
| 56 | 54 | } |
| 57 | 55 | |
| ... | ... | @@ -65,7 +63,7 @@ public interface BpmTaskConvert { |
| 65 | 63 | // 用户信息 |
| 66 | 64 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| 67 | 65 | if (assignUser != null) { |
| 68 | - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); | |
| 66 | + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); | |
| 69 | 67 | findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); |
| 70 | 68 | } |
| 71 | 69 | // 流程实例 |
| ... | ... | @@ -73,7 +71,7 @@ public interface BpmTaskConvert { |
| 73 | 71 | if (processInstance != null) { |
| 74 | 72 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 75 | 73 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| 76 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | |
| 74 | + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); | |
| 77 | 75 | } |
| 78 | 76 | return taskVO; |
| 79 | 77 | }); |
| ... | ... | @@ -81,19 +79,17 @@ public interface BpmTaskConvert { |
| 81 | 79 | } |
| 82 | 80 | |
| 83 | 81 | default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList, |
| 84 | - HistoricProcessInstance processInstance, | |
| 85 | 82 | Map<Long, BpmFormDO> formMap, |
| 86 | 83 | Map<Long, AdminUserRespDTO> userMap, |
| 87 | - Map<Long, DeptRespDTO> deptMap, | |
| 88 | - BpmnModel bpmnModel) { | |
| 89 | - List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> { | |
| 84 | + Map<Long, DeptRespDTO> deptMap) { | |
| 85 | + return CollectionUtils.convertList(taskList, task -> { | |
| 86 | + // 特殊:已取消的任务,不返回 | |
| 90 | 87 | BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); |
| 91 | 88 | Integer taskStatus = FlowableUtils.getTaskStatus(task); |
| 89 | + if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) { | |
| 90 | + return null; | |
| 91 | + } | |
| 92 | 92 | taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); |
| 93 | - // 流程实例 | |
| 94 | - AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | |
| 95 | - taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); | |
| 96 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | |
| 97 | 93 | // 表单信息 |
| 98 | 94 | BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); |
| 99 | 95 | if (form != null) { |
| ... | ... | @@ -101,35 +97,10 @@ public interface BpmTaskConvert { |
| 101 | 97 | .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); |
| 102 | 98 | } |
| 103 | 99 | // 用户信息 |
| 104 | - AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); | |
| 105 | - if (assignUser != null) { | |
| 106 | - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); | |
| 107 | - findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); | |
| 108 | - } | |
| 109 | - AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); | |
| 110 | - if (ownerUser != null) { | |
| 111 | - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); | |
| 112 | - findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); | |
| 113 | - } | |
| 114 | - if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){ | |
| 115 | - // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置 | |
| 116 | - // TODO @jason:貌似这么返回,主要解决当前审批 task 的表单权限,但是不同抄送人的表单权限,可能不太对。例如说,对 A 抄送人是隐藏某个字段。 | |
| 117 | - // @芋艿 表单权限需要分离开。单独的接口来获取了 BpmProcessInstanceService.getProcessInstanceFormFieldsPermission | |
| 118 | - taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey())); | |
| 119 | - // 操作按钮设置 | |
| 120 | - taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey())); | |
| 121 | - } | |
| 122 | - return taskVO; | |
| 100 | + buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap); | |
| 101 | + buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap); | |
| 102 | + return taskVO; | |
| 123 | 103 | }); |
| 124 | - | |
| 125 | - // 拼接父子关系 | |
| 126 | - Map<String, List<BpmTaskRespVO>> childrenTaskMap = convertMultiMap( | |
| 127 | - filterList(taskVOList, r -> StrUtil.isNotEmpty(r.getParentTaskId())), | |
| 128 | - BpmTaskRespVO::getParentTaskId); | |
| 129 | - for (BpmTaskRespVO taskVO : taskVOList) { | |
| 130 | - taskVO.setChildren(childrenTaskMap.get(taskVO.getId())); | |
| 131 | - } | |
| 132 | - return filterList(taskVOList, r -> StrUtil.isEmpty(r.getParentTaskId())); | |
| 133 | 104 | } |
| 134 | 105 | |
| 135 | 106 | default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList, |
| ... | ... | @@ -138,7 +109,7 @@ public interface BpmTaskConvert { |
| 138 | 109 | return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> { |
| 139 | 110 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| 140 | 111 | if (assignUser != null) { |
| 141 | - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); | |
| 112 | + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); | |
| 142 | 113 | DeptRespDTO dept = deptMap.get(assignUser.getDeptId()); |
| 143 | 114 | if (dept != null) { |
| 144 | 115 | taskVO.getAssigneeUser().setDeptName(dept.getName()); |
| ... | ... | @@ -146,12 +117,21 @@ public interface BpmTaskConvert { |
| 146 | 117 | } |
| 147 | 118 | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); |
| 148 | 119 | if (ownerUser != null) { |
| 149 | - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); | |
| 120 | + taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); | |
| 150 | 121 | findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); |
| 151 | 122 | } |
| 152 | 123 | })); |
| 153 | 124 | } |
| 154 | 125 | |
| 126 | + default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks, | |
| 127 | + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) { | |
| 128 | + return BeanUtils.toBean(todoTask, BpmTaskRespVO.class) | |
| 129 | + .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask)) | |
| 130 | + .setButtonsSetting(buttonsSetting) | |
| 131 | + .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class) | |
| 132 | + .setStatus(FlowableUtils.getTaskStatus(childTask)))); | |
| 133 | + } | |
| 134 | + | |
| 155 | 135 | default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, |
| 156 | 136 | Task task) { |
| 157 | 137 | BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO(); |
| ... | ... | @@ -162,6 +142,42 @@ public interface BpmTaskConvert { |
| 162 | 142 | return reqDTO; |
| 163 | 143 | } |
| 164 | 144 | |
| 145 | + default void buildTaskOwner(BpmTaskRespVO task, String taskOwner, | |
| 146 | + Map<Long, AdminUserRespDTO> userMap, | |
| 147 | + Map<Long, DeptRespDTO> deptMap) { | |
| 148 | + AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner)); | |
| 149 | + if (ownerUser != null) { | |
| 150 | + task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); | |
| 151 | + findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName())); | |
| 152 | + } | |
| 153 | + } | |
| 154 | + | |
| 155 | + default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap, | |
| 156 | + Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { | |
| 157 | + List<Task> childTasks = childrenTaskMap.get(task.getId()); | |
| 158 | + if (CollUtil.isNotEmpty(childTasks)) { | |
| 159 | + task.setChildren( | |
| 160 | + convertList(childTasks, childTask -> { | |
| 161 | + BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class); | |
| 162 | + childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask)); | |
| 163 | + buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap); | |
| 164 | + buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap); | |
| 165 | + return childTaskVO; | |
| 166 | + }) | |
| 167 | + ); | |
| 168 | + } | |
| 169 | + } | |
| 170 | + | |
| 171 | + default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee, | |
| 172 | + Map<Long, AdminUserRespDTO> userMap, | |
| 173 | + Map<Long, DeptRespDTO> deptMap) { | |
| 174 | + AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee)); | |
| 175 | + if (assignUser != null) { | |
| 176 | + task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); | |
| 177 | + findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); | |
| 178 | + } | |
| 179 | + } | |
| 180 | + | |
| 165 | 181 | /** |
| 166 | 182 | * 将父任务的属性,拷贝到子任务(加签任务) |
| 167 | 183 | * <p> | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.dal.dataobject.definition; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; | |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler; |
| 5 | 6 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| 6 | 7 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; |
| ... | ... | @@ -122,6 +123,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { |
| 122 | 123 | * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 |
| 123 | 124 | */ |
| 124 | 125 | private Boolean visible; |
| 126 | + /** | |
| 127 | + * 排序值 | |
| 128 | + */ | |
| 129 | + private Long sort; | |
| 125 | 130 | |
| 126 | 131 | /** |
| 127 | 132 | * 可发起用户编号数组 |
| ... | ... | @@ -134,7 +139,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { |
| 134 | 139 | * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起 |
| 135 | 140 | * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的 |
| 136 | 141 | */ |
| 137 | - @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 | |
| 142 | + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 | |
| 138 | 143 | private List<Long> startUserIds; |
| 139 | 144 | |
| 140 | 145 | /** | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
| ... | ... | @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 4 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 5 | 5 | import com.baomidou.mybatisplus.annotation.TableId; |
| 6 | 6 | import com.baomidou.mybatisplus.annotation.TableName; |
| 7 | +import org.flowable.bpmn.model.FlowNode; | |
| 8 | +import org.flowable.task.api.history.HistoricTaskInstance; | |
| 7 | 9 | import lombok.AllArgsConstructor; |
| 8 | 10 | import lombok.Builder; |
| 9 | 11 | import lombok.Data; |
| ... | ... | @@ -54,23 +56,25 @@ public class BpmProcessInstanceCopyDO extends BaseDO { |
| 54 | 56 | */ |
| 55 | 57 | private String category; |
| 56 | 58 | /** |
| 57 | - * 流程活动编号 | |
| 59 | + * 流程活动的编号 | |
| 58 | 60 | * <p/> |
| 59 | - * 对应 BPMN XML 节点编号,用于查询抄送节点的表单字段权限 | |
| 60 | - * 这里冗余的原因:如果是钉钉易搭的抄送节点 (ServiceTask),使用 taskId 可能查不到对应的 activityId | |
| 61 | + * | |
| 62 | + * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号 | |
| 63 | + * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId | |
| 61 | 64 | */ |
| 62 | 65 | private String activityId; |
| 63 | 66 | /** |
| 64 | - * 任务主键 | |
| 65 | - * 关联 Task 的 id 属性 | |
| 67 | + * 流程活动的名字 | |
| 68 | + * | |
| 69 | + * 冗余 {@link FlowNode#getName()} | |
| 66 | 70 | */ |
| 67 | - private String taskId; | |
| 71 | + private String activityName; | |
| 68 | 72 | /** |
| 69 | - * 任务名称 | |
| 73 | + * 流程活动的编号 | |
| 70 | 74 | * |
| 71 | - * 冗余 Task 的 name 属性 | |
| 75 | + * 关联 {@link HistoricTaskInstance#getId()} | |
| 72 | 76 | */ |
| 73 | - private String taskName; | |
| 77 | + private String taskId; | |
| 74 | 78 | |
| 75 | 79 | /** |
| 76 | 80 | * 用户编号(被抄送的用户编号) |
| ... | ... | @@ -79,4 +83,9 @@ public class BpmProcessInstanceCopyDO extends BaseDO { |
| 79 | 83 | */ |
| 80 | 84 | private Long userId; |
| 81 | 85 | |
| 86 | + /** | |
| 87 | + * 抄送意见 | |
| 88 | + */ | |
| 89 | + private String reason; | |
| 90 | + | |
| 82 | 91 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.dal.mysql.definition; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 3 | 4 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 6 | import org.apache.ibatis.annotations.Mapper; |
| ... | ... | @@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX<BpmProcessDe |
| 18 | 19 | return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); |
| 19 | 20 | } |
| 20 | 21 | |
| 22 | + default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) { | |
| 23 | + update(updateObj, | |
| 24 | + new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId)); | |
| 25 | + } | |
| 26 | + | |
| 21 | 27 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
| ... | ... | @@ -53,7 +53,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav |
| 53 | 53 | @SuppressWarnings("unchecked") |
| 54 | 54 | Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); |
| 55 | 55 | if (assigneeUserIds == null) { |
| 56 | - assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); | |
| 56 | + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); | |
| 57 | 57 | execution.setVariable(super.collectionVariable, assigneeUserIds); |
| 58 | 58 | if (CollUtil.isEmpty(assigneeUserIds)) { |
| 59 | 59 | // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
| ... | ... | @@ -46,7 +46,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB |
| 46 | 46 | @SuppressWarnings("unchecked") |
| 47 | 47 | Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); |
| 48 | 48 | if (assigneeUserIds == null) { |
| 49 | - assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); | |
| 49 | + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); | |
| 50 | 50 | execution.setVariable(super.collectionVariable, assigneeUserIds); |
| 51 | 51 | if (CollUtil.isEmpty(assigneeUserIds)) { |
| 52 | 52 | // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
| ... | ... | @@ -57,7 +57,7 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { |
| 57 | 57 | |
| 58 | 58 | // 情况二,如果非多实例的任务,则计算任务处理人 |
| 59 | 59 | // 第一步,先计算可处理该任务的处理人们 |
| 60 | - Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution); | |
| 60 | + Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution); | |
| 61 | 61 | if (CollUtil.isEmpty(candidateUserIds)) { |
| 62 | 62 | return null; |
| 63 | 63 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
| ... | ... | @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 18 | 18 | import com.google.common.annotations.VisibleForTesting; |
| 19 | 19 | import lombok.extern.slf4j.Slf4j; |
| 20 | 20 | import org.flowable.bpmn.model.BpmnModel; |
| 21 | +import org.flowable.bpmn.model.FlowElement; | |
| 21 | 22 | import org.flowable.bpmn.model.UserTask; |
| 22 | 23 | import org.flowable.engine.delegate.DelegateExecution; |
| 23 | 24 | import org.flowable.engine.runtime.ProcessInstance; |
| ... | ... | @@ -89,32 +90,66 @@ public class BpmTaskCandidateInvoker { |
| 89 | 90 | * @return 用户编号集合 |
| 90 | 91 | */ |
| 91 | 92 | @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 |
| 92 | - public Set<Long> calculateUsers(DelegateExecution execution) { | |
| 93 | + public Set<Long> calculateUsersByTask(DelegateExecution execution) { | |
| 93 | 94 | // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 |
| 94 | - Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement()); | |
| 95 | + FlowElement flowElement = execution.getCurrentFlowElement(); | |
| 96 | + Integer approveType = BpmnModelUtils.parseApproveType(flowElement); | |
| 95 | 97 | if (ObjectUtils.equalsAny(approveType, |
| 96 | 98 | BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), |
| 97 | 99 | BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { |
| 98 | 100 | return new HashSet<>(); |
| 99 | 101 | } |
| 100 | 102 | |
| 101 | - Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement()); | |
| 102 | - String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); | |
| 103 | 103 | // 1.1 计算任务的候选人 |
| 104 | - Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); | |
| 104 | + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); | |
| 105 | + String param = BpmnModelUtils.parseCandidateParam(flowElement); | |
| 106 | + Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param); | |
| 107 | + // 1.2 移除被禁用的用户 | |
| 105 | 108 | removeDisableUsers(userIds); |
| 109 | + | |
| 110 | + // 2. 候选人为空时,根据“审批人为空”的配置补充 | |
| 111 | + if (CollUtil.isEmpty(userIds)) { | |
| 112 | + userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) | |
| 113 | + .calculateUsersByTask(execution, param); | |
| 114 | + // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! | |
| 115 | + } | |
| 116 | + | |
| 117 | + // 3. 移除发起人的用户 | |
| 118 | + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) | |
| 119 | + .getProcessInstance(execution.getProcessInstanceId()); | |
| 120 | + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); | |
| 121 | + removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId())); | |
| 122 | + return userIds; | |
| 123 | + } | |
| 124 | + | |
| 125 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, | |
| 126 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 127 | + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 | |
| 128 | + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); | |
| 129 | + Integer approveType = BpmnModelUtils.parseApproveType(flowElement); | |
| 130 | + if (ObjectUtils.equalsAny(approveType, | |
| 131 | + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), | |
| 132 | + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { | |
| 133 | + return new HashSet<>(); | |
| 134 | + } | |
| 135 | + | |
| 136 | + // 1.1 计算任务的候选人 | |
| 137 | + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); | |
| 138 | + String param = BpmnModelUtils.parseCandidateParam(flowElement); | |
| 139 | + Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param, | |
| 140 | + startUserId, processDefinitionId, processVariables); | |
| 106 | 141 | // 1.2 移除被禁用的用户 |
| 107 | 142 | removeDisableUsers(userIds); |
| 108 | 143 | |
| 109 | 144 | // 2. 候选人为空时,根据“审批人为空”的配置补充 |
| 110 | 145 | if (CollUtil.isEmpty(userIds)) { |
| 111 | 146 | userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) |
| 112 | - .calculateUsers(execution, param); | |
| 147 | + .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables); | |
| 113 | 148 | // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! |
| 114 | 149 | } |
| 115 | 150 | |
| 116 | 151 | // 3. 移除发起人的用户 |
| 117 | - removeStartUserIfSkip(execution, userIds); | |
| 152 | + removeStartUserIfSkip(userIds, flowElement, startUserId); | |
| 118 | 153 | return userIds; |
| 119 | 154 | } |
| 120 | 155 | |
| ... | ... | @@ -126,7 +161,7 @@ public class BpmTaskCandidateInvoker { |
| 126 | 161 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds); |
| 127 | 162 | assigneeUserIds.removeIf(id -> { |
| 128 | 163 | AdminUserRespDTO user = userMap.get(id); |
| 129 | - return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); | |
| 164 | + return user == null || CommonStatusEnum.isDisable(user.getStatus()); | |
| 130 | 165 | }); |
| 131 | 166 | } |
| 132 | 167 | |
| ... | ... | @@ -135,25 +170,23 @@ public class BpmTaskCandidateInvoker { |
| 135 | 170 | * |
| 136 | 171 | * 注意:如果只有一个候选人,则不处理,避免无法审批 |
| 137 | 172 | * |
| 138 | - * @param execution 执行中的任务 | |
| 139 | 173 | * @param assigneeUserIds 当前分配的候选人 |
| 174 | + * @param flowElement 当前节点 | |
| 175 | + * @param startUserId 发起人 | |
| 140 | 176 | */ |
| 141 | 177 | @VisibleForTesting |
| 142 | - void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) { | |
| 178 | + void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) { | |
| 143 | 179 | if (CollUtil.size(assigneeUserIds) <= 1) { |
| 144 | 180 | return; |
| 145 | 181 | } |
| 146 | - Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); | |
| 182 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement); | |
| 147 | 183 | if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { |
| 148 | 184 | return; |
| 149 | 185 | } |
| 150 | - ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) | |
| 151 | - .getProcessInstance(execution.getProcessInstanceId()); | |
| 152 | - Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); | |
| 153 | - assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); | |
| 186 | + assigneeUserIds.remove(startUserId); | |
| 154 | 187 | } |
| 155 | 188 | |
| 156 | - public BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { | |
| 189 | + private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { | |
| 157 | 190 | BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); |
| 158 | 191 | Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); |
| 159 | 192 | BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 4 | +import org.flowable.bpmn.model.BpmnModel; | |
| 4 | 5 | import org.flowable.engine.delegate.DelegateExecution; |
| 5 | -import org.flowable.engine.runtime.ProcessInstance; | |
| 6 | 6 | |
| 7 | -import java.util.Collections; | |
| 7 | +import java.util.Map; | |
| 8 | 8 | import java.util.Set; |
| 9 | 9 | |
| 10 | 10 | /** |
| ... | ... | @@ -42,47 +42,44 @@ public interface BpmTaskCandidateStrategy { |
| 42 | 42 | /** |
| 43 | 43 | * 基于候选人参数,获得任务的候选用户们 |
| 44 | 44 | * |
| 45 | + * 注意:实现 calculateUsers 系列方法时,有两种选择: | |
| 46 | + * 1. 只重写 calculateUsers 默认方法 | |
| 47 | + * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法 | |
| 48 | + * | |
| 45 | 49 | * @param param 执行任务 |
| 46 | 50 | * @return 用户编号集合 |
| 47 | 51 | */ |
| 48 | 52 | default Set<Long> calculateUsers(String param) { |
| 49 | - return Collections.emptySet(); | |
| 53 | + throw new UnsupportedOperationException("该分配方法未实现,请检查!"); | |
| 50 | 54 | } |
| 51 | 55 | |
| 52 | 56 | /** |
| 53 | - * 基于执行任务,获得任务的候选用户们 | |
| 57 | + * 基于【执行任务】,获得任务的候选用户们 | |
| 54 | 58 | * |
| 55 | 59 | * @param execution 执行任务 |
| 56 | 60 | * @return 用户编号集合 |
| 57 | 61 | */ |
| 58 | - default Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 59 | - Set<Long> users = calculateUsers(param); | |
| 60 | - removeDisableUsers(users); | |
| 61 | - return users; | |
| 62 | + default Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 63 | + return calculateUsers(param); | |
| 62 | 64 | } |
| 63 | 65 | |
| 64 | 66 | /** |
| 65 | - * 基于流程实例,获得任务的候选用户们 | |
| 67 | + * 基于【流程活动】,获得任务的候选用户们 | |
| 66 | 68 | * <p> |
| 67 | 69 | * 目的:用于获取未执行节点的候选用户们 |
| 68 | 70 | * |
| 69 | - * @param startUserId 流程发起人编号 | |
| 70 | - * @param processInstance 流程实例编号 | |
| 71 | - * @param activityId 活动 Id (对应 Bpmn XML id) | |
| 71 | + * @param bpmnModel 流程图 | |
| 72 | + * @param activityId 活动 ID (对应 Bpmn XML id) | |
| 72 | 73 | * @param param 节点的参数 |
| 74 | + * @param startUserId 流程发起人编号 | |
| 75 | + * @param processDefinitionId 流程定义编号 | |
| 76 | + * @param processVariables 流程变量 | |
| 73 | 77 | * @return 用户编号集合 |
| 74 | 78 | */ |
| 75 | - default Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 76 | - Set<Long> users = calculateUsers(param); | |
| 77 | - removeDisableUsers(users); | |
| 78 | - return users; | |
| 79 | + @SuppressWarnings("unused") | |
| 80 | + default Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 81 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 82 | + return calculateUsers(param); | |
| 79 | 83 | } |
| 80 | 84 | |
| 81 | - /** | |
| 82 | - * 移除被禁用的用户 | |
| 83 | - * | |
| 84 | - * @param users 用户 Ids | |
| 85 | - */ | |
| 86 | - void removeDisableUsers(Set<Long> users); | |
| 87 | - | |
| 88 | 85 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | - | |
| 3 | -import cn.hutool.core.collection.CollUtil; | |
| 4 | -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 5 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 7 | -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 8 | - | |
| 9 | -import java.util.Map; | |
| 10 | -import java.util.Set; | |
| 11 | - | |
| 12 | -/** | |
| 13 | - * {@link BpmTaskCandidateStrategy} 抽象类 | |
| 14 | - * | |
| 15 | - * @author jason | |
| 16 | - */ | |
| 17 | -public abstract class BpmTaskCandidateAbstractStrategy implements BpmTaskCandidateStrategy { | |
| 18 | - | |
| 19 | - protected AdminUserApi adminUserApi; | |
| 20 | - | |
| 21 | - public BpmTaskCandidateAbstractStrategy(AdminUserApi adminUserApi) { | |
| 22 | - this.adminUserApi = adminUserApi; | |
| 23 | - } | |
| 24 | - | |
| 25 | - @Override | |
| 26 | - public void removeDisableUsers(Set<Long> users) { | |
| 27 | - if (CollUtil.isEmpty(users)) { | |
| 28 | - return; | |
| 29 | - } | |
| 30 | - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(users); | |
| 31 | - users.removeIf(id -> { | |
| 32 | - AdminUserRespDTO user = userMap.get(id); | |
| 33 | - return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); | |
| 34 | - }); | |
| 35 | - } | |
| 36 | - | |
| 37 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.lang.Assert; |
| ... | ... | @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand |
| 6 | 6 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| 7 | 7 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 9 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 10 | +import jakarta.annotation.Resource; | |
| 9 | 11 | |
| 10 | 12 | import java.util.*; |
| 11 | 13 | |
| ... | ... | @@ -14,14 +16,12 @@ import java.util.*; |
| 14 | 16 | * |
| 15 | 17 | * @author jason |
| 16 | 18 | */ |
| 17 | -public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 19 | +public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { | |
| 18 | 20 | |
| 21 | + @Resource | |
| 19 | 22 | protected DeptApi deptApi; |
| 20 | - | |
| 21 | - public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 22 | - super(adminUserApi); | |
| 23 | - this.deptApi = deptApi; | |
| 24 | - } | |
| 23 | + @Resource | |
| 24 | + protected AdminUserApi adminUserApi; | |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | 27 | * 获得指定层级的部门负责人,只有第 level 的负责人 |
| ... | ... | @@ -75,4 +75,17 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTas |
| 75 | 75 | return deptLeaderIds; |
| 76 | 76 | } |
| 77 | 77 | |
| 78 | + /** | |
| 79 | + * 获取发起人的部门 | |
| 80 | + * | |
| 81 | + * @param startUserId 发起人 Id | |
| 82 | + */ | |
| 83 | + protected DeptRespDTO getStartUserDept(Long startUserId) { | |
| 84 | + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); | |
| 85 | + if (startUser.getDeptId() == null) { // 找不到部门 | |
| 86 | + return null; | |
| 87 | + } | |
| 88 | + return deptApi.getDept(startUser.getDeptId()); | |
| 89 | + } | |
| 90 | + | |
| 78 | 91 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 7 | -import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 8 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 9 | 7 | import org.springframework.stereotype.Component; |
| 10 | 8 | |
| 9 | +import java.util.List; | |
| 11 | 10 | import java.util.Set; |
| 12 | 11 | |
| 13 | 12 | /** |
| ... | ... | @@ -16,11 +15,7 @@ import java.util.Set; |
| 16 | 15 | * @author jason |
| 17 | 16 | */ |
| 18 | 17 | @Component |
| 19 | -public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { | |
| 20 | - | |
| 21 | - public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 22 | - super(adminUserApi, deptApi); | |
| 23 | - } | |
| 18 | +public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { | |
| 24 | 19 | |
| 25 | 20 | @Override |
| 26 | 21 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -32,14 +27,19 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs |
| 32 | 27 | // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级 |
| 33 | 28 | String[] params = param.split("\\|"); |
| 34 | 29 | Assert.isTrue(params.length == 2, "参数格式不匹配"); |
| 35 | - deptApi.validateDeptList(StrUtils.splitToLong(params[0], ",")); | |
| 36 | - Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0"); | |
| 30 | + List<Long> deptIds = StrUtils.splitToLong(params[0], ","); | |
| 31 | + int level = Integer.parseInt(params[1]); | |
| 32 | + // 校验部门存在 | |
| 33 | + deptApi.validateDeptList(deptIds); | |
| 34 | + Assert.isTrue(level > 0, "部门层级必须大于 0"); | |
| 37 | 35 | } |
| 38 | 36 | |
| 39 | 37 | @Override |
| 40 | 38 | public Set<Long> calculateUsers(String param) { |
| 41 | 39 | String[] params = param.split("\\|"); |
| 42 | - return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1])); | |
| 40 | + List<Long> deptIds = StrUtils.splitToLong(params[0], ","); | |
| 41 | + int level = Integer.parseInt(params[1]); | |
| 42 | + return super.getMultiLevelDeptLeaderIds(deptIds, level); | |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | 45 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 6 | 6 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| 7 | 7 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 8 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 8 | +import jakarta.annotation.Resource; | |
| 9 | 9 | import org.springframework.stereotype.Component; |
| 10 | 10 | |
| 11 | 11 | import java.util.List; |
| ... | ... | @@ -19,14 +19,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 19 | 19 | * @author kyle |
| 20 | 20 | */ |
| 21 | 21 | @Component |
| 22 | -public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 22 | +public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { | |
| 23 | 23 | |
| 24 | - private final DeptApi deptApi; | |
| 25 | - | |
| 26 | - public BpmTaskCandidateDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 27 | - super(adminUserApi); | |
| 28 | - this.deptApi = deptApi; | |
| 29 | - } | |
| 24 | + @Resource | |
| 25 | + private DeptApi deptApi; | |
| 30 | 26 | |
| 31 | 27 | @Override |
| 32 | 28 | public BpmTaskCandidateStrategyEnum getStrategy() { | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat |
| 6 | 6 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| 7 | 7 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 8 | 8 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 9 | +import jakarta.annotation.Resource; | |
| 9 | 10 | import org.springframework.stereotype.Component; |
| 10 | 11 | |
| 11 | 12 | import java.util.List; |
| ... | ... | @@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 19 | 20 | * @author kyle |
| 20 | 21 | */ |
| 21 | 22 | @Component |
| 22 | -public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 23 | +public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { | |
| 23 | 24 | |
| 24 | - private final DeptApi deptApi; | |
| 25 | - | |
| 26 | - public BpmTaskCandidateDeptMemberStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 27 | - super(adminUserApi); | |
| 28 | - this.deptApi = deptApi; | |
| 29 | - } | |
| 25 | + @Resource | |
| 26 | + private DeptApi deptApi; | |
| 27 | + @Resource | |
| 28 | + private AdminUserApi adminUserApi; | |
| 30 | 29 | |
| 31 | 30 | @Override |
| 32 | 31 | public BpmTaskCandidateStrategyEnum getStrategy() { | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 8 | -import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 9 | 8 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 10 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 12 | 9 | import jakarta.annotation.Resource; |
| 10 | +import org.flowable.bpmn.model.BpmnModel; | |
| 13 | 11 | import org.flowable.engine.delegate.DelegateExecution; |
| 14 | 12 | import org.flowable.engine.runtime.ProcessInstance; |
| 15 | 13 | import org.springframework.context.annotation.Lazy; |
| 16 | 14 | import org.springframework.stereotype.Component; |
| 17 | 15 | |
| 18 | 16 | import java.util.HashSet; |
| 17 | +import java.util.Map; | |
| 19 | 18 | import java.util.Set; |
| 20 | 19 | |
| 21 | 20 | import static cn.hutool.core.collection.ListUtil.toList; |
| ... | ... | @@ -26,16 +25,12 @@ import static cn.hutool.core.collection.ListUtil.toList; |
| 26 | 25 | * @author jason |
| 27 | 26 | */ |
| 28 | 27 | @Component |
| 29 | -public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { | |
| 28 | +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { | |
| 30 | 29 | |
| 31 | 30 | @Resource |
| 32 | 31 | @Lazy |
| 33 | 32 | private BpmProcessInstanceService processInstanceService; |
| 34 | 33 | |
| 35 | - public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 36 | - super(adminUserApi, deptApi); | |
| 37 | - } | |
| 38 | - | |
| 39 | 34 | @Override |
| 40 | 35 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 41 | 36 | return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI; |
| ... | ... | @@ -43,48 +38,33 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan |
| 43 | 38 | |
| 44 | 39 | @Override |
| 45 | 40 | public void validateParam(String param) { |
| 46 | - // 参数是部门的层级 | |
| 47 | - Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); | |
| 41 | + int level = Integer.parseInt(param); // 参数是部门的层级 | |
| 42 | + Assert.isTrue(level > 0, "部门的层级必须大于 0"); | |
| 48 | 43 | } |
| 49 | 44 | |
| 50 | 45 | @Override |
| 51 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 46 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 47 | + int level = Integer.parseInt(param); // 参数是部门的层级 | |
| 52 | 48 | // 获得流程发起人 |
| 53 | 49 | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| 54 | 50 | Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); |
| 55 | 51 | // 获取发起人的 multi 部门负责人 |
| 56 | - DeptRespDTO dept = getStartUserDept(startUserId); | |
| 52 | + DeptRespDTO dept = super.getStartUserDept(startUserId); | |
| 57 | 53 | if (dept == null) { |
| 58 | 54 | return new HashSet<>(); |
| 59 | 55 | } |
| 60 | - Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 | |
| 61 | - // TODO @jason:这里 removeDisableUsers 的原因是啥呀? | |
| 62 | - removeDisableUsers(users); | |
| 63 | - return users; | |
| 56 | + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); | |
| 64 | 57 | } |
| 65 | 58 | |
| 66 | 59 | @Override |
| 67 | - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 68 | - DeptRespDTO dept = getStartUserDept(startUserId); | |
| 60 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 61 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 62 | + int level = Integer.parseInt(param); // 参数是部门的层级 | |
| 63 | + DeptRespDTO dept = super.getStartUserDept(startUserId); | |
| 69 | 64 | if (dept == null) { |
| 70 | 65 | return new HashSet<>(); |
| 71 | 66 | } |
| 72 | - Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 | |
| 73 | - removeDisableUsers(users); | |
| 74 | - return users; | |
| 75 | - } | |
| 76 | - | |
| 77 | - /** | |
| 78 | - * 获取发起人的部门 | |
| 79 | - * | |
| 80 | - * @param startUserId 发起人 Id | |
| 81 | - */ | |
| 82 | - protected DeptRespDTO getStartUserDept(Long startUserId) { | |
| 83 | - AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); | |
| 84 | - if (startUser.getDeptId() == null) { // 找不到部门 | |
| 85 | - return null; | |
| 86 | - } | |
| 87 | - return deptApi.getDept(startUser.getDeptId()); | |
| 67 | + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); | |
| 88 | 68 | } |
| 89 | 69 | |
| 90 | 70 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 8 | -import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 9 | 8 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 10 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 12 | 9 | import jakarta.annotation.Resource; |
| 10 | +import org.flowable.bpmn.model.BpmnModel; | |
| 13 | 11 | import org.flowable.engine.delegate.DelegateExecution; |
| 14 | 12 | import org.flowable.engine.runtime.ProcessInstance; |
| 15 | 13 | import org.springframework.context.annotation.Lazy; |
| 16 | 14 | import org.springframework.stereotype.Component; |
| 17 | 15 | |
| 18 | 16 | import java.util.HashSet; |
| 17 | +import java.util.Map; | |
| 19 | 18 | import java.util.Set; |
| 20 | 19 | |
| 21 | 20 | import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; |
| ... | ... | @@ -26,7 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; |
| 26 | 25 | * @author jason |
| 27 | 26 | */ |
| 28 | 27 | @Component |
| 29 | -public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { | |
| 28 | +public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { | |
| 30 | 29 | |
| 31 | 30 | @Resource |
| 32 | 31 | @Lazy // 避免循环依赖 |
| ... | ... | @@ -37,10 +36,6 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat |
| 37 | 36 | return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; |
| 38 | 37 | } |
| 39 | 38 | |
| 40 | - public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 41 | - super(adminUserApi, deptApi); | |
| 42 | - } | |
| 43 | - | |
| 44 | 39 | @Override |
| 45 | 40 | public void validateParam(String param) { |
| 46 | 41 | // 参数是部门的层级 |
| ... | ... | @@ -48,44 +43,29 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat |
| 48 | 43 | } |
| 49 | 44 | |
| 50 | 45 | @Override |
| 51 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 46 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 52 | 47 | // 获得流程发起人 |
| 53 | 48 | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| 54 | 49 | Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); |
| 55 | 50 | // 获取发起人的部门负责人 |
| 56 | - Set<Long> users = getStartUserDeptLeader(startUserId, param); | |
| 57 | - removeDisableUsers(users); | |
| 58 | - return users; | |
| 51 | + return getStartUserDeptLeader(startUserId, param); | |
| 59 | 52 | } |
| 60 | 53 | |
| 61 | 54 | @Override |
| 62 | - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 55 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 56 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 63 | 57 | // 获取发起人的部门负责人 |
| 64 | - Set<Long> users = getStartUserDeptLeader(startUserId, param); | |
| 65 | - removeDisableUsers(users); | |
| 66 | - return users; | |
| 58 | + return getStartUserDeptLeader(startUserId, param); | |
| 67 | 59 | } |
| 68 | 60 | |
| 69 | 61 | private Set<Long> getStartUserDeptLeader(Long startUserId, String param) { |
| 70 | - DeptRespDTO dept = getStartUserDept(startUserId); | |
| 62 | + int level = Integer.parseInt(param); // 参数是部门的层级 | |
| 63 | + DeptRespDTO dept = super.getStartUserDept(startUserId); | |
| 71 | 64 | if (dept == null) { |
| 72 | 65 | return new HashSet<>(); |
| 73 | 66 | } |
| 74 | - Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 | |
| 67 | + Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level); | |
| 75 | 68 | return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); |
| 76 | 69 | } |
| 77 | 70 | |
| 78 | - /** | |
| 79 | - * 获取发起人的部门 | |
| 80 | - * | |
| 81 | - * @param startUserId 发起人 Id | |
| 82 | - */ | |
| 83 | - protected DeptRespDTO getStartUserDept(Long startUserId) { | |
| 84 | - AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); | |
| 85 | - if (startUser.getDeptId() == null) { // 找不到部门 | |
| 86 | - return null; | |
| 87 | - } | |
| 88 | - return deptApi.getDept(startUser.getDeptId()); | |
| 89 | - } | |
| 90 | - | |
| 91 | 71 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.lang.Assert; |
| 5 | +import cn.hutool.core.util.ObjectUtil; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; | |
| 5 | 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 6 | 8 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 7 | 9 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 8 | 10 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 9 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | +import com.google.common.collect.Sets; | |
| 10 | 12 | import jakarta.annotation.Resource; |
| 11 | 13 | import org.flowable.bpmn.model.BpmnModel; |
| 14 | +import org.flowable.bpmn.model.ServiceTask; | |
| 15 | +import org.flowable.bpmn.model.Task; | |
| 12 | 16 | import org.flowable.bpmn.model.UserTask; |
| 13 | 17 | import org.flowable.engine.delegate.DelegateExecution; |
| 14 | 18 | import org.flowable.engine.runtime.ProcessInstance; |
| ... | ... | @@ -23,16 +27,12 @@ import java.util.*; |
| 23 | 27 | * @author 芋道源码 |
| 24 | 28 | */ |
| 25 | 29 | @Component |
| 26 | -public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 30 | +public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { | |
| 27 | 31 | |
| 28 | 32 | @Resource |
| 29 | 33 | @Lazy // 延迟加载,避免循环依赖 |
| 30 | 34 | private BpmProcessInstanceService processInstanceService; |
| 31 | 35 | |
| 32 | - public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) { | |
| 33 | - super(adminUserApi); | |
| 34 | - } | |
| 35 | - | |
| 36 | 36 | @Override |
| 37 | 37 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 38 | 38 | return BpmTaskCandidateStrategyEnum.START_USER_SELECT; |
| ... | ... | @@ -42,7 +42,12 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs |
| 42 | 42 | public void validateParam(String param) {} |
| 43 | 43 | |
| 44 | 44 | @Override |
| 45 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 45 | + public boolean isParamRequired() { | |
| 46 | + return false; | |
| 47 | + } | |
| 48 | + | |
| 49 | + @Override | |
| 50 | + public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 46 | 51 | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| 47 | 52 | Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); |
| 48 | 53 | Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); |
| ... | ... | @@ -50,47 +55,43 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs |
| 50 | 55 | execution.getProcessInstanceId()); |
| 51 | 56 | // 获得审批人 |
| 52 | 57 | List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); |
| 53 | - Set<Long> users = new LinkedHashSet<>(assignees); | |
| 54 | - removeDisableUsers(users); | |
| 55 | - return users; | |
| 58 | + return new LinkedHashSet<>(assignees); | |
| 56 | 59 | } |
| 57 | 60 | |
| 58 | 61 | @Override |
| 59 | - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 60 | - if (processInstance == null) { | |
| 61 | - return Collections.emptySet(); | |
| 62 | + public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 63 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 64 | + if (processVariables == null) { | |
| 65 | + return Sets.newLinkedHashSet(); | |
| 66 | + } | |
| 67 | + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables); | |
| 68 | + if (startUserSelectAssignees == null) { | |
| 69 | + return Sets.newLinkedHashSet(); | |
| 62 | 70 | } |
| 63 | - Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); | |
| 64 | - Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId()); | |
| 65 | 71 | // 获得审批人 |
| 66 | 72 | List<Long> assignees = startUserSelectAssignees.get(activityId); |
| 67 | - Set<Long> users = new LinkedHashSet<>(assignees); | |
| 68 | - removeDisableUsers(users); | |
| 69 | - return users; | |
| 70 | - } | |
| 71 | - | |
| 72 | - @Override | |
| 73 | - public boolean isParamRequired() { | |
| 74 | - return false; | |
| 73 | + return new LinkedHashSet<>(assignees); | |
| 75 | 74 | } |
| 76 | 75 | |
| 77 | 76 | /** |
| 78 | - * 获得发起人自选审批人的 UserTask 列表 | |
| 77 | + * 获得发起人自选审批人或抄送人的 Task 列表 | |
| 79 | 78 | * |
| 80 | 79 | * @param bpmnModel BPMN 模型 |
| 81 | - * @return UserTask 列表 | |
| 80 | + * @return Task 列表 | |
| 82 | 81 | */ |
| 83 | - public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) { | |
| 82 | + public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) { | |
| 84 | 83 | if (bpmnModel == null) { |
| 85 | - return null; | |
| 84 | + return Collections.emptyList(); | |
| 86 | 85 | } |
| 87 | - List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); | |
| 88 | - if (CollUtil.isEmpty(userTaskList)) { | |
| 89 | - return null; | |
| 86 | + List<Task> tasks = new ArrayList<>(); | |
| 87 | + tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class)); | |
| 88 | + tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class)); | |
| 89 | + if (CollUtil.isEmpty(tasks)) { | |
| 90 | + return Collections.emptyList(); | |
| 90 | 91 | } |
| 91 | - userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask), | |
| 92 | + tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task), | |
| 92 | 93 | BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())); |
| 93 | - return userTaskList; | |
| 94 | + return tasks; | |
| 94 | 95 | } |
| 95 | 96 | |
| 96 | 97 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form; | |
| 2 | + | |
| 3 | +import cn.hutool.core.convert.Convert; | |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy; | |
| 7 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 8 | +import org.flowable.bpmn.model.BpmnModel; | |
| 9 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +import java.util.Map; | |
| 13 | +import java.util.Set; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 表单内部门负责人 {@link BpmTaskCandidateStrategy} 实现类 | |
| 17 | + * | |
| 18 | + * @author jason | |
| 19 | + */ | |
| 20 | +@Component | |
| 21 | +public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 25 | + return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER; | |
| 26 | + } | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public void validateParam(String param) { | |
| 30 | + // 参数格式: | 分隔:1)左边为表单内部门字段。2)右边为部门层级 | |
| 31 | + String[] params = param.split("\\|"); | |
| 32 | + Assert.isTrue(params.length == 2, "参数格式不匹配"); | |
| 33 | + Assert.notEmpty(param, "表单内部门字段不能为空"); | |
| 34 | + int level = Integer.parseInt(params[1]); | |
| 35 | + Assert.isTrue(level > 0, "部门层级必须大于 0"); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 40 | + String[] params = param.split("\\|"); | |
| 41 | + Object result = execution.getVariable(params[0]); | |
| 42 | + int level = Integer.parseInt(params[1]); | |
| 43 | + return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level); | |
| 44 | + } | |
| 45 | + | |
| 46 | + @Override | |
| 47 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, | |
| 48 | + String param, Long startUserId, String processDefinitionId, | |
| 49 | + Map<String, Object> processVariables) { | |
| 50 | + String[] params = param.split("\\|"); | |
| 51 | + Object result = processVariables == null ? null : processVariables.get(params[0]); | |
| 52 | + int level = Integer.parseInt(params[1]); | |
| 53 | + return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level); | |
| 54 | + } | |
| 55 | + | |
| 56 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form; | |
| 2 | + | |
| 3 | +import cn.hutool.core.convert.Convert; | |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; | |
| 7 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 8 | +import org.flowable.bpmn.model.BpmnModel; | |
| 9 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +import java.util.Map; | |
| 13 | +import java.util.Set; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 表单内用户字段 {@link BpmTaskCandidateUserStrategy} 实现类 | |
| 17 | + * | |
| 18 | + * @author jason | |
| 19 | + */ | |
| 20 | +@Component | |
| 21 | +public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy { | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 25 | + return BpmTaskCandidateStrategyEnum.FORM_USER; | |
| 26 | + } | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public void validateParam(String param) { | |
| 30 | + Assert.notEmpty(param, "表单内用户字段不能为空"); | |
| 31 | + } | |
| 32 | + | |
| 33 | + @Override | |
| 34 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 35 | + Object result = execution.getVariable(param); | |
| 36 | + return Convert.toSet(Long.class, result); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, | |
| 41 | + String param, Long startUserId, String processDefinitionId, | |
| 42 | + Map<String, Object> processVariables) { | |
| 43 | + Object result = processVariables == null ? null : processVariables.get(param); | |
| 44 | + return Convert.toSet(Long.class, result); | |
| 45 | + } | |
| 46 | + | |
| 47 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| ... | ... | @@ -7,13 +7,15 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| 10 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | 10 | import jakarta.annotation.Resource; |
| 11 | +import org.flowable.bpmn.model.BpmnModel; | |
| 12 | +import org.flowable.bpmn.model.FlowElement; | |
| 12 | 13 | import org.flowable.engine.delegate.DelegateExecution; |
| 13 | 14 | import org.springframework.context.annotation.Lazy; |
| 14 | 15 | import org.springframework.stereotype.Component; |
| 15 | 16 | |
| 16 | 17 | import java.util.HashSet; |
| 18 | +import java.util.Map; | |
| 17 | 19 | import java.util.Objects; |
| 18 | 20 | import java.util.Set; |
| 19 | 21 | |
| ... | ... | @@ -23,16 +25,12 @@ import java.util.Set; |
| 23 | 25 | * @author kyle |
| 24 | 26 | */ |
| 25 | 27 | @Component |
| 26 | -public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 28 | +public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy { | |
| 27 | 29 | |
| 28 | 30 | @Resource |
| 29 | 31 | @Lazy // 延迟加载,避免循环依赖 |
| 30 | 32 | private BpmProcessDefinitionService processDefinitionService; |
| 31 | 33 | |
| 32 | - public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) { | |
| 33 | - super(adminUserApi); | |
| 34 | - } | |
| 35 | - | |
| 36 | 34 | @Override |
| 37 | 35 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 38 | 36 | return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY; |
| ... | ... | @@ -43,19 +41,28 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac |
| 43 | 41 | } |
| 44 | 42 | |
| 45 | 43 | @Override |
| 46 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 44 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 45 | + return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement()); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 50 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 51 | + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); | |
| 52 | + return getCandidateUsers(processDefinitionId, flowElement); | |
| 53 | + } | |
| 54 | + | |
| 55 | + private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) { | |
| 47 | 56 | // 情况一:指定人员审批 |
| 48 | - Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement()); | |
| 57 | + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement); | |
| 49 | 58 | if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { |
| 50 | - Set<Long> users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); | |
| 51 | - removeDisableUsers(users); | |
| 52 | - return users; | |
| 59 | + return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement)); | |
| 53 | 60 | } |
| 54 | 61 | |
| 55 | 62 | // 情况二:流程管理员 |
| 56 | 63 | if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { |
| 57 | - BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId()); | |
| 58 | - Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId()); | |
| 64 | + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); | |
| 65 | + Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId); | |
| 59 | 66 | return new HashSet<>(processDefinition.getManagerUserIds()); |
| 60 | 67 | } |
| 61 | 68 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.convert.Convert; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 7 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 7 | +import org.flowable.bpmn.model.BpmnModel; | |
| 8 | 8 | import org.flowable.engine.delegate.DelegateExecution; |
| 9 | 9 | import org.springframework.stereotype.Component; |
| 10 | 10 | |
| 11 | +import java.util.Map; | |
| 11 | 12 | import java.util.Set; |
| 12 | 13 | |
| 13 | 14 | /** |
| ... | ... | @@ -16,11 +17,7 @@ import java.util.Set; |
| 16 | 17 | * @author 芋道源码 |
| 17 | 18 | */ |
| 18 | 19 | @Component |
| 19 | -public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 20 | - | |
| 21 | - public BpmTaskCandidateExpressionStrategy(AdminUserApi adminUserApi) { | |
| 22 | - super(adminUserApi); | |
| 23 | - } | |
| 20 | +public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy { | |
| 24 | 21 | |
| 25 | 22 | @Override |
| 26 | 23 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -33,11 +30,16 @@ public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstract |
| 33 | 30 | } |
| 34 | 31 | |
| 35 | 32 | @Override |
| 36 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 33 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 37 | 34 | Object result = FlowableUtils.getExpressionValue(execution, param); |
| 38 | - Set<Long> users = Convert.toSet(Long.class, result); | |
| 39 | - removeDisableUsers(users); | |
| 40 | - return users; | |
| 35 | + return Convert.toSet(Long.class, result); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 40 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 41 | + Object result = FlowableUtils.getExpressionValue(processVariables, param); | |
| 42 | + return Convert.toSet(Long.class, result); | |
| 41 | 43 | } |
| 42 | 44 | |
| 43 | 45 | } |
| 44 | 46 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; |
| 8 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 8 | +import jakarta.annotation.Resource; | |
| 9 | 9 | import org.springframework.stereotype.Component; |
| 10 | 10 | |
| 11 | 11 | import java.util.Collection; |
| ... | ... | @@ -20,14 +20,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 20 | 20 | * @author kyle |
| 21 | 21 | */ |
| 22 | 22 | @Component |
| 23 | -public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 23 | +public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { | |
| 24 | 24 | |
| 25 | - private final BpmUserGroupService userGroupService; | |
| 26 | - | |
| 27 | - public BpmTaskCandidateGroupStrategy(AdminUserApi adminUserApi, BpmUserGroupService userGroupService) { | |
| 28 | - super(adminUserApi); | |
| 29 | - this.userGroupService = userGroupService; | |
| 30 | - } | |
| 25 | + @Resource | |
| 26 | + private BpmUserGroupService userGroupService; | |
| 31 | 27 | |
| 32 | 28 | @Override |
| 33 | 29 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -37,7 +33,7 @@ public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrat |
| 37 | 33 | @Override |
| 38 | 34 | public void validateParam(String param) { |
| 39 | 35 | Set<Long> groupIds = StrUtils.splitToLongSet(param); |
| 40 | - userGroupService.getUserGroupList(groupIds); | |
| 36 | + userGroupService.validUserGroups(groupIds); | |
| 41 | 37 | } |
| 42 | 38 | |
| 43 | 39 | @Override | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat |
| 6 | 6 | import cn.iocoder.yudao.module.system.api.dept.PostApi; |
| 7 | 7 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 8 | 8 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 9 | +import jakarta.annotation.Resource; | |
| 9 | 10 | import org.springframework.stereotype.Component; |
| 10 | 11 | |
| 11 | 12 | import java.util.List; |
| ... | ... | @@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 19 | 20 | * @author kyle |
| 20 | 21 | */ |
| 21 | 22 | @Component |
| 22 | -public class BpmTaskCandidatePostStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 23 | +public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { | |
| 23 | 24 | |
| 24 | - private final PostApi postApi; | |
| 25 | - | |
| 26 | - public BpmTaskCandidatePostStrategy(AdminUserApi adminUserApi, PostApi postApi) { | |
| 27 | - super(adminUserApi); | |
| 28 | - this.postApi = postApi; | |
| 29 | - } | |
| 25 | + @Resource | |
| 26 | + private PostApi postApi; | |
| 27 | + @Resource | |
| 28 | + private AdminUserApi adminUserApi; | |
| 30 | 29 | |
| 31 | 30 | @Override |
| 32 | 31 | public BpmTaskCandidateStrategyEnum getStrategy() { | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 6 | 6 | import cn.iocoder.yudao.module.system.api.permission.PermissionApi; |
| 7 | 7 | import cn.iocoder.yudao.module.system.api.permission.RoleApi; |
| 8 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 9 | 8 | import jakarta.annotation.Resource; |
| 10 | 9 | import org.springframework.stereotype.Component; |
| 11 | 10 | |
| ... | ... | @@ -17,17 +16,13 @@ import java.util.Set; |
| 17 | 16 | * @author kyle |
| 18 | 17 | */ |
| 19 | 18 | @Component |
| 20 | -public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 19 | +public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { | |
| 21 | 20 | |
| 22 | 21 | @Resource |
| 23 | 22 | private RoleApi roleApi; |
| 24 | 23 | @Resource |
| 25 | 24 | private PermissionApi permissionApi; |
| 26 | 25 | |
| 27 | - public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) { | |
| 28 | - super(adminUserApi); | |
| 29 | - } | |
| 30 | - | |
| 31 | 26 | @Override |
| 32 | 27 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 33 | 28 | return BpmTaskCandidateStrategyEnum.ROLE; | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; |
| 4 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 4 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 5 | 6 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 6 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 7 | 7 | import jakarta.annotation.Resource; |
| 8 | +import org.flowable.bpmn.model.BpmnModel; | |
| 8 | 9 | import org.flowable.engine.delegate.DelegateExecution; |
| 9 | 10 | import org.flowable.engine.runtime.ProcessInstance; |
| 10 | 11 | import org.springframework.context.annotation.Lazy; |
| 11 | 12 | import org.springframework.stereotype.Component; |
| 12 | 13 | |
| 14 | +import java.util.Map; | |
| 13 | 15 | import java.util.Set; |
| 14 | 16 | |
| 15 | 17 | /** |
| ... | ... | @@ -20,16 +22,12 @@ import java.util.Set; |
| 20 | 22 | * @author jason |
| 21 | 23 | */ |
| 22 | 24 | @Component |
| 23 | -public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 25 | +public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy { | |
| 24 | 26 | |
| 25 | 27 | @Resource |
| 26 | 28 | @Lazy // 延迟加载,避免循环依赖 |
| 27 | 29 | private BpmProcessInstanceService processInstanceService; |
| 28 | 30 | |
| 29 | - public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) { | |
| 30 | - super(adminUserApi); | |
| 31 | - } | |
| 32 | - | |
| 33 | 31 | @Override |
| 34 | 32 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 35 | 33 | return BpmTaskCandidateStrategyEnum.START_USER; |
| ... | ... | @@ -45,18 +43,15 @@ public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractS |
| 45 | 43 | } |
| 46 | 44 | |
| 47 | 45 | @Override |
| 48 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 46 | + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
| 49 | 47 | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| 50 | - Set<Long> users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); | |
| 51 | - removeDisableUsers(users); | |
| 52 | - return users; | |
| 48 | + return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); | |
| 53 | 49 | } |
| 54 | 50 | |
| 55 | 51 | @Override |
| 56 | - public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 57 | - Set<Long> users = SetUtils.asSet(startUserId); | |
| 58 | - removeDisableUsers(users); | |
| 59 | - return users; | |
| 52 | + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
| 53 | + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
| 54 | + return SetUtils.asSet(startUserId); | |
| 60 | 55 | } |
| 61 | 56 | |
| 62 | 57 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.text.StrPool; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 7 | 7 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 8 | +import jakarta.annotation.Resource; | |
| 8 | 9 | import org.springframework.stereotype.Component; |
| 9 | 10 | |
| 10 | 11 | import java.util.LinkedHashSet; |
| 11 | -import java.util.Set; | |
| 12 | 12 | |
| 13 | 13 | /** |
| 14 | 14 | * 用户 {@link BpmTaskCandidateStrategy} 实现类 |
| ... | ... | @@ -16,11 +16,10 @@ import java.util.Set; |
| 16 | 16 | * @author kyle |
| 17 | 17 | */ |
| 18 | 18 | @Component |
| 19 | -public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 19 | +public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { | |
| 20 | 20 | |
| 21 | - public BpmTaskCandidateUserStrategy(AdminUserApi adminUserApi) { | |
| 22 | - super(adminUserApi); | |
| 23 | - } | |
| 21 | + @Resource | |
| 22 | + private AdminUserApi adminUserApi; | |
| 24 | 23 | |
| 25 | 24 | @Override |
| 26 | 25 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -33,7 +32,7 @@ public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrate |
| 33 | 32 | } |
| 34 | 33 | |
| 35 | 34 | @Override |
| 36 | - public Set<Long> calculateUsers(String param) { | |
| 35 | + public LinkedHashSet<Long> calculateUsers(String param) { | |
| 37 | 36 | return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); |
| 38 | 37 | } |
| 39 | 38 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java
| ... | ... | @@ -4,9 +4,10 @@ import org.flowable.common.engine.api.variable.VariableContainer; |
| 4 | 4 | import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction; |
| 5 | 5 | import org.springframework.stereotype.Component; |
| 6 | 6 | |
| 7 | -// TODO @jason:这个自定义转换的原因是啥呀? | |
| 8 | 7 | /** |
| 9 | - * 根据流程变量 variable 的类型, 转换参数的值 | |
| 8 | + * 根据流程变量 variable 的类型,转换参数的值 | |
| 9 | + * | |
| 10 | + * 目前用于 ConditionNodeConvert 的 buildConditionExpression 方法中 | |
| 10 | 11 | * |
| 11 | 12 | * @author jason |
| 12 | 13 | */ |
| ... | ... | @@ -20,11 +21,12 @@ public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVar |
| 20 | 21 | public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) { |
| 21 | 22 | Object variable = variableContainer.getVariable(variableName); |
| 22 | 23 | if (variable != null && parmaValue != null) { |
| 23 | - // 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串 | |
| 24 | + // 如果值不是字符串类型,流程变量的类型是字符串,把值转成字符串 | |
| 24 | 25 | if (!(parmaValue instanceof String) && variable instanceof String ) { |
| 25 | 26 | return parmaValue.toString(); |
| 26 | 27 | } |
| 27 | 28 | } |
| 28 | 29 | return parmaValue; |
| 29 | 30 | } |
| 31 | + | |
| 30 | 32 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
| ... | ... | @@ -29,6 +29,8 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { |
| 29 | 29 | START_USER_DEPT_LEADER(37, "发起人部门负责人"), |
| 30 | 30 | START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), |
| 31 | 31 | USER_GROUP(40, "用户组"), |
| 32 | + FORM_USER(50, "表单内用户字段"), | |
| 33 | + FORM_DEPT_LEADER(51, "表单内部门负责人"), | |
| 32 | 34 | EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager |
| 33 | 35 | ASSIGN_EMPTY(1, "审批人为空"), |
| 34 | 36 | ; | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
| ... | ... | @@ -52,7 +52,7 @@ public interface BpmnModelConstants { |
| 52 | 52 | */ |
| 53 | 53 | String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; |
| 54 | 54 | /** |
| 55 | - * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id | |
| 55 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的退回的任务 Id | |
| 56 | 56 | */ |
| 57 | 57 | String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; |
| 58 | 58 | |
| ... | ... | @@ -104,10 +104,6 @@ public interface BpmnModelConstants { |
| 104 | 104 | * BPMN Start Event Node Id |
| 105 | 105 | */ |
| 106 | 106 | String START_EVENT_NODE_ID = "StartEvent"; |
| 107 | - /** | |
| 108 | - * BPMN Start Event Node Name | |
| 109 | - */ | |
| 110 | - String START_EVENT_NODE_NAME = "开始"; | |
| 111 | 107 | |
| 112 | 108 | /** |
| 113 | 109 | * 发起人节点 ID | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
| ... | ... | @@ -29,7 +29,12 @@ public class BpmnVariableConstants { |
| 29 | 29 | * @see ProcessInstance#getProcessVariables() |
| 30 | 30 | */ |
| 31 | 31 | public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; |
| 32 | - | |
| 32 | + /** | |
| 33 | + * 流程实例的变量 - 发起用户 ID | |
| 34 | + * | |
| 35 | + * @see ProcessInstance#getProcessVariables() | |
| 36 | + */ | |
| 37 | + public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID"; | |
| 33 | 38 | /** |
| 34 | 39 | * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} |
| 35 | 40 | * | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; | |
| 2 | - | |
| 3 | -// TODO @jason:要不合并到 BpmnModelConstants 那 | |
| 4 | -/** | |
| 5 | - * 仿钉钉快搭 JSON 常量信息 | |
| 6 | - * | |
| 7 | - * @author jason | |
| 8 | - */ | |
| 9 | -public interface SimpleModelConstants { | |
| 10 | - | |
| 11 | - // TODO @芋艿:条件表达式的字段名 | |
| 12 | - | |
| 13 | - /** | |
| 14 | - * 网关节点默认序列流属性 | |
| 15 | - */ | |
| 16 | - String DEFAULT_FLOW_ATTRIBUTE = "defaultFlow"; | |
| 17 | - | |
| 18 | - /** | |
| 19 | - * 条件节点的条件类型属性 | |
| 20 | - */ | |
| 21 | - String CONDITION_TYPE_ATTRIBUTE = "conditionType"; | |
| 22 | - | |
| 23 | - /** | |
| 24 | - * 条件节点条件表达式属性 | |
| 25 | - */ | |
| 26 | - String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression"; | |
| 27 | - | |
| 28 | - /** | |
| 29 | - * 条件规则的条件组属性 | |
| 30 | - */ | |
| 31 | - String CONDITION_GROUPS_ATTRIBUTE = "conditionGroups"; | |
| 32 | - | |
| 33 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java
| ... | ... | @@ -15,8 +15,8 @@ import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCo |
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * 处理抄送用户的 {@link JavaDelegate} 的实现类 |
| 18 | - * | |
| 19 | - * 目前只有快搭模式的【抄送节点】使用 | |
| 18 | + * <p> | |
| 19 | + * 目前只有仿钉钉/飞书模式的【抄送节点】使用 | |
| 20 | 20 | * |
| 21 | 21 | * @author jason |
| 22 | 22 | */ |
| ... | ... | @@ -34,14 +34,14 @@ public class BpmCopyTaskDelegate implements JavaDelegate { |
| 34 | 34 | @Override |
| 35 | 35 | public void execute(DelegateExecution execution) { |
| 36 | 36 | // 1. 获得抄送人 |
| 37 | - Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution); | |
| 37 | + Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution); | |
| 38 | 38 | if (CollUtil.isEmpty(userIds)) { |
| 39 | 39 | return; |
| 40 | 40 | } |
| 41 | 41 | // 2. 执行抄送 |
| 42 | 42 | FlowElement currentFlowElement = execution.getCurrentFlowElement(); |
| 43 | - processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), | |
| 44 | - currentFlowElement.getId(), null, currentFlowElement.getName()); | |
| 43 | + processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(), | |
| 44 | + currentFlowElement.getId(), currentFlowElement.getName(), null); | |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
| ... | ... | @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 10 | 10 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |
| 11 | -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; | |
| 12 | 11 | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| 13 | 12 | import com.google.common.collect.ImmutableSet; |
| 14 | 13 | import jakarta.annotation.Resource; |
| ... | ... | @@ -44,9 +43,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 44 | 43 | @Resource |
| 45 | 44 | @Lazy // 解决循环依赖 |
| 46 | 45 | private BpmTaskService taskService; |
| 47 | - @Resource | |
| 48 | - @Lazy // 解决循环依赖 | |
| 49 | - private BpmActivityService activityService; | |
| 50 | 46 | |
| 51 | 47 | public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() |
| 52 | 48 | .add(FlowableEngineEventType.TASK_CREATED) |
| ... | ... | @@ -72,7 +68,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 72 | 68 | |
| 73 | 69 | @Override |
| 74 | 70 | protected void activityCancelled(FlowableActivityCancelledEvent event) { |
| 75 | - List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); | |
| 71 | + List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId()); | |
| 76 | 72 | if (CollUtil.isEmpty(activityList)) { |
| 77 | 73 | log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); |
| 78 | 74 | return; |
| ... | ... | @@ -87,6 +83,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 87 | 83 | } |
| 88 | 84 | |
| 89 | 85 | @Override |
| 86 | + @SuppressWarnings("PatternVariableCanBeUsed") | |
| 90 | 87 | protected void timerFired(FlowableEngineEntityEvent event) { |
| 91 | 88 | // 1.1 只处理 BoundaryEvent 边界计时时间 |
| 92 | 89 | String processDefinitionId = event.getProcessDefinitionId(); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| ... | ... | @@ -3,27 +3,116 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.map.MapUtil; |
| 5 | 5 | import cn.hutool.core.util.ArrayUtil; |
| 6 | +import cn.hutool.core.util.ObjUtil; | |
| 6 | 7 | import cn.hutool.core.util.StrUtil; |
| 8 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 7 | 9 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 8 | 10 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 11 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 9 | 12 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| 13 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; | |
| 14 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; | |
| 15 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | |
| 10 | 16 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; |
| 11 | 17 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 18 | +import com.google.common.collect.Maps; | |
| 19 | +import lombok.extern.slf4j.Slf4j; | |
| 12 | 20 | import org.flowable.bpmn.converter.BpmnXMLConverter; |
| 13 | 21 | import org.flowable.bpmn.model.Process; |
| 14 | 22 | import org.flowable.bpmn.model.*; |
| 23 | +import org.flowable.common.engine.api.FlowableException; | |
| 15 | 24 | import org.flowable.common.engine.impl.util.io.BytesStreamSource; |
| 16 | 25 | |
| 17 | 26 | import java.util.*; |
| 18 | 27 | |
| 19 | 28 | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; |
| 20 | 29 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; |
| 30 | +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; | |
| 21 | 31 | |
| 22 | 32 | /** |
| 23 | - * 流程模型转操作工具类 | |
| 33 | + * BPMN Model 操作工具类。目前分成三部分: | |
| 34 | + * | |
| 35 | + * 1. BPMN 修改 + 解析元素相关的方法 | |
| 36 | + * 2. BPMN 简单查找相关的方法 | |
| 37 | + * 3. BPMN 复杂遍历相关的方法 | |
| 38 | + * 4. BPMN 流程预测相关的方法 | |
| 39 | + * | |
| 40 | + * @author 芋道源码 | |
| 24 | 41 | */ |
| 42 | +@Slf4j | |
| 25 | 43 | public class BpmnModelUtils { |
| 26 | 44 | |
| 45 | + // ========== BPMN 修改 + 解析元素相关的方法 ========== | |
| 46 | + | |
| 47 | + public static void addExtensionElement(FlowElement element, String name, String value) { | |
| 48 | + if (value == null) { | |
| 49 | + return; | |
| 50 | + } | |
| 51 | + ExtensionElement extensionElement = new ExtensionElement(); | |
| 52 | + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 53 | + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); | |
| 54 | + extensionElement.setElementText(value); | |
| 55 | + extensionElement.setName(name); | |
| 56 | + element.addExtensionElement(extensionElement); | |
| 57 | + } | |
| 58 | + | |
| 59 | + public static void addExtensionElement(FlowElement element, String name, Integer value) { | |
| 60 | + if (value == null) { | |
| 61 | + return; | |
| 62 | + } | |
| 63 | + addExtensionElement(element, name, String.valueOf(value)); | |
| 64 | + } | |
| 65 | + | |
| 66 | + public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) { | |
| 67 | + if (attributes == null) { | |
| 68 | + return; | |
| 69 | + } | |
| 70 | + ExtensionElement extensionElement = new ExtensionElement(); | |
| 71 | + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 72 | + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); | |
| 73 | + extensionElement.setName(name); | |
| 74 | + attributes.forEach((key, value) -> { | |
| 75 | + ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value); | |
| 76 | + extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 77 | + extensionElement.addAttribute(extensionAttribute); | |
| 78 | + }); | |
| 79 | + element.addExtensionElement(extensionElement); | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** | |
| 83 | + * 解析扩展元素 | |
| 84 | + * | |
| 85 | + * @param flowElement 节点 | |
| 86 | + * @param elementName 元素名称 | |
| 87 | + * @return 扩展元素 | |
| 88 | + */ | |
| 89 | + public static String parseExtensionElement(FlowElement flowElement, String elementName) { | |
| 90 | + if (flowElement == null) { | |
| 91 | + return null; | |
| 92 | + } | |
| 93 | + ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); | |
| 94 | + return element != null ? element.getElementText() : null; | |
| 95 | + } | |
| 96 | + | |
| 97 | + /** | |
| 98 | + * 给节点添加候选人元素 | |
| 99 | + * | |
| 100 | + * @param candidateStrategy 候选人策略 | |
| 101 | + * @param candidateParam 候选人参数,允许空 | |
| 102 | + * @param flowElement 节点 | |
| 103 | + */ | |
| 104 | + public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) { | |
| 105 | + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, | |
| 106 | + candidateStrategy == null ? null : candidateStrategy.toString()); | |
| 107 | + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** | |
| 111 | + * 解析候选人策略 | |
| 112 | + * | |
| 113 | + * @param userTask 任务节点 | |
| 114 | + * @return 候选人策略 | |
| 115 | + */ | |
| 27 | 116 | public static Integer parseCandidateStrategy(FlowElement userTask) { |
| 28 | 117 | Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( |
| 29 | 118 | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); |
| ... | ... | @@ -35,6 +124,12 @@ public class BpmnModelUtils { |
| 35 | 124 | return candidateStrategy; |
| 36 | 125 | } |
| 37 | 126 | |
| 127 | + /** | |
| 128 | + * 解析候选人参数 | |
| 129 | + * | |
| 130 | + * @param userTask 任务节点 | |
| 131 | + * @return 候选人参数 | |
| 132 | + */ | |
| 38 | 133 | public static String parseCandidateParam(FlowElement userTask) { |
| 39 | 134 | String candidateParam = userTask.getAttributeValue( |
| 40 | 135 | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); |
| ... | ... | @@ -45,39 +140,130 @@ public class BpmnModelUtils { |
| 45 | 140 | return candidateParam; |
| 46 | 141 | } |
| 47 | 142 | |
| 143 | + /** | |
| 144 | + * 解析审批类型 | |
| 145 | + * | |
| 146 | + * @see BpmUserTaskApproveTypeEnum | |
| 147 | + * @param userTask 任务节点 | |
| 148 | + * @return 审批类型 | |
| 149 | + */ | |
| 48 | 150 | public static Integer parseApproveType(FlowElement userTask) { |
| 49 | 151 | return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE)); |
| 50 | 152 | } |
| 51 | 153 | |
| 154 | + /** | |
| 155 | + * 添加任务拒绝处理元素 | |
| 156 | + * | |
| 157 | + * @param rejectHandler 任务拒绝处理 | |
| 158 | + * @param userTask 任务节点 | |
| 159 | + */ | |
| 160 | + public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) { | |
| 161 | + if (rejectHandler == null) { | |
| 162 | + return; | |
| 163 | + } | |
| 164 | + addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); | |
| 165 | + addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); | |
| 166 | + } | |
| 167 | + | |
| 168 | + /** | |
| 169 | + * 解析任务拒绝处理类型 | |
| 170 | + * | |
| 171 | + * @param userTask 任务节点 | |
| 172 | + * @return 任务拒绝处理类型 | |
| 173 | + */ | |
| 52 | 174 | public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { |
| 53 | 175 | Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); |
| 54 | 176 | return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); |
| 55 | 177 | } |
| 56 | 178 | |
| 179 | + /** | |
| 180 | + * 解析任务拒绝返回任务节点 ID | |
| 181 | + * | |
| 182 | + * @param flowElement 任务节点 | |
| 183 | + * @return 任务拒绝返回任务节点 ID | |
| 184 | + */ | |
| 57 | 185 | public static String parseReturnTaskId(FlowElement flowElement) { |
| 58 | 186 | return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); |
| 59 | 187 | } |
| 60 | 188 | |
| 189 | + /** | |
| 190 | + * 给节点添加用户任务的审批人与发起人相同时,处理类型枚举 | |
| 191 | + * | |
| 192 | + * @see BpmUserTaskAssignStartUserHandlerTypeEnum | |
| 193 | + * @param assignStartUserHandlerType 发起人处理类型 | |
| 194 | + * @param userTask 任务节点 | |
| 195 | + */ | |
| 196 | + public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { | |
| 197 | + if (assignStartUserHandlerType == null) { | |
| 198 | + return; | |
| 199 | + } | |
| 200 | + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); | |
| 201 | + } | |
| 202 | + | |
| 203 | + /** | |
| 204 | + * 给节点添加用户任务的审批人为空时,处理类型枚举 | |
| 205 | + * | |
| 206 | + * @see BpmUserTaskAssignEmptyHandlerTypeEnum | |
| 207 | + * @param emptyHandler 空处理 | |
| 208 | + * @param userTask 任务节点 | |
| 209 | + */ | |
| 210 | + public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) { | |
| 211 | + if (emptyHandler == null) { | |
| 212 | + return; | |
| 213 | + } | |
| 214 | + addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType())); | |
| 215 | + addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds())); | |
| 216 | + } | |
| 217 | + | |
| 218 | + /** | |
| 219 | + * 解析用户任务的审批人与发起人相同时,处理类型枚举 | |
| 220 | + * | |
| 221 | + * @param userTask 任务节点 | |
| 222 | + * @return 处理类型枚举 | |
| 223 | + */ | |
| 61 | 224 | public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { |
| 62 | 225 | return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); |
| 63 | 226 | } |
| 64 | 227 | |
| 228 | + /** | |
| 229 | + * 解析用户任务的审批人为空时,处理类型枚举 | |
| 230 | + * | |
| 231 | + * @param userTask 任务节点 | |
| 232 | + * @return 处理类型枚举 | |
| 233 | + */ | |
| 65 | 234 | public static Integer parseAssignEmptyHandlerType(FlowElement userTask) { |
| 66 | 235 | return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE)); |
| 67 | 236 | } |
| 68 | 237 | |
| 238 | + /** | |
| 239 | + * 解析用户任务的审批人为空时,处理用户 ID 数组 | |
| 240 | + * | |
| 241 | + * @param userTask 任务节点 | |
| 242 | + * @return 处理用户 ID 数组 | |
| 243 | + */ | |
| 69 | 244 | public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) { |
| 70 | 245 | return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); |
| 71 | 246 | } |
| 72 | 247 | |
| 73 | - public static String parseExtensionElement(FlowElement flowElement, String elementName) { | |
| 74 | - if (flowElement == null) { | |
| 75 | - return null; | |
| 248 | + /** | |
| 249 | + * 给节点添加表单字段权限元素 | |
| 250 | + * | |
| 251 | + * @param fieldsPermissions 表单字段权限 | |
| 252 | + * @param flowElement 节点 | |
| 253 | + */ | |
| 254 | + public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) { | |
| 255 | + if (CollUtil.isNotEmpty(fieldsPermissions)) { | |
| 256 | + fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); | |
| 76 | 257 | } |
| 77 | - ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); | |
| 78 | - return element != null ? element.getElementText() : null; | |
| 79 | 258 | } |
| 80 | 259 | |
| 260 | + /** | |
| 261 | + * 解析表单字段权限 | |
| 262 | + * | |
| 263 | + * @param bpmnModel bpmnModel 对象 | |
| 264 | + * @param flowElementId 元素 ID | |
| 265 | + * @return 表单字段权限 | |
| 266 | + */ | |
| 81 | 267 | public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { |
| 82 | 268 | if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { |
| 83 | 269 | return null; |
| ... | ... | @@ -101,6 +287,29 @@ public class BpmnModelUtils { |
| 101 | 287 | return fieldsPermission; |
| 102 | 288 | } |
| 103 | 289 | |
| 290 | + /** | |
| 291 | + * 给节点添加操作按钮设置元素 | |
| 292 | + */ | |
| 293 | + public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) { | |
| 294 | + if (CollUtil.isNotEmpty(buttonsSetting)) { | |
| 295 | + List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> { | |
| 296 | + Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3); | |
| 297 | + settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId())); | |
| 298 | + settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName()); | |
| 299 | + settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable())); | |
| 300 | + return settingMap; | |
| 301 | + }); | |
| 302 | + list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item)); | |
| 303 | + } | |
| 304 | + } | |
| 305 | + | |
| 306 | + /** | |
| 307 | + * 解析操作按钮设置 | |
| 308 | + * | |
| 309 | + * @param bpmnModel bpmnModel 对象 | |
| 310 | + * @param flowElementId 元素 ID | |
| 311 | + * @return 操作按钮设置 | |
| 312 | + */ | |
| 104 | 313 | public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { |
| 105 | 314 | FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); |
| 106 | 315 | if (flowElement == null) { |
| ... | ... | @@ -110,7 +319,7 @@ public class BpmnModelUtils { |
| 110 | 319 | if (CollUtil.isEmpty(extensionElements)) { |
| 111 | 320 | return null; |
| 112 | 321 | } |
| 113 | - Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = MapUtil.newHashMap(16); | |
| 322 | + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size()); | |
| 114 | 323 | extensionElements.forEach(element -> { |
| 115 | 324 | String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); |
| 116 | 325 | String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); |
| ... | ... | @@ -124,6 +333,23 @@ public class BpmnModelUtils { |
| 124 | 333 | } |
| 125 | 334 | |
| 126 | 335 | /** |
| 336 | + * 解析边界事件扩展元素 | |
| 337 | + * | |
| 338 | + * @param boundaryEvent 边界事件 | |
| 339 | + * @param customElement 元素 | |
| 340 | + * @return 扩展元素 | |
| 341 | + */ | |
| 342 | + public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { | |
| 343 | + if (boundaryEvent == null) { | |
| 344 | + return null; | |
| 345 | + } | |
| 346 | + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement)); | |
| 347 | + return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null); | |
| 348 | + } | |
| 349 | + | |
| 350 | + // ========== BPM 简单查找相关的方法 ========== | |
| 351 | + | |
| 352 | + /** | |
| 127 | 353 | * 根据节点,获取入口连线 |
| 128 | 354 | * |
| 129 | 355 | * @param source 起始节点 |
| ... | ... | @@ -168,15 +394,14 @@ public class BpmnModelUtils { |
| 168 | 394 | * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 |
| 169 | 395 | * @return 元素们 |
| 170 | 396 | */ |
| 397 | + @SuppressWarnings("unchecked") | |
| 171 | 398 | public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { |
| 172 | 399 | List<T> result = new ArrayList<>(); |
| 173 | - model.getProcesses().forEach(process -> { | |
| 174 | - process.getFlowElements().forEach(flowElement -> { | |
| 175 | - if (flowElement.getClass().isAssignableFrom(clazz)) { | |
| 176 | - result.add((T) flowElement); | |
| 177 | - } | |
| 178 | - }); | |
| 179 | - }); | |
| 400 | + model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> { | |
| 401 | + if (flowElement.getClass().isAssignableFrom(clazz)) { | |
| 402 | + result.add((T) flowElement); | |
| 403 | + } | |
| 404 | + })); | |
| 180 | 405 | return result; |
| 181 | 406 | } |
| 182 | 407 | |
| ... | ... | @@ -193,7 +418,7 @@ public class BpmnModelUtils { |
| 193 | 418 | |
| 194 | 419 | public static EndEvent getEndEvent(BpmnModel model) { |
| 195 | 420 | Process process = model.getMainProcess(); |
| 196 | - // 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题 | |
| 421 | + // 从 flowElementList 找 endEvent | |
| 197 | 422 | return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent); |
| 198 | 423 | } |
| 199 | 424 | |
| ... | ... | @@ -221,7 +446,7 @@ public class BpmnModelUtils { |
| 221 | 446 | return StrUtil.utf8Str(bpmnBytes); |
| 222 | 447 | } |
| 223 | 448 | |
| 224 | - // ========== 遍历相关的方法 ========== | |
| 449 | + // ========== BPMN 复杂遍历相关的方法 ========== | |
| 225 | 450 | |
| 226 | 451 | /** |
| 227 | 452 | * 找到 source 节点之前的所有用户任务节点 |
| ... | ... | @@ -316,16 +541,16 @@ public class BpmnModelUtils { |
| 316 | 541 | return userTaskList; |
| 317 | 542 | } |
| 318 | 543 | |
| 319 | - | |
| 320 | 544 | /** |
| 321 | 545 | * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 |
| 322 | - * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 | |
| 546 | + * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况 | |
| 323 | 547 | * |
| 324 | 548 | * @param source 起始节点 |
| 325 | 549 | * @param target 目标节点 |
| 326 | 550 | * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 |
| 327 | 551 | * @return 结果 |
| 328 | 552 | */ |
| 553 | + @SuppressWarnings("BooleanMethodIsAlwaysInverted") | |
| 329 | 554 | public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { |
| 330 | 555 | visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; |
| 331 | 556 | // 不能是开始事件和子流程 |
| ... | ... | @@ -436,11 +661,136 @@ public class BpmnModelUtils { |
| 436 | 661 | return userTaskList; |
| 437 | 662 | } |
| 438 | 663 | |
| 439 | - public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { | |
| 440 | - if (boundaryEvent == null) { | |
| 441 | - return null; | |
| 664 | + // ========== BPMN 流程预测相关的方法 ========== | |
| 665 | + | |
| 666 | + /** | |
| 667 | + * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果 | |
| 668 | + * | |
| 669 | + * @param bpmnModel BPMN 图 | |
| 670 | + * @param variables 变量 | |
| 671 | + * @return 节点元素数组 | |
| 672 | + */ | |
| 673 | + public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) { | |
| 674 | + List<FlowElement> resultElements = new ArrayList<>(); | |
| 675 | + Set<FlowElement> visitElements = new HashSet<>(); | |
| 676 | + | |
| 677 | + // 从 StartEvent 开始遍历 | |
| 678 | + StartEvent startEvent = getStartEvent(bpmnModel); | |
| 679 | + simulateNextFlowElements(startEvent, variables, resultElements, visitElements); | |
| 680 | + | |
| 681 | + // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中 | |
| 682 | + List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements, | |
| 683 | + flowElement -> flowElement instanceof EndEvent); | |
| 684 | + resultElements.addAll(endEvents); | |
| 685 | + return resultElements; | |
| 686 | + } | |
| 687 | + | |
| 688 | + @SuppressWarnings("PatternVariableCanBeUsed") | |
| 689 | + private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables, | |
| 690 | + List<FlowElement> resultElements, Set<FlowElement> visitElements) { | |
| 691 | + // 如果为空,或者已经遍历过,则直接结束 | |
| 692 | + if (currentElement == null) { | |
| 693 | + return; | |
| 694 | + } | |
| 695 | + if (visitElements.contains(currentElement)) { | |
| 696 | + return; | |
| 697 | + } | |
| 698 | + visitElements.add(currentElement); | |
| 699 | + | |
| 700 | + // 情况:StartEvent/EndEvent/UserTask/ServiceTask | |
| 701 | + if (currentElement instanceof StartEvent | |
| 702 | + || currentElement instanceof EndEvent | |
| 703 | + || currentElement instanceof UserTask | |
| 704 | + || currentElement instanceof ServiceTask) { | |
| 705 | + // 添加元素 | |
| 706 | + FlowNode flowNode = (FlowNode) currentElement; | |
| 707 | + resultElements.add(flowNode); | |
| 708 | + // 遍历子节点 | |
| 709 | + flowNode.getOutgoingFlows().forEach( | |
| 710 | + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); | |
| 711 | + return; | |
| 712 | + } | |
| 713 | + | |
| 714 | + // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的 | |
| 715 | + if (currentElement instanceof ExclusiveGateway) { | |
| 716 | + // 查找满足条件的 SequenceFlow 路径 | |
| 717 | + Gateway gateway = (Gateway) currentElement; | |
| 718 | + SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), | |
| 719 | + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) | |
| 720 | + && evalConditionExpress(variables, flow.getConditionExpression())); | |
| 721 | + if (matchSequenceFlow == null) { | |
| 722 | + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), | |
| 723 | + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())); | |
| 724 | + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 | |
| 725 | + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) { | |
| 726 | + matchSequenceFlow = gateway.getOutgoingFlows().get(0); | |
| 727 | + } | |
| 728 | + } | |
| 729 | + // 遍历满足条件的 SequenceFlow 路径 | |
| 730 | + if (matchSequenceFlow != null) { | |
| 731 | + simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements); | |
| 732 | + } | |
| 733 | + return; | |
| 734 | + } | |
| 735 | + | |
| 736 | + // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的 | |
| 737 | + if (currentElement instanceof InclusiveGateway) { | |
| 738 | + // 查找满足条件的 SequenceFlow 路径 | |
| 739 | + Gateway gateway = (Gateway) currentElement; | |
| 740 | + Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), | |
| 741 | + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) | |
| 742 | + && evalConditionExpress(variables, flow.getConditionExpression())); | |
| 743 | + if (CollUtil.isEmpty(matchSequenceFlows)) { | |
| 744 | + matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), | |
| 745 | + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())); | |
| 746 | + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 | |
| 747 | + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) { | |
| 748 | + matchSequenceFlows = gateway.getOutgoingFlows(); | |
| 749 | + } | |
| 750 | + } | |
| 751 | + // 遍历满足条件的 SequenceFlow 路径 | |
| 752 | + matchSequenceFlows.forEach( | |
| 753 | + flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements)); | |
| 754 | + } | |
| 755 | + | |
| 756 | + // 情况:ParallelGateway 并行,都满足,都走 | |
| 757 | + if (currentElement instanceof ParallelGateway) { | |
| 758 | + Gateway gateway = (Gateway) currentElement; | |
| 759 | + // 遍历子节点 | |
| 760 | + gateway.getOutgoingFlows().forEach( | |
| 761 | + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); | |
| 762 | + return; | |
| 763 | + } | |
| 764 | + } | |
| 765 | + | |
| 766 | + /** | |
| 767 | + * 计算条件表达式是否为 true 满足条件 | |
| 768 | + * | |
| 769 | + * @param variables 流程实例 | |
| 770 | + * @param express 条件表达式 | |
| 771 | + * @return 是否满足条件 | |
| 772 | + */ | |
| 773 | + public static boolean evalConditionExpress(Map<String, Object> variables, String express) { | |
| 774 | + if (express == null) { | |
| 775 | + return Boolean.FALSE; | |
| 776 | + } | |
| 777 | + try { | |
| 778 | + Object result = FlowableUtils.getExpressionValue(variables, express); | |
| 779 | + return Boolean.TRUE.equals(result); | |
| 780 | + } catch (FlowableException ex) { | |
| 781 | + log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, variables, ex); | |
| 782 | + return Boolean.FALSE; | |
| 442 | 783 | } |
| 443 | - ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement)); | |
| 444 | - return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null); | |
| 445 | 784 | } |
| 785 | + | |
| 786 | + @SuppressWarnings("PatternVariableCanBeUsed") | |
| 787 | + public static boolean isSequentialUserTask(FlowElement flowElement) { | |
| 788 | + if (!(flowElement instanceof UserTask)) { | |
| 789 | + return false; | |
| 790 | + } | |
| 791 | + UserTask userTask = (UserTask) flowElement; | |
| 792 | + MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics(); | |
| 793 | + return loopCharacteristics != null && loopCharacteristics.isSequential(); | |
| 794 | + } | |
| 795 | + | |
| 446 | 796 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.ObjectUtil; |
| 4 | +import cn.hutool.extra.spring.SpringUtil; | |
| 4 | 5 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 5 | 6 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |
| 6 | 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| ... | ... | @@ -8,6 +9,8 @@ import org.flowable.common.engine.api.delegate.Expression; |
| 8 | 9 | import org.flowable.common.engine.api.variable.VariableContainer; |
| 9 | 10 | import org.flowable.common.engine.impl.el.ExpressionManager; |
| 10 | 11 | import org.flowable.common.engine.impl.identity.Authentication; |
| 12 | +import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer; | |
| 13 | +import org.flowable.engine.ManagementService; | |
| 11 | 14 | import org.flowable.engine.ProcessEngineConfiguration; |
| 12 | 15 | import org.flowable.engine.history.HistoricProcessInstance; |
| 13 | 16 | import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; |
| ... | ... | @@ -95,15 +98,35 @@ public class FlowableUtils { |
| 95 | 98 | } |
| 96 | 99 | |
| 97 | 100 | /** |
| 101 | + * 获得流程实例的审批原因 | |
| 102 | + * | |
| 103 | + * @param processInstance 流程实例 | |
| 104 | + * @return 审批原因 | |
| 105 | + */ | |
| 106 | + public static String getProcessInstanceReason(HistoricProcessInstance processInstance) { | |
| 107 | + return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** | |
| 111 | + * 获得流程实例的表单 | |
| 112 | + * | |
| 113 | + * @param processInstance 流程实例 | |
| 114 | + * @return 表单 | |
| 115 | + */ | |
| 116 | + public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) { | |
| 117 | + Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables()); | |
| 118 | + return filterProcessInstanceFormVariable(processVariables); | |
| 119 | + } | |
| 120 | + | |
| 121 | + /** | |
| 98 | 122 | * 获得流程实例的表单 |
| 99 | 123 | * |
| 100 | 124 | * @param processInstance 流程实例 |
| 101 | 125 | * @return 表单 |
| 102 | 126 | */ |
| 103 | 127 | public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) { |
| 104 | - Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables()); | |
| 105 | - filterProcessInstanceFormVariable(formVariables); | |
| 106 | - return formVariables; | |
| 128 | + Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables()); | |
| 129 | + return filterProcessInstanceFormVariable(processVariables); | |
| 107 | 130 | } |
| 108 | 131 | |
| 109 | 132 | /** |
| ... | ... | @@ -125,9 +148,22 @@ public class FlowableUtils { |
| 125 | 148 | * @param processInstance 流程实例 |
| 126 | 149 | * @return 发起用户选择的审批人 Map |
| 127 | 150 | */ |
| 128 | - @SuppressWarnings("unchecked") | |
| 129 | 151 | public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) { |
| 130 | - return (Map<String, List<Long>>) processInstance.getProcessVariables().get( | |
| 152 | + return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null; | |
| 153 | + } | |
| 154 | + | |
| 155 | + /** | |
| 156 | + * 获得流程实例的发起用户选择的审批人 Map | |
| 157 | + * | |
| 158 | + * @param processVariables 流程变量 | |
| 159 | + * @return 发起用户选择的审批人 Map | |
| 160 | + */ | |
| 161 | + @SuppressWarnings("unchecked") | |
| 162 | + public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) { | |
| 163 | + if (processVariables == null) { | |
| 164 | + return null; | |
| 165 | + } | |
| 166 | + return (Map<String, List<Long>>) processVariables.get( | |
| 131 | 167 | BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); |
| 132 | 168 | } |
| 133 | 169 | |
| ... | ... | @@ -181,13 +217,30 @@ public class FlowableUtils { |
| 181 | 217 | |
| 182 | 218 | // ========== Expression 相关的工具方法 ========== |
| 183 | 219 | |
| 184 | - public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { | |
| 185 | - ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); | |
| 186 | - assert processEngineConfiguration != null; | |
| 220 | + private static Object getExpressionValue(VariableContainer variableContainer, String expressionString, | |
| 221 | + ProcessEngineConfigurationImpl processEngineConfiguration) { | |
| 222 | + assert processEngineConfiguration!= null; | |
| 187 | 223 | ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); |
| 188 | - assert expressionManager != null; | |
| 224 | + assert expressionManager!= null; | |
| 189 | 225 | Expression expression = expressionManager.createExpression(expressionString); |
| 190 | 226 | return expression.getValue(variableContainer); |
| 191 | 227 | } |
| 192 | 228 | |
| 229 | + public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { | |
| 230 | + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); | |
| 231 | + if (processEngineConfiguration != null) { | |
| 232 | + return getExpressionValue(variableContainer, expressionString, processEngineConfiguration); | |
| 233 | + } | |
| 234 | + // 如果 ProcessEngineConfigurationImpl 获取不到,则需要通过 ManagementService 来获取 | |
| 235 | + ManagementService managementService = SpringUtil.getBean(ManagementService.class); | |
| 236 | + assert managementService != null; | |
| 237 | + return managementService.executeCommand(context -> | |
| 238 | + getExpressionValue(variableContainer, expressionString, CommandContextUtil.getProcessEngineConfiguration())); | |
| 239 | + } | |
| 240 | + | |
| 241 | + public static Object getExpressionValue(Map<String, Object> variable, String expressionString) { | |
| 242 | + VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty()); | |
| 243 | + return getExpressionValue(variableContainer, expressionString); | |
| 244 | + } | |
| 245 | + | |
| 193 | 246 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
| ... | ... | @@ -7,64 +7,47 @@ import cn.hutool.core.util.*; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; |
| 10 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; | |
| 11 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | |
| 12 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; | |
| 13 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; | |
| 14 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum; | |
| 10 | +import cn.iocoder.yudao.module.bpm.enums.definition.*; | |
| 11 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 15 | 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 16 | 13 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; |
| 17 | 14 | import org.flowable.bpmn.BpmnAutoLayout; |
| 15 | +import org.flowable.bpmn.constants.BpmnXMLConstants; | |
| 18 | 16 | import org.flowable.bpmn.model.Process; |
| 19 | 17 | import org.flowable.bpmn.model.*; |
| 20 | 18 | |
| 21 | -import java.util.ArrayList; | |
| 22 | -import java.util.List; | |
| 23 | -import java.util.Map; | |
| 24 | -import java.util.Objects; | |
| 25 | - | |
| 26 | -import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; | |
| 27 | -import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; | |
| 28 | -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; | |
| 29 | -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.*; | |
| 30 | -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; | |
| 31 | -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP; | |
| 32 | -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER; | |
| 33 | -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; | |
| 19 | +import java.util.*; | |
| 20 | + | |
| 34 | 21 | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; |
| 35 | -import static org.flowable.bpmn.constants.BpmnXMLConstants.*; | |
| 22 | +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; | |
| 23 | +import static java.util.Arrays.asList; | |
| 36 | 24 | |
| 37 | 25 | /** |
| 38 | - * 仿钉钉快搭模型相关的工具方法 | |
| 26 | + * 仿钉钉/飞书的模型相关的工具方法 | |
| 27 | + * <p> | |
| 28 | + * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法 | |
| 29 | + * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类 | |
| 39 | 30 | * |
| 40 | 31 | * @author jason |
| 41 | 32 | */ |
| 42 | 33 | public class SimpleModelUtils { |
| 43 | 34 | |
| 44 | - /** | |
| 45 | - * 聚合网关节点 Id 后缀 | |
| 46 | - */ | |
| 47 | - public static final String JOIN_GATE_WAY_NODE_ID_SUFFIX = "_join"; | |
| 48 | - | |
| 49 | - /** | |
| 50 | - * 所有审批人同意的表达式 | |
| 51 | - */ | |
| 52 | - public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }"; | |
| 53 | - | |
| 54 | - /** | |
| 55 | - * 任一一名审批人同意的表达式 | |
| 56 | - */ | |
| 57 | - public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; | |
| 58 | - | |
| 59 | - /** | |
| 60 | - * 按通过比例完成表达式 | |
| 61 | - */ | |
| 62 | - public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; | |
| 35 | + private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap(); | |
| 63 | 36 | |
| 64 | - // TODO @yunai:注释需要完善下; | |
| 37 | + static { | |
| 38 | + List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), | |
| 39 | + new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), | |
| 40 | + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert()); | |
| 41 | + converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); | |
| 42 | + } | |
| 65 | 43 | |
| 66 | 44 | /** |
| 67 | - * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) | |
| 45 | + * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model | |
| 46 | + * <p> | |
| 47 | + * 整体逻辑如下: | |
| 48 | + * 1. 创建:BpmnModel、Process 对象 | |
| 49 | + * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素 | |
| 50 | + * 3. 连接:构建并添加节点之间的连线 Sequence Flow | |
| 68 | 51 | * |
| 69 | 52 | * @param processId 流程标识 |
| 70 | 53 | * @param processName 流程名称 |
| ... | ... | @@ -72,42 +55,76 @@ public class SimpleModelUtils { |
| 72 | 55 | * @return Bpmn Model |
| 73 | 56 | */ |
| 74 | 57 | public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { |
| 58 | + // 1. 创建 BpmnModel | |
| 75 | 59 | BpmnModel bpmnModel = new BpmnModel(); |
| 76 | - // 不加这个 解析 Message 会报 NPE 异常 . | |
| 77 | - bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace; | |
| 78 | - // TODO 芋艿:后续在 review | |
| 79 | - | |
| 60 | + bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常 | |
| 61 | + // 创建 Process 对象 | |
| 80 | 62 | Process process = new Process(); |
| 81 | 63 | process.setId(processId); |
| 82 | 64 | process.setName(processName); |
| 83 | - process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么? | |
| 65 | + process.setExecutable(Boolean.TRUE); | |
| 84 | 66 | bpmnModel.addProcess(process); |
| 85 | 67 | |
| 86 | - // TODO 芋艿:这里可能纠结下,到底前端传递,还是后端创建出来。 | |
| 87 | - // 目前前端的第一个节点是“发起人节点”。这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 | |
| 88 | - BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode(); | |
| 68 | + // 2.1 创建 StartNode 节点 | |
| 69 | + // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 | |
| 70 | + BpmSimpleModelNodeVO startNode = buildStartNode(); | |
| 89 | 71 | startNode.setChildNode(simpleModelNode); |
| 90 | - // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process | |
| 72 | + // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中 | |
| 91 | 73 | traverseNodeToBuildFlowNode(startNode, process); |
| 92 | - // 找到 end event | |
| 93 | - EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent); | |
| 94 | 74 | |
| 95 | - // 构建并添加节点之间的连线 Sequence Flow | |
| 75 | + // 3. 构建并添加节点之间的连线 Sequence Flow | |
| 76 | + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); | |
| 96 | 77 | traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); |
| 97 | - // 自动布局 | |
| 78 | + | |
| 79 | + // 4. 自动布局 | |
| 98 | 80 | new BpmnAutoLayout(bpmnModel).execute(); |
| 99 | 81 | return bpmnModel; |
| 100 | 82 | } |
| 101 | 83 | |
| 102 | - private static BpmSimpleModelNodeVO buildStartSimpleModelNode() { | |
| 103 | - BpmSimpleModelNodeVO startNode = new BpmSimpleModelNodeVO(); | |
| 104 | - startNode.setId(START_EVENT_NODE_ID); | |
| 105 | - startNode.setName(START_EVENT_NODE_NAME); | |
| 106 | - startNode.setType(START_NODE.getType()); | |
| 107 | - return startNode; | |
| 84 | + private static BpmSimpleModelNodeVO buildStartNode() { | |
| 85 | + return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID) | |
| 86 | + .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) | |
| 87 | + .setType(BpmSimpleModelNodeType.START_NODE.getType()); | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 遍历节点,构建 FlowNode 元素 | |
| 92 | + * | |
| 93 | + * @param node SIMPLE 节点 | |
| 94 | + * @param process BPMN 流程 | |
| 95 | + */ | |
| 96 | + private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { | |
| 97 | + // 1. 判断是否有效节点 | |
| 98 | + if (!isValidNode(node)) { | |
| 99 | + return; | |
| 100 | + } | |
| 101 | + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); | |
| 102 | + Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType()); | |
| 103 | + | |
| 104 | + // 2. 处理当前节点 | |
| 105 | + NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType); | |
| 106 | + Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType()); | |
| 107 | + List<? extends FlowElement> flowElements = nodeConvert.convertList(node); | |
| 108 | + flowElements.forEach(process::addFlowElement); | |
| 109 | + | |
| 110 | + // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点 | |
| 111 | + if (BpmSimpleModelNodeType.isBranchNode(node.getType()) | |
| 112 | + && CollUtil.isNotEmpty(node.getConditionNodes())) { | |
| 113 | + // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件 | |
| 114 | + node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); | |
| 115 | + } | |
| 116 | + | |
| 117 | + // 3.2 情况二:如果有“子”节点,则递归处理子节点 | |
| 118 | + traverseNodeToBuildFlowNode(node.getChildNode(), process); | |
| 108 | 119 | } |
| 109 | 120 | |
| 110 | - // TODO @芋艿:在优化下这个注释 | |
| 121 | + /** | |
| 122 | + * 遍历节点,构建 SequenceFlow 元素 | |
| 123 | + * | |
| 124 | + * @param process Bpmn 流程 | |
| 125 | + * @param node 当前节点 | |
| 126 | + * @param targetNodeId 目标节点 ID | |
| 127 | + */ | |
| 111 | 128 | private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { |
| 112 | 129 | // 1.1 无效节点返回 |
| 113 | 130 | if (!isValidNode(node)) { |
| ... | ... | @@ -116,516 +133,555 @@ public class SimpleModelUtils { |
| 116 | 133 | // 1.2 END_NODE 直接返回 |
| 117 | 134 | BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); |
| 118 | 135 | Assert.notNull(nodeType, "模型节点类型不支持"); |
| 119 | - if (nodeType == END_NODE) { | |
| 136 | + if (nodeType == BpmSimpleModelNodeType.END_NODE) { | |
| 120 | 137 | return; |
| 121 | 138 | } |
| 139 | + | |
| 122 | 140 | // 2.1 情况一:普通节点 |
| 123 | - BpmSimpleModelNodeVO childNode = node.getChildNode(); | |
| 124 | 141 | if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { |
| 125 | - if (!isValidNode(childNode)) { | |
| 126 | - // 2.1.1 普通节点且无孩子节点。分两种情况 | |
| 127 | - // a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线。 | |
| 128 | - if (StrUtil.isNotEmpty(node.getAttachNodeId())) { | |
| 129 | - // 2.1.1.1 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线 | |
| 130 | - List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), targetNodeId); | |
| 131 | - sequenceFlows.forEach(process::addFlowElement); | |
| 132 | - } else { | |
| 133 | - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null); | |
| 134 | - process.addFlowElement(sequenceFlow); | |
| 135 | - } | |
| 136 | - } else { | |
| 137 | - // 2.1.2 普通节点且有孩子节点。建立连线 | |
| 138 | - if (StrUtil.isNotEmpty(node.getAttachNodeId())) { | |
| 139 | - // 2.1.1.2 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线 | |
| 140 | - List<SequenceFlow> sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), childNode.getId()); | |
| 141 | - sequenceFlows.forEach(process::addFlowElement); | |
| 142 | - } else { | |
| 143 | - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null); | |
| 144 | - process.addFlowElement(sequenceFlow); | |
| 145 | - } | |
| 146 | - // 递归调用后续节点 | |
| 147 | - traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); | |
| 148 | - } | |
| 142 | + traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId); | |
| 149 | 143 | } else { |
| 150 | 144 | // 2.2 情况二:分支节点 |
| 151 | - List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes(); | |
| 152 | - Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); | |
| 153 | - // 分支终点节点 Id | |
| 154 | - String branchEndNodeId = null; | |
| 155 | - if (nodeType == CONDITION_BRANCH_NODE) { // 条件分支 | |
| 156 | - // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点Id | |
| 157 | - branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; | |
| 158 | - } else if (nodeType == PARALLEL_BRANCH_NODE) { // 并行分支 | |
| 159 | - // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 | |
| 160 | - branchEndNodeId = node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX; | |
| 161 | - } | |
| 162 | - // TODO 包容网关待实现 | |
| 163 | - Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); | |
| 164 | - // 3.1 遍历分支节点. 如下情况: | |
| 165 | - // 分支1、A->B->C->D->E 和 分支2、A->D->E。 A为分支节点, D为A孩子节点 | |
| 166 | - for (BpmSimpleModelNodeVO item : conditionNodes) { | |
| 167 | - // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件 | |
| 168 | - // @芋艿 这个是啥意思。 这里的 item 的节点类型为 BpmSimpleModelNodeType.CONDITION_NODE 类型,没有对应的 bpmn 的节点。 仅仅用于构建条件表达式。 | |
| 169 | - Assert.isTrue(Objects.equals(item.getType(), CONDITION_NODE.getType()), "条件节点类型不符合"); | |
| 170 | - // 构建表达式,可以为空. 并行分支为空 | |
| 171 | - String conditionExpression = buildConditionExpression(item); | |
| 172 | - BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); | |
| 173 | - // 3.2 分支有后续节点, 分支1: A->B->C->D | |
| 174 | - if (isValidNode(nextNodeOnCondition)) { | |
| 175 | - // 3.2.1 建立 A->B | |
| 176 | - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(), | |
| 177 | - item.getId(), item.getName(), conditionExpression); | |
| 178 | - process.addFlowElement(sequenceFlow); | |
| 179 | - // 3.2.2 递归调用后续节点连线。 建立 B->C->D 的连线 | |
| 180 | - traverseNodeToBuildSequenceFlow(process, nextNodeOnCondition, branchEndNodeId); | |
| 181 | - } else { | |
| 182 | - // 3.3 分支无后续节点 建立 A->D | |
| 183 | - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId, | |
| 184 | - item.getId(), item.getName(), conditionExpression); | |
| 185 | - process.addFlowElement(sequenceFlow); | |
| 186 | - } | |
| 187 | - } | |
| 188 | - // 如果是并行分支。由于是程序创建的聚合网关。需要手工创建聚合网关和下一个节点的连线 | |
| 189 | - if (nodeType == PARALLEL_BRANCH_NODE) { | |
| 190 | - String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; | |
| 191 | - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId, null, null, null); | |
| 192 | - process.addFlowElement(sequenceFlow); | |
| 193 | - } | |
| 194 | - // 4.递归调用后续节点 继续递归建立 D->E 的连线 | |
| 195 | - traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); | |
| 145 | + traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId); | |
| 196 | 146 | } |
| 197 | 147 | } |
| 198 | 148 | |
| 199 | 149 | /** |
| 200 | - * 构建有附加节点的连线 | |
| 150 | + * 遍历普通(非条件)节点,构建 SequenceFlow 元素 | |
| 201 | 151 | * |
| 202 | - * @param nodeId 当前节点 Id | |
| 203 | - * @param attachNodeId 附属节点 Id | |
| 204 | - * @param targetNodeId 目标节点 Id | |
| 152 | + * @param process Bpmn 流程 | |
| 153 | + * @param node 当前节点 | |
| 154 | + * @param targetNodeId 目标节点 ID | |
| 205 | 155 | */ |
| 206 | - private static List<SequenceFlow> buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) { | |
| 207 | - SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null); | |
| 208 | - SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null); | |
| 209 | - return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow); | |
| 156 | + private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { | |
| 157 | + BpmSimpleModelNodeVO childNode = node.getChildNode(); | |
| 158 | + boolean isChildNodeValid = isValidNode(childNode); | |
| 159 | + // 情况一:有“子”节点,则建立连线 | |
| 160 | + // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点 | |
| 161 | + String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId; | |
| 162 | + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId); | |
| 163 | + process.addFlowElement(sequenceFlow); | |
| 164 | + | |
| 165 | + // 因为有子节点,递归调用后续子节点 | |
| 166 | + if (isChildNodeValid) { | |
| 167 | + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); | |
| 168 | + } | |
| 210 | 169 | } |
| 211 | 170 | |
| 212 | 171 | /** |
| 213 | - * 构造条件表达式 | |
| 172 | + * 遍历条件节点,构建 SequenceFlow 元素 | |
| 214 | 173 | * |
| 215 | - * @param conditionNode 条件节点 | |
| 174 | + * @param process Bpmn 流程 | |
| 175 | + * @param node 当前节点 | |
| 176 | + * @param targetNodeId 目标节点 ID | |
| 216 | 177 | */ |
| 217 | - public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) { | |
| 218 | - BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionNode.getConditionType()); | |
| 219 | - String conditionExpression = null; | |
| 220 | - if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) { | |
| 221 | - conditionExpression = conditionNode.getConditionExpression(); | |
| 222 | - } else if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { | |
| 223 | - ConditionGroups conditionGroups = conditionNode.getConditionGroups(); | |
| 224 | - if (conditionGroups != null && CollUtil.isNotEmpty(conditionGroups.getConditions())) { | |
| 225 | - List<String> strConditionGroups = conditionGroups.getConditions().stream().map(item -> { | |
| 226 | - if (CollUtil.isNotEmpty(item.getRules())) { | |
| 227 | - Boolean and = item.getAnd(); | |
| 228 | - List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> { | |
| 229 | - // 如果非数值类型加引号 | |
| 230 | - String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() : "\"" + rule.getRightSide() + "\""; | |
| 231 | - return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide); | |
| 232 | - }); | |
| 233 | - return "(" + CollUtil.join(list, and ? " && " : " || ") + ")"; | |
| 234 | - } else { | |
| 235 | - return ""; | |
| 236 | - } | |
| 237 | - }).toList(); | |
| 238 | - conditionExpression = String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); | |
| 178 | + private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { | |
| 179 | + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); | |
| 180 | + BpmSimpleModelNodeVO childNode = node.getChildNode(); | |
| 181 | + List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes(); | |
| 182 | + Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); | |
| 183 | + // 分支终点节点 ID | |
| 184 | + String branchEndNodeId = null; | |
| 185 | + if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支 | |
| 186 | + // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID | |
| 187 | + branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; | |
| 188 | + } else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE | |
| 189 | + || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支 | |
| 190 | + // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 | |
| 191 | + branchEndNodeId = buildGatewayJoinId(node.getId()); | |
| 192 | + } | |
| 193 | + Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); | |
| 194 | + | |
| 195 | + // 3. 遍历分支节点 | |
| 196 | + // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点 | |
| 197 | + for (BpmSimpleModelNodeVO item : conditionNodes) { | |
| 198 | + Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()), | |
| 199 | + "条件节点类型({})不符合", item.getType()); | |
| 200 | + BpmSimpleModelNodeVO conditionChildNode = item.getChildNode(); | |
| 201 | + // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况 | |
| 202 | + if (isValidNode(conditionChildNode)) { | |
| 203 | + // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线 | |
| 204 | + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item); | |
| 205 | + process.addFlowElement(sequenceFlow); | |
| 206 | + // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线 | |
| 207 | + traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId); | |
| 208 | + } else { | |
| 209 | + // 3.2 分支没有后续节点。例如说,建立 A->D 的连线 | |
| 210 | + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item); | |
| 211 | + process.addFlowElement(sequenceFlow); | |
| 239 | 212 | } |
| 240 | 213 | } |
| 241 | - // TODO 待增加其它类型 | |
| 242 | - return conditionExpression; | |
| 214 | + | |
| 215 | + // 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线 | |
| 216 | + if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE | |
| 217 | + || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) { | |
| 218 | + String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; | |
| 219 | + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId); | |
| 220 | + process.addFlowElement(sequenceFlow); | |
| 221 | + } | |
| 222 | + | |
| 223 | + // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线 | |
| 224 | + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); | |
| 225 | + } | |
| 226 | + | |
| 227 | + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) { | |
| 228 | + return buildBpmnSequenceFlow(sourceId, targetId, null, null, null); | |
| 243 | 229 | } |
| 244 | 230 | |
| 245 | - private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) { | |
| 231 | + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, | |
| 232 | + String sequenceFlowId, String sequenceFlowName, | |
| 233 | + String conditionExpression) { | |
| 246 | 234 | Assert.notEmpty(sourceId, "sourceId 不能为空"); |
| 247 | 235 | Assert.notEmpty(targetId, "targetId 不能为空"); |
| 248 | - // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? @芋艿: 貌似不需要,Flowable 会默认生成 | |
| 249 | - // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧 | |
| 236 | + // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。 | |
| 237 | + // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。 | |
| 250 | 238 | SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); |
| 251 | - if (StrUtil.isNotEmpty(conditionExpression)) { | |
| 252 | - sequenceFlow.setConditionExpression(conditionExpression); | |
| 239 | + if (StrUtil.isNotEmpty(sequenceFlowId)) { | |
| 240 | + sequenceFlow.setId(sequenceFlowId); | |
| 253 | 241 | } |
| 254 | - if (StrUtil.isNotEmpty(seqFlowId)) { | |
| 255 | - sequenceFlow.setId(seqFlowId); | |
| 242 | + if (StrUtil.isNotEmpty(sequenceFlowName)) { | |
| 243 | + sequenceFlow.setName(sequenceFlowName); | |
| 256 | 244 | } |
| 257 | - if (StrUtil.isNotEmpty(seqName)) { | |
| 258 | - sequenceFlow.setName(seqName); | |
| 245 | + if (StrUtil.isNotEmpty(conditionExpression)) { | |
| 246 | + sequenceFlow.setConditionExpression(conditionExpression); | |
| 259 | 247 | } |
| 260 | 248 | return sequenceFlow; |
| 261 | 249 | } |
| 262 | 250 | |
| 263 | - // TODO @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow | |
| 264 | - private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { | |
| 265 | - // 判断是否有效节点 | |
| 266 | - if (!isValidNode(node)) { | |
| 267 | - return; | |
| 251 | + public static boolean isValidNode(BpmSimpleModelNodeVO node) { | |
| 252 | + return node != null && node.getId() != null; | |
| 253 | + } | |
| 254 | + | |
| 255 | + public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { | |
| 256 | + return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType()) | |
| 257 | + && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod()); | |
| 258 | + } | |
| 259 | + | |
| 260 | + // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ========== | |
| 261 | + | |
| 262 | + private interface NodeConvert { | |
| 263 | + | |
| 264 | + default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { | |
| 265 | + return Collections.singletonList(convert(node)); | |
| 268 | 266 | } |
| 269 | - BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); | |
| 270 | - Assert.notNull(nodeType, "模型节点类型不支持"); | |
| 271 | 267 | |
| 272 | - List<FlowElement> flowElements = buildFlowNode(node, nodeType); | |
| 273 | - flowElements.forEach(process::addFlowElement); | |
| 268 | + default FlowElement convert(BpmSimpleModelNodeVO node) { | |
| 269 | + throw new UnsupportedOperationException("请实现该方法"); | |
| 270 | + } | |
| 274 | 271 | |
| 275 | - // 如果不是网关类型的接口, 并且chileNode为空退出 | |
| 276 | - // 如果是“分支”节点,则递归处理条件 | |
| 277 | - if (BpmSimpleModelNodeType.isBranchNode(node.getType()) | |
| 278 | - && ArrayUtil.isNotEmpty(node.getConditionNodes())) { | |
| 279 | - node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); | |
| 272 | + BpmSimpleModelNodeType getType(); | |
| 273 | + | |
| 274 | + } | |
| 275 | + | |
| 276 | + private static class StartNodeConvert implements NodeConvert { | |
| 277 | + | |
| 278 | + @Override | |
| 279 | + public StartEvent convert(BpmSimpleModelNodeVO node) { | |
| 280 | + StartEvent startEvent = new StartEvent(); | |
| 281 | + startEvent.setId(node.getId()); | |
| 282 | + startEvent.setName(node.getName()); | |
| 283 | + return startEvent; | |
| 284 | + } | |
| 285 | + | |
| 286 | + @Override | |
| 287 | + public BpmSimpleModelNodeType getType() { | |
| 288 | + return BpmSimpleModelNodeType.START_NODE; | |
| 280 | 289 | } |
| 281 | 290 | |
| 282 | - // 如果有“子”节点,则递归处理子节点 | |
| 283 | - traverseNodeToBuildFlowNode(node.getChildNode(), process); | |
| 284 | 291 | } |
| 285 | 292 | |
| 286 | - public static boolean isValidNode(BpmSimpleModelNodeVO node) { | |
| 287 | - return node != null && node.getId() != null; | |
| 293 | + private static class EndNodeConvert implements NodeConvert { | |
| 294 | + | |
| 295 | + @Override | |
| 296 | + public EndEvent convert(BpmSimpleModelNodeVO node) { | |
| 297 | + EndEvent endEvent = new EndEvent(); | |
| 298 | + endEvent.setId(node.getId()); | |
| 299 | + endEvent.setName(node.getName()); | |
| 300 | + // TODO @芋艿 + jason:要不要加一个终止定义? | |
| 301 | + return endEvent; | |
| 302 | + } | |
| 303 | + | |
| 304 | + @Override | |
| 305 | + public BpmSimpleModelNodeType getType() { | |
| 306 | + return BpmSimpleModelNodeType.END_NODE; | |
| 307 | + } | |
| 308 | + | |
| 288 | 309 | } |
| 289 | 310 | |
| 290 | - public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { | |
| 291 | - return APPROVE_NODE.getType().equals(node.getType()) && SEQUENTIAL.getMethod().equals(node.getApproveMethod()); | |
| 311 | + private static class StartUserNodeConvert implements NodeConvert { | |
| 312 | + | |
| 313 | + @Override | |
| 314 | + public UserTask convert(BpmSimpleModelNodeVO node) { | |
| 315 | + UserTask userTask = new UserTask(); | |
| 316 | + userTask.setId(node.getId()); | |
| 317 | + userTask.setName(node.getName()); | |
| 318 | + | |
| 319 | + // 人工审批 | |
| 320 | + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType()); | |
| 321 | + // 候选人策略为发起人自己 | |
| 322 | + addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask); | |
| 323 | + // 添加表单字段权限属性元素 | |
| 324 | + addFormFieldsPermission(node.getFieldsPermission(), userTask); | |
| 325 | + // 添加操作按钮配置属性元素 | |
| 326 | + addButtonsSetting(node.getButtonsSetting(), userTask); | |
| 327 | + // 使用自动通过策略 | |
| 328 | + // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过; | |
| 329 | + addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask); | |
| 330 | + return userTask; | |
| 331 | + } | |
| 332 | + | |
| 333 | + @Override | |
| 334 | + public BpmSimpleModelNodeType getType() { | |
| 335 | + return BpmSimpleModelNodeType.START_USER_NODE; | |
| 336 | + } | |
| 337 | + | |
| 292 | 338 | } |
| 293 | 339 | |
| 294 | - private static List<FlowElement> buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) { | |
| 295 | - List<FlowElement> list = new ArrayList<>(); | |
| 296 | - switch (nodeType) { | |
| 297 | - case START_NODE: { // 开始节点 | |
| 298 | - StartEvent startEvent = convertStartNode(node); | |
| 299 | - list.add(startEvent); | |
| 300 | - break; | |
| 301 | - } | |
| 302 | - case END_NODE: { // 结束节点 | |
| 303 | - EndEvent endEvent = convertEndNode(node); | |
| 304 | - list.add(endEvent); | |
| 305 | - break; | |
| 306 | - } | |
| 307 | - case START_USER_NODE: { // 发起人节点 | |
| 308 | - UserTask userTask = convertStartUserNode(node); | |
| 309 | - list.add(userTask); | |
| 310 | - break; | |
| 311 | - } | |
| 312 | - case APPROVE_NODE: { // 审批节点 | |
| 313 | - List<FlowElement> flowElements = convertApproveNode(node); | |
| 314 | - list.addAll(flowElements); | |
| 315 | - break; | |
| 340 | + private static class ApproveNodeConvert implements NodeConvert { | |
| 341 | + | |
| 342 | + @Override | |
| 343 | + public List<FlowElement> convertList(BpmSimpleModelNodeVO node) { | |
| 344 | + List<FlowElement> flowElements = new ArrayList<>(2); | |
| 345 | + // 1. 构建用户任务 | |
| 346 | + UserTask userTask = buildBpmnUserTask(node); | |
| 347 | + flowElements.add(userTask); | |
| 348 | + | |
| 349 | + // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 | |
| 350 | + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { | |
| 351 | + BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler()); | |
| 352 | + flowElements.add(boundaryEvent); | |
| 316 | 353 | } |
| 317 | - case COPY_NODE: { // 抄送节点 | |
| 318 | - ServiceTask serviceTask = convertCopyNode(node); | |
| 319 | - list.add(serviceTask); | |
| 320 | - break; | |
| 354 | + return flowElements; | |
| 355 | + } | |
| 356 | + | |
| 357 | + @Override | |
| 358 | + public BpmSimpleModelNodeType getType() { | |
| 359 | + return BpmSimpleModelNodeType.APPROVE_NODE; | |
| 360 | + } | |
| 361 | + | |
| 362 | + /** | |
| 363 | + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 | |
| 364 | + * | |
| 365 | + * @param userTask 审批任务 | |
| 366 | + * @param timeoutHandler 超时处理器 | |
| 367 | + * @return BoundaryEvent 超时事件 | |
| 368 | + */ | |
| 369 | + private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, | |
| 370 | + BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) { | |
| 371 | + // 1.1 定时器边界事件 | |
| 372 | + BoundaryEvent boundaryEvent = new BoundaryEvent(); | |
| 373 | + boundaryEvent.setId("Event-" + IdUtil.fastUUID()); | |
| 374 | + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 | |
| 375 | + boundaryEvent.setAttachedToRef(userTask); | |
| 376 | + // 1.2 定义超时时间、最大提醒次数 | |
| 377 | + TimerEventDefinition eventDefinition = new TimerEventDefinition(); | |
| 378 | + eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); | |
| 379 | + if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) && | |
| 380 | + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { | |
| 381 | + eventDefinition.setTimeCycle(String.format("R%d/%s", | |
| 382 | + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); | |
| 321 | 383 | } |
| 322 | - case CONDITION_BRANCH_NODE: { | |
| 323 | - ExclusiveGateway exclusiveGateway = convertConditionBranchNode(node); | |
| 324 | - list.add(exclusiveGateway); | |
| 325 | - break; | |
| 384 | + boundaryEvent.addEventDefinition(eventDefinition); | |
| 385 | + | |
| 386 | + // 2.1 添加定时器边界事件类型 | |
| 387 | + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType()); | |
| 388 | + // 2.2 添加超时执行动作元素 | |
| 389 | + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType()); | |
| 390 | + return boundaryEvent; | |
| 391 | + } | |
| 392 | + | |
| 393 | + private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { | |
| 394 | + UserTask userTask = new UserTask(); | |
| 395 | + userTask.setId(node.getId()); | |
| 396 | + userTask.setName(node.getName()); | |
| 397 | + | |
| 398 | + // 如果不是审批人节点,则直接返回 | |
| 399 | + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType()); | |
| 400 | + if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { | |
| 401 | + return userTask; | |
| 326 | 402 | } |
| 327 | - case PARALLEL_BRANCH_NODE: { | |
| 328 | - List<ParallelGateway> parallelGateways = convertParallelBranchNode(node); | |
| 329 | - list.addAll(parallelGateways); | |
| 330 | - break; | |
| 403 | + | |
| 404 | + // 添加候选人元素 | |
| 405 | + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); | |
| 406 | + // 添加表单字段权限属性元素 | |
| 407 | + addFormFieldsPermission(node.getFieldsPermission(), userTask); | |
| 408 | + // 添加操作按钮配置属性元素 | |
| 409 | + addButtonsSetting(node.getButtonsSetting(), userTask); | |
| 410 | + // 处理多实例(审批方式) | |
| 411 | + processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); | |
| 412 | + // 添加任务被拒绝的处理元素 | |
| 413 | + addTaskRejectElements(node.getRejectHandler(), userTask); | |
| 414 | + // 添加用户任务的审批人与发起人相同时的处理元素 | |
| 415 | + addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); | |
| 416 | + // 添加用户任务的空处理元素 | |
| 417 | + addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); | |
| 418 | + // 设置审批任务的截止时间 | |
| 419 | + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { | |
| 420 | + userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); | |
| 331 | 421 | } |
| 422 | + return userTask; | |
| 423 | + } | |
| 332 | 424 | |
| 333 | - case INCLUSIVE_BRANCH_NODE: { | |
| 334 | - // TODO jason 待实现 | |
| 335 | - break; | |
| 425 | + private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { | |
| 426 | + BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); | |
| 427 | + Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); | |
| 428 | + // 添加审批方式的扩展属性 | |
| 429 | + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod); | |
| 430 | + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { | |
| 431 | + // 随机审批,不需要设置多实例属性 | |
| 432 | + return; | |
| 336 | 433 | } |
| 337 | - default: { | |
| 338 | - // TODO 其它节点类型的实现 | |
| 434 | + | |
| 435 | + // 处理多实例审批方式 | |
| 436 | + MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); | |
| 437 | + // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错 | |
| 438 | + multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); | |
| 439 | + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) { | |
| 440 | + multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); | |
| 441 | + multiInstanceCharacteristics.setSequential(false); | |
| 442 | + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) { | |
| 443 | + multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); | |
| 444 | + multiInstanceCharacteristics.setSequential(true); | |
| 445 | + multiInstanceCharacteristics.setLoopCardinality("1"); | |
| 446 | + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) { | |
| 447 | + Assert.notNull(approveRatio, "通过比例不能为空"); | |
| 448 | + multiInstanceCharacteristics.setCompletionCondition( | |
| 449 | + String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D))); | |
| 450 | + multiInstanceCharacteristics.setSequential(false); | |
| 339 | 451 | } |
| 452 | + userTask.setLoopCharacteristics(multiInstanceCharacteristics); | |
| 340 | 453 | } |
| 341 | - return list; | |
| 342 | - } | |
| 343 | 454 | |
| 344 | - private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) { | |
| 345 | - return buildBpmnStartUserTask(node); | |
| 346 | 455 | } |
| 347 | 456 | |
| 348 | - private static List<FlowElement> convertApproveNode(BpmSimpleModelNodeVO node) { | |
| 349 | - List<FlowElement> flowElements = new ArrayList<>(); | |
| 350 | - UserTask userTask = buildBpmnUserTask(node); | |
| 351 | - flowElements.add(userTask); | |
| 352 | - | |
| 353 | - // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 | |
| 354 | - if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { | |
| 355 | - BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler()); | |
| 356 | - flowElements.add(boundaryEvent); | |
| 457 | + private static class CopyNodeConvert implements NodeConvert { | |
| 458 | + | |
| 459 | + @Override | |
| 460 | + public ServiceTask convert(BpmSimpleModelNodeVO node) { | |
| 461 | + ServiceTask serviceTask = new ServiceTask(); | |
| 462 | + serviceTask.setId(node.getId()); | |
| 463 | + serviceTask.setName(node.getName()); | |
| 464 | + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); | |
| 465 | + serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}"); | |
| 466 | + | |
| 467 | + // 添加抄送候选人元素 | |
| 468 | + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); | |
| 469 | + // 添加表单字段权限属性元素 | |
| 470 | + addFormFieldsPermission(node.getFieldsPermission(), serviceTask); | |
| 471 | + return serviceTask; | |
| 357 | 472 | } |
| 358 | - return flowElements; | |
| 359 | - } | |
| 360 | 473 | |
| 361 | - /** | |
| 362 | - * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 | |
| 363 | - * | |
| 364 | - * @param userTask 审批任务 | |
| 365 | - * @param timeoutHandler 超时处理器 | |
| 366 | - * @return BoundaryEvent 超时事件 | |
| 367 | - */ | |
| 368 | - private static BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { | |
| 369 | - // 1.1 定时器边界事件 | |
| 370 | - BoundaryEvent boundaryEvent = new BoundaryEvent(); | |
| 371 | - boundaryEvent.setId("Event-" + IdUtil.fastUUID()); | |
| 372 | - boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 | |
| 373 | - boundaryEvent.setAttachedToRef(userTask); | |
| 374 | - // 1.2 定义超时时间、最大提醒次数 | |
| 375 | - TimerEventDefinition eventDefinition = new TimerEventDefinition(); | |
| 376 | - eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); | |
| 377 | - if (Objects.equals(REMINDER.getType(), timeoutHandler.getType()) && | |
| 378 | - timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { | |
| 379 | - eventDefinition.setTimeCycle(String.format("R%d/%s", | |
| 380 | - timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); | |
| 381 | - } | |
| 382 | - boundaryEvent.addEventDefinition(eventDefinition); | |
| 383 | - | |
| 384 | - // 2.1 添加定时器边界事件类型 | |
| 385 | - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString()); | |
| 386 | - // 2.2 添加超时执行动作元素 | |
| 387 | - addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, StrUtil.toStringOrNull(timeoutHandler.getType())); | |
| 388 | - return boundaryEvent; | |
| 389 | - } | |
| 390 | - | |
| 391 | - private static List<ParallelGateway> convertParallelBranchNode(BpmSimpleModelNodeVO node) { | |
| 392 | - ParallelGateway parallelGateway = new ParallelGateway(); | |
| 393 | - parallelGateway.setId(node.getId()); | |
| 394 | - // TODO @jason:setName | |
| 474 | + @Override | |
| 475 | + public BpmSimpleModelNodeType getType() { | |
| 476 | + return BpmSimpleModelNodeType.COPY_NODE; | |
| 477 | + } | |
| 395 | 478 | |
| 396 | - // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论 | |
| 397 | - // 并行聚合网关有程序创建。前端不需要传入 | |
| 398 | - ParallelGateway joinParallelGateway = new ParallelGateway(); | |
| 399 | - joinParallelGateway.setId(node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX); | |
| 400 | - return CollUtil.newArrayList(parallelGateway, joinParallelGateway); | |
| 401 | 479 | } |
| 402 | 480 | |
| 403 | - private static ServiceTask convertCopyNode(BpmSimpleModelNodeVO node) { | |
| 404 | - ServiceTask serviceTask = new ServiceTask(); | |
| 405 | - serviceTask.setId(node.getId()); | |
| 406 | - serviceTask.setName(node.getName()); | |
| 407 | - serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); | |
| 408 | - serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}"); | |
| 409 | - | |
| 410 | - // 添加抄送候选人元素 | |
| 411 | - addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); | |
| 412 | - // 添加表单字段权限属性元素 | |
| 413 | - addFormFieldsPermission(node.getFieldsPermission(), serviceTask); | |
| 414 | - return serviceTask; | |
| 415 | - } | |
| 481 | + private static class ConditionBranchNodeConvert implements NodeConvert { | |
| 416 | 482 | |
| 417 | - /** | |
| 418 | - * 给节点添加候选人元素 | |
| 419 | - */ | |
| 420 | - private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) { | |
| 421 | - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, | |
| 422 | - candidateStrategy == null ? null : candidateStrategy.toString()); | |
| 423 | - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); | |
| 424 | - } | |
| 483 | + @Override | |
| 484 | + public ExclusiveGateway convert(BpmSimpleModelNodeVO node) { | |
| 485 | + ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); | |
| 486 | + exclusiveGateway.setId(node.getId()); | |
| 487 | + // TODO @jason:setName | |
| 425 | 488 | |
| 426 | - private static ExclusiveGateway convertConditionBranchNode(BpmSimpleModelNodeVO node) { | |
| 427 | - Assert.notEmpty(node.getConditionNodes(), "条件分支节点不能为空"); | |
| 428 | - ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); | |
| 429 | - exclusiveGateway.setId(node.getId()); | |
| 430 | - // 寻找默认的序列流 | |
| 431 | - BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), | |
| 432 | - item -> BooleanUtil.isTrue(item.getDefaultFlow())); | |
| 433 | - if (defaultSeqFlow != null) { | |
| 489 | + // 设置默认的序列流(条件) | |
| 490 | + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), | |
| 491 | + item -> BooleanUtil.isTrue(item.getDefaultFlow())); | |
| 492 | + Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId()); | |
| 434 | 493 | exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); |
| 494 | + return exclusiveGateway; | |
| 435 | 495 | } |
| 436 | - return exclusiveGateway; | |
| 437 | - } | |
| 438 | 496 | |
| 439 | - private static InclusiveGateway convertInclusiveBranchNode(BpmSimpleModelNodeVO node, Boolean isFork) { | |
| 440 | - InclusiveGateway inclusiveGateway = new InclusiveGateway(); | |
| 441 | - inclusiveGateway.setId(node.getId()); | |
| 442 | - // TODO @jason:这里是不是 setName 哈; | |
| 443 | - | |
| 444 | - // TODO @芋艿 + jason:是不是搞个合并网关;这里微信讨论下,有点奇怪; | |
| 445 | - // @芋艿 isFork 为 false 就是合并网关。由前端传入。这个前端暂时还未实现 | |
| 446 | - if (isFork) { | |
| 447 | - Assert.notEmpty(node.getConditionNodes(), "条件节点不能为空"); | |
| 448 | - // 寻找默认的序列流 | |
| 449 | - BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne( | |
| 450 | - node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getDefaultFlow())); | |
| 451 | - if (defaultSeqFlow != null) { | |
| 452 | - inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); | |
| 453 | - } | |
| 497 | + @Override | |
| 498 | + public BpmSimpleModelNodeType getType() { | |
| 499 | + return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE; | |
| 454 | 500 | } |
| 455 | - return inclusiveGateway; | |
| 501 | + | |
| 456 | 502 | } |
| 457 | 503 | |
| 458 | - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { | |
| 459 | - UserTask userTask = new UserTask(); | |
| 460 | - userTask.setId(node.getId()); | |
| 461 | - userTask.setName(node.getName()); | |
| 504 | + private static class ParallelBranchNodeConvert implements NodeConvert { | |
| 462 | 505 | |
| 463 | - // 如果不是审批人节点,则直接返回 | |
| 464 | - addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType())); | |
| 465 | - if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) { | |
| 466 | - return userTask; | |
| 506 | + @Override | |
| 507 | + public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) { | |
| 508 | + ParallelGateway parallelGateway = new ParallelGateway(); | |
| 509 | + parallelGateway.setId(node.getId()); | |
| 510 | + // TODO @jason:setName | |
| 511 | + | |
| 512 | + // 并行聚合网关由程序创建,前端不需要传入 | |
| 513 | + ParallelGateway joinParallelGateway = new ParallelGateway(); | |
| 514 | + joinParallelGateway.setId(buildGatewayJoinId(node.getId())); | |
| 515 | + // TODO @jason:setName | |
| 516 | + return CollUtil.newArrayList(parallelGateway, joinParallelGateway); | |
| 517 | + } | |
| 518 | + | |
| 519 | + @Override | |
| 520 | + public BpmSimpleModelNodeType getType() { | |
| 521 | + return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE; | |
| 467 | 522 | } |
| 468 | 523 | |
| 469 | - // 添加候选人元素 | |
| 470 | - addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); | |
| 471 | - // 添加表单字段权限属性元素 | |
| 472 | - addFormFieldsPermission(node.getFieldsPermission(), userTask); | |
| 473 | - // 添加操作按钮配置属性元素 | |
| 474 | - addButtonsSetting(node.getButtonsSetting(), userTask); | |
| 475 | - // 处理多实例(审批方式) | |
| 476 | - processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); | |
| 477 | - // 添加任务被拒绝的处理元素 | |
| 478 | - addTaskRejectElements(node.getRejectHandler(), userTask); | |
| 479 | - // 添加用户任务的审批人与发起人相同时的处理元素 | |
| 480 | - addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); | |
| 481 | - // 添加用户任务的空处理元素 | |
| 482 | - addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); | |
| 483 | - // 设置审批任务的截止时间 | |
| 484 | - if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { | |
| 485 | - userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); | |
| 486 | - } | |
| 487 | - return userTask; | |
| 488 | 524 | } |
| 489 | 525 | |
| 490 | - private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { | |
| 491 | - if (rejectHandler == null) { | |
| 492 | - return; | |
| 526 | + private static class InclusiveBranchNodeConvert implements NodeConvert { | |
| 527 | + | |
| 528 | + @Override | |
| 529 | + public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) { | |
| 530 | + InclusiveGateway inclusiveGateway = new InclusiveGateway(); | |
| 531 | + inclusiveGateway.setId(node.getId()); | |
| 532 | + // 设置默认的序列流(条件) | |
| 533 | + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), | |
| 534 | + item -> BooleanUtil.isTrue(item.getDefaultFlow())); | |
| 535 | + Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId()); | |
| 536 | + inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); | |
| 537 | + // TODO @jason:setName | |
| 538 | + | |
| 539 | + // 并行聚合网关由程序创建,前端不需要传入 | |
| 540 | + InclusiveGateway joinInclusiveGateway = new InclusiveGateway(); | |
| 541 | + joinInclusiveGateway.setId(buildGatewayJoinId(node.getId())); | |
| 542 | + // TODO @jason:setName | |
| 543 | + return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway); | |
| 493 | 544 | } |
| 494 | - addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); | |
| 495 | - addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); | |
| 496 | - } | |
| 497 | 545 | |
| 498 | - private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { | |
| 499 | - if (assignStartUserHandlerType == null) { | |
| 500 | - return; | |
| 546 | + @Override | |
| 547 | + public BpmSimpleModelNodeType getType() { | |
| 548 | + return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE; | |
| 501 | 549 | } |
| 502 | - addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); | |
| 550 | + | |
| 503 | 551 | } |
| 504 | 552 | |
| 505 | - private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) { | |
| 506 | - if (emptyHandler == null) { | |
| 507 | - return; | |
| 553 | + public static class ConditionNodeConvert implements NodeConvert { | |
| 554 | + | |
| 555 | + @Override | |
| 556 | + public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { | |
| 557 | + // 原因是:正常情况下,它不会被调用到 | |
| 558 | + throw new UnsupportedOperationException("条件节点不支持转换"); | |
| 508 | 559 | } |
| 509 | - addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType())); | |
| 510 | - addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds())); | |
| 511 | - } | |
| 512 | 560 | |
| 513 | - private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { | |
| 514 | - BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); | |
| 515 | - // TODO @jason:这种枚举,最终不要去掉哈 BpmUserTaskApproveMethodEnum。因为容易不经意重叠 | |
| 516 | - if (approveMethodEnum == null || approveMethodEnum == RANDOM) { | |
| 517 | - return; | |
| 561 | + @Override | |
| 562 | + public BpmSimpleModelNodeType getType() { | |
| 563 | + return BpmSimpleModelNodeType.CONDITION_NODE; | |
| 518 | 564 | } |
| 519 | - // 添加审批方式的扩展属性 | |
| 520 | - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, | |
| 521 | - approveMethod == null ? null : approveMethod.toString()); | |
| 522 | - MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); | |
| 523 | - // 设置 collectionVariable。本系统用不到。仅仅为了 Flowable 校验不报错。 | |
| 524 | - multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); | |
| 525 | - if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) { | |
| 526 | - multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION); | |
| 527 | - multiInstanceCharacteristics.setSequential(false); | |
| 528 | - userTask.setLoopCharacteristics(multiInstanceCharacteristics); | |
| 529 | - } else if (approveMethodEnum == SEQUENTIAL) { | |
| 530 | - multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION); | |
| 531 | - multiInstanceCharacteristics.setSequential(true); | |
| 532 | - multiInstanceCharacteristics.setLoopCardinality("1"); | |
| 533 | - userTask.setLoopCharacteristics(multiInstanceCharacteristics); | |
| 534 | - } else if (approveMethodEnum == RATIO) { | |
| 535 | - Assert.notNull(approveRatio, "通过比例不能为空"); | |
| 536 | - multiInstanceCharacteristics.setCompletionCondition( | |
| 537 | - String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100))); | |
| 538 | - multiInstanceCharacteristics.setSequential(false); | |
| 565 | + | |
| 566 | + public static SequenceFlow buildSequenceFlow(String sourceId, String targetId, | |
| 567 | + BpmSimpleModelNodeVO node) { | |
| 568 | + String conditionExpression = buildConditionExpression(node); | |
| 569 | + return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression); | |
| 539 | 570 | } |
| 540 | - userTask.setLoopCharacteristics(multiInstanceCharacteristics); | |
| 541 | - } | |
| 542 | 571 | |
| 543 | - /** | |
| 544 | - * 给节点添加操作按钮设置元素 | |
| 545 | - */ | |
| 546 | - private static void addButtonsSetting(List<OperationButtonSetting> buttonsSetting, UserTask userTask) { | |
| 547 | - if (CollUtil.isNotEmpty(buttonsSetting)) { | |
| 548 | - List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> { | |
| 549 | - Map<String, String> settingMap = MapUtil.newHashMap(16); | |
| 550 | - settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId())); | |
| 551 | - settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName()); | |
| 552 | - settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable())); | |
| 553 | - return settingMap; | |
| 554 | - }); | |
| 555 | - list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item)); | |
| 572 | + /** | |
| 573 | + * 构造条件表达式 | |
| 574 | + * | |
| 575 | + * @param node 条件节点 | |
| 576 | + */ | |
| 577 | + public static String buildConditionExpression(BpmSimpleModelNodeVO node) { | |
| 578 | + BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType()); | |
| 579 | + if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) { | |
| 580 | + return node.getConditionExpression(); | |
| 581 | + } | |
| 582 | + if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { | |
| 583 | + ConditionGroups conditionGroups = node.getConditionGroups(); | |
| 584 | + if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) { | |
| 585 | + return null; | |
| 586 | + } | |
| 587 | + List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> { | |
| 588 | + if (CollUtil.isEmpty(item.getRules())) { | |
| 589 | + return ""; | |
| 590 | + } | |
| 591 | + // 构造规则表达式 | |
| 592 | + List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> { | |
| 593 | + String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() | |
| 594 | + : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号 | |
| 595 | + return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide); | |
| 596 | + }); | |
| 597 | + // 构造条件组的表达式 | |
| 598 | + Boolean and = item.getAnd(); | |
| 599 | + return "(" + CollUtil.join(list, and ? " && " : " || ") + ")"; | |
| 600 | + }); | |
| 601 | + return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); | |
| 602 | + } | |
| 603 | + return null; | |
| 556 | 604 | } |
| 605 | + | |
| 557 | 606 | } |
| 558 | 607 | |
| 559 | - /** | |
| 560 | - * 给节点添加表单字段权限元素 | |
| 561 | - */ | |
| 562 | - private static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) { | |
| 563 | - if (CollUtil.isNotEmpty(fieldsPermissions)) { | |
| 564 | - fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); | |
| 565 | - } | |
| 608 | + private static String buildGatewayJoinId(String id) { | |
| 609 | + return id + "_join"; | |
| 566 | 610 | } |
| 567 | 611 | |
| 568 | - private static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) { | |
| 569 | - if (attributes == null) { | |
| 570 | - return; | |
| 571 | - } | |
| 572 | - ExtensionElement extensionElement = new ExtensionElement(); | |
| 573 | - extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 574 | - extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); | |
| 575 | - extensionElement.setName(name); | |
| 576 | - attributes.forEach((key, value) -> { | |
| 577 | - ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value); | |
| 578 | - extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 579 | - extensionElement.addAttribute(extensionAttribute); | |
| 580 | - }); | |
| 581 | - element.addExtensionElement(extensionElement); | |
| 612 | + // ========== SIMPLE 流程预测相关的方法 ========== | |
| 613 | + | |
| 614 | + public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) { | |
| 615 | + List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>(); | |
| 616 | + | |
| 617 | + // 从头开始遍历 | |
| 618 | + simulateNextNode(rootNode, variables, resultNodes); | |
| 619 | + return resultNodes; | |
| 582 | 620 | } |
| 583 | 621 | |
| 584 | - private static void addExtensionElement(FlowElement element, String name, String value) { | |
| 585 | - if (value == null) { | |
| 622 | + private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables, | |
| 623 | + List<BpmSimpleModelNodeVO> resultNodes) { | |
| 624 | + // 如果不合法(包括为空),则直接结束 | |
| 625 | + if (!isValidNode(currentNode)) { | |
| 586 | 626 | return; |
| 587 | 627 | } |
| 588 | - ExtensionElement extensionElement = new ExtensionElement(); | |
| 589 | - extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 590 | - extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); | |
| 591 | - extensionElement.setElementText(value); | |
| 592 | - extensionElement.setName(name); | |
| 593 | - element.addExtensionElement(extensionElement); | |
| 594 | - } | |
| 628 | + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType()); | |
| 629 | + Assert.notNull(nodeType, "模型节点类型不支持"); | |
| 595 | 630 | |
| 596 | - // ========== 各种 build 节点的方法 ========== | |
| 631 | + // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE | |
| 632 | + if (nodeType == BpmSimpleModelNodeType.START_NODE | |
| 633 | + || nodeType == BpmSimpleModelNodeType.START_USER_NODE | |
| 634 | + || nodeType == BpmSimpleModelNodeType.APPROVE_NODE | |
| 635 | + || nodeType == BpmSimpleModelNodeType.COPY_NODE | |
| 636 | + || nodeType == BpmSimpleModelNodeType.END_NODE) { | |
| 637 | + // 添加元素 | |
| 638 | + resultNodes.add(currentNode); | |
| 639 | + } | |
| 597 | 640 | |
| 598 | - private static StartEvent convertStartNode(BpmSimpleModelNodeVO node) { | |
| 599 | - StartEvent startEvent = new StartEvent(); | |
| 600 | - startEvent.setId(node.getId()); | |
| 601 | - startEvent.setName(node.getName()); | |
| 602 | - return startEvent; | |
| 603 | - } | |
| 641 | + // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的 | |
| 642 | + if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { | |
| 643 | + // 查找满足条件的 BpmSimpleModelNodeVO 节点 | |
| 644 | + BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), | |
| 645 | + conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow()) | |
| 646 | + && evalConditionExpress(variables, conditionNode)); | |
| 647 | + if (matchConditionNode == null) { | |
| 648 | + matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), | |
| 649 | + conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow())); | |
| 650 | + } | |
| 651 | + Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode); | |
| 652 | + // 遍历满足条件的 BpmSimpleModelNodeVO 节点 | |
| 653 | + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes); | |
| 654 | + } | |
| 604 | 655 | |
| 605 | - private static UserTask buildBpmnStartUserTask(BpmSimpleModelNodeVO node) { | |
| 606 | - UserTask userTask = new UserTask(); | |
| 607 | - userTask.setId(node.getId()); | |
| 608 | - userTask.setName(node.getName()); | |
| 609 | - // 人工审批 | |
| 610 | - addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, USER.getType().toString()); | |
| 611 | - // 候选人策略为发起人自己 | |
| 612 | - addCandidateElements(START_USER.getStrategy(), null, userTask); | |
| 613 | - // 添加表单字段权限属性元素 | |
| 614 | - addFormFieldsPermission(node.getFieldsPermission(), userTask); | |
| 615 | - // 添加操作按钮配置属性元素 | |
| 616 | - addButtonsSetting(node.getButtonsSetting(), userTask); | |
| 617 | - // 使用自动通过策略 TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过; | |
| 618 | - addAssignStartUserHandlerType(SKIP.getType(), userTask); | |
| 619 | - return userTask; | |
| 620 | - } | |
| 656 | + // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的 | |
| 657 | + if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { | |
| 658 | + // 查找满足条件的 BpmSimpleModelNodeVO 节点 | |
| 659 | + Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), | |
| 660 | + conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow()) | |
| 661 | + && evalConditionExpress(variables, conditionNode)); | |
| 662 | + if (CollUtil.isEmpty(matchConditionNodes)) { | |
| 663 | + matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), | |
| 664 | + conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow())); | |
| 665 | + } | |
| 666 | + Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode); | |
| 667 | + // 遍历满足条件的 BpmSimpleModelNodeVO 节点 | |
| 668 | + matchConditionNodes.forEach(matchConditionNode -> | |
| 669 | + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); | |
| 670 | + } | |
| 621 | 671 | |
| 622 | - private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) { | |
| 623 | - EndEvent endEvent = new EndEvent(); | |
| 624 | - endEvent.setId(node.getId()); | |
| 625 | - endEvent.setName(node.getName()); | |
| 672 | + // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走 | |
| 673 | + if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) { | |
| 674 | + // 遍历所有 BpmSimpleModelNodeVO 节点 | |
| 675 | + currentNode.getConditionNodes().forEach(matchConditionNode -> | |
| 676 | + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); | |
| 677 | + } | |
| 678 | + | |
| 679 | + // 遍历子节点 | |
| 680 | + simulateNextNode(currentNode.getChildNode(), variables, resultNodes); | |
| 681 | + } | |
| 626 | 682 | |
| 627 | - // TODO @芋艿 + jason:要不要加一个终止定义? | |
| 628 | - return endEvent; | |
| 683 | + public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) { | |
| 684 | + return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode)); | |
| 629 | 685 | } |
| 630 | 686 | |
| 631 | 687 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryService.java
| ... | ... | @@ -82,4 +82,11 @@ public interface BpmCategoryService { |
| 82 | 82 | */ |
| 83 | 83 | List<BpmCategoryDO> getCategoryListByStatus(Integer status); |
| 84 | 84 | |
| 85 | + /** | |
| 86 | + * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增 | |
| 87 | + * | |
| 88 | + * @param ids 分类编号列表 | |
| 89 | + */ | |
| 90 | + void updateCategorySortBatch(List<Long> ids); | |
| 91 | + | |
| 85 | 92 | } |
| 86 | 93 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryServiceImpl.java
| ... | ... | @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.bpm.service.definition; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 6 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 5 | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; |
| 6 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; |
| 7 | -import org.springframework.stereotype.Service; | |
| 8 | -import jakarta.annotation.Resource; | |
| 9 | -import org.springframework.validation.annotation.Validated; | |
| 10 | - | |
| 11 | 9 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 12 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 13 | -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 14 | - | |
| 15 | 10 | import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper; |
| 11 | +import jakarta.annotation.Resource; | |
| 12 | +import org.springframework.stereotype.Service; | |
| 13 | +import org.springframework.transaction.annotation.Transactional; | |
| 14 | +import org.springframework.validation.annotation.Validated; | |
| 16 | 15 | |
| 17 | 16 | import java.util.Collection; |
| 18 | 17 | import java.util.Collections; |
| 19 | 18 | import java.util.List; |
| 19 | +import java.util.stream.Collectors; | |
| 20 | +import java.util.stream.IntStream; | |
| 20 | 21 | |
| 21 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 22 | 23 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -58,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { |
| 58 | 59 | private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) { |
| 59 | 60 | BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName()); |
| 60 | 61 | if (category == null |
| 61 | - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { | |
| 62 | + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { | |
| 62 | 63 | return; |
| 63 | 64 | } |
| 64 | 65 | throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName()); |
| ... | ... | @@ -67,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { |
| 67 | 68 | private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) { |
| 68 | 69 | BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode()); |
| 69 | 70 | if (category == null |
| 70 | - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { | |
| 71 | + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { | |
| 71 | 72 | return; |
| 72 | 73 | } |
| 73 | 74 | throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode()); |
| ... | ... | @@ -110,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { |
| 110 | 111 | return bpmCategoryMapper.selectListByStatus(status); |
| 111 | 112 | } |
| 112 | 113 | |
| 114 | + @Override | |
| 115 | + @Transactional(rollbackFor = Exception.class) | |
| 116 | + public void updateCategorySortBatch(List<Long> ids) { | |
| 117 | + // 校验分类都存在 | |
| 118 | + List<BpmCategoryDO> categories = bpmCategoryMapper.selectByIds(ids); | |
| 119 | + if (categories.size() != ids.size()) { | |
| 120 | + throw exception(CATEGORY_NOT_EXISTS); | |
| 121 | + } | |
| 122 | + | |
| 123 | + // 批量更新排序 | |
| 124 | + List<BpmCategoryDO> updateList = IntStream.range(0, ids.size()) | |
| 125 | + .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index)) | |
| 126 | + .collect(Collectors.toList()); | |
| 127 | + bpmCategoryMapper.updateBatch(updateList); | |
| 128 | + } | |
| 129 | + | |
| 113 | 130 | } |
| 114 | 131 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.service.definition; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; | |
| 5 | 3 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| 6 | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 7 | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| ... | ... | @@ -9,20 +7,22 @@ import jakarta.validation.Valid; |
| 9 | 7 | import org.flowable.bpmn.model.BpmnModel; |
| 10 | 8 | import org.flowable.engine.repository.Model; |
| 11 | 9 | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 12 | 12 | /** |
| 13 | - * Flowable流程模型接口 | |
| 13 | + * 流程模型接口 | |
| 14 | 14 | * |
| 15 | 15 | * @author yunlongn |
| 16 | 16 | */ |
| 17 | 17 | public interface BpmModelService { |
| 18 | 18 | |
| 19 | 19 | /** |
| 20 | - * 获得流程模型分页 | |
| 20 | + * 获得流程模型列表 | |
| 21 | 21 | * |
| 22 | - * @param pageVO 分页查询 | |
| 23 | - * @return 流程模型分页 | |
| 22 | + * @param name 模型名称 | |
| 23 | + * @return 流程模型列表 | |
| 24 | 24 | */ |
| 25 | - PageResult<Model> getModelPage(BpmModelPageReqVO pageVO); | |
| 25 | + List<Model> getModelList(String name); | |
| 26 | 26 | |
| 27 | 27 | /** |
| 28 | 28 | * 创建流程模型 |
| ... | ... | @@ -65,6 +65,14 @@ public interface BpmModelService { |
| 65 | 65 | void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); |
| 66 | 66 | |
| 67 | 67 | /** |
| 68 | + * 批量更新模型排序 | |
| 69 | + * | |
| 70 | + * @param userId 用户编号 | |
| 71 | + * @param ids 编号列表 | |
| 72 | + */ | |
| 73 | + void updateModelSortBatch(Long userId, List<String> ids); | |
| 74 | + | |
| 75 | + /** | |
| 68 | 76 | * 将流程模型,部署成一个流程定义 |
| 69 | 77 | * |
| 70 | 78 | * @param userId 用户编号 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
| ... | ... | @@ -3,12 +3,9 @@ package cn.iocoder.yudao.module.bpm.service.definition; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ArrayUtil; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 7 | 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 8 | -import cn.iocoder.yudao.framework.common.util.object.PageUtils; | |
| 9 | 7 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 10 | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| 11 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; | |
| 12 | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| 13 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 14 | 11 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| ... | ... | @@ -35,14 +32,15 @@ import org.springframework.transaction.annotation.Transactional; |
| 35 | 32 | import org.springframework.validation.annotation.Validated; |
| 36 | 33 | |
| 37 | 34 | import java.util.List; |
| 35 | +import java.util.Map; | |
| 38 | 36 | import java.util.Objects; |
| 39 | 37 | |
| 40 | 38 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 39 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 41 | 40 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; |
| 42 | 41 | |
| 43 | 42 | /** |
| 44 | - * Flowable流程模型实现 | |
| 45 | - * 主要进行 Flowable {@link Model} 的维护 | |
| 43 | + * 流程模型实现:主要进行 Flowable {@link Model} 的维护 | |
| 46 | 44 | * |
| 47 | 45 | * @author yunlongn |
| 48 | 46 | * @author 芋道源码 |
| ... | ... | @@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 64 | 62 | private BpmTaskCandidateInvoker taskCandidateInvoker; |
| 65 | 63 | |
| 66 | 64 | @Override |
| 67 | - public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) { | |
| 65 | + public List<Model> getModelList(String name) { | |
| 68 | 66 | ModelQuery modelQuery = repositoryService.createModelQuery(); |
| 69 | - modelQuery.modelTenantId(FlowableUtils.getTenantId()); | |
| 70 | - if (StrUtil.isNotBlank(pageVO.getKey())) { | |
| 71 | - modelQuery.modelKey(pageVO.getKey()); | |
| 67 | + if (StrUtil.isNotEmpty(name)) { | |
| 68 | + modelQuery.modelNameLike(name); | |
| 72 | 69 | } |
| 73 | - if (StrUtil.isNotBlank(pageVO.getName())) { | |
| 74 | - modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配 | |
| 75 | - } | |
| 76 | - if (StrUtil.isNotBlank(pageVO.getCategory())) { | |
| 77 | - modelQuery.modelCategory(pageVO.getCategory()); | |
| 78 | - } | |
| 79 | - // 执行查询 | |
| 80 | - long count = modelQuery.count(); | |
| 81 | - if (count == 0) { | |
| 82 | - return PageResult.empty(count); | |
| 83 | - } | |
| 84 | - List<Model> models = modelQuery | |
| 85 | - .orderByCreateTime().desc() | |
| 86 | - .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); | |
| 87 | - return new PageResult<>(models, count); | |
| 70 | + return modelQuery.list(); | |
| 88 | 71 | } |
| 89 | 72 | |
| 90 | 73 | @Override |
| ... | ... | @@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 100 | 83 | } |
| 101 | 84 | |
| 102 | 85 | // 2.1 创建流程定义 |
| 86 | + createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序 | |
| 103 | 87 | Model model = repositoryService.newModel(); |
| 104 | 88 | BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); |
| 105 | 89 | model.setTenantId(FlowableUtils.getTenantId()); |
| ... | ... | @@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 120 | 104 | repositoryService.saveModel(model); |
| 121 | 105 | } |
| 122 | 106 | |
| 107 | + @Override | |
| 108 | + @Transactional(rollbackFor = Exception.class) | |
| 109 | + public void updateModelSortBatch(Long userId, List<String> ids) { | |
| 110 | + // 1.1 校验流程模型存在 | |
| 111 | + List<Model> models = repositoryService.createModelQuery() | |
| 112 | + .modelTenantId(FlowableUtils.getTenantId()).list(); | |
| 113 | + models.removeIf(model ->!ids.contains(model.getId())); | |
| 114 | + if (ids.size() != models.size()) { | |
| 115 | + throw exception(MODEL_NOT_EXISTS); | |
| 116 | + } | |
| 117 | + Map<String, Model> modelMap = convertMap(models, Model::getId); | |
| 118 | + // 1.2 校验是否为管理员 | |
| 119 | + ids.forEach(id -> validateModelManager(id, userId)); | |
| 120 | + | |
| 121 | + // 保存排序 | |
| 122 | + long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序 | |
| 123 | + for (int i = ids.size() - 1; i > 0; i--) { | |
| 124 | + Model model = modelMap.get(ids.get(i)); | |
| 125 | + // 更新模型 | |
| 126 | + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort); | |
| 127 | + model.setMetaInfo(JsonUtils.toJsonString(metaInfo)); | |
| 128 | + repositoryService.saveModel(model); | |
| 129 | + // 更新排序 | |
| 130 | + processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort); | |
| 131 | + sort--; | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 123 | 135 | private Model validateModelExists(String id) { |
| 124 | 136 | Model model = repositoryService.getModel(id); |
| 125 | 137 | if (model == null) { |
| ... | ... | @@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 139 | 151 | Model model = validateModelExists(id); |
| 140 | 152 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| 141 | 153 | if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { |
| 142 | - throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER); | |
| 154 | + throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName()); | |
| 143 | 155 | } |
| 144 | 156 | return model; |
| 145 | 157 | } |
| ... | ... | @@ -158,10 +170,10 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 158 | 170 | // 1.4 校验任务分配规则已配置 |
| 159 | 171 | taskCandidateInvoker.validateBpmnConfig(bpmnBytes); |
| 160 | 172 | // 1.5 获取仿钉钉流程设计器模型数据 |
| 161 | - byte[] simpleBytes = getModelSimpleJson(model.getId()); | |
| 173 | + String simpleJson = getModelSimpleJson(model.getId()); | |
| 162 | 174 | |
| 163 | 175 | // 2.1 创建流程定义 |
| 164 | - String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleBytes, form); | |
| 176 | + String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form); | |
| 165 | 177 | |
| 166 | 178 | // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 |
| 167 | 179 | updateProcessDefinitionSuspended(model.getDeploymentId()); |
| ... | ... | @@ -226,8 +238,8 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 226 | 238 | public BpmSimpleModelNodeVO getSimpleModel(String modelId) { |
| 227 | 239 | Model model = validateModelExists(modelId); |
| 228 | 240 | // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 |
| 229 | - byte[] jsonBytes = getModelSimpleJson(model.getId()); | |
| 230 | - return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); | |
| 241 | + String json = getModelSimpleJson(model.getId()); | |
| 242 | + return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class); | |
| 231 | 243 | } |
| 232 | 244 | |
| 233 | 245 | @Override |
| ... | ... | @@ -240,7 +252,7 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 240 | 252 | // 2.2 保存 Bpmn XML |
| 241 | 253 | updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); |
| 242 | 254 | // 2.3 保存 JSON 数据 |
| 243 | - saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel())); | |
| 255 | + updateModelSimpleJson(model.getId(), reqVO.getSimpleModel()); | |
| 244 | 256 | } |
| 245 | 257 | |
| 246 | 258 | /** |
| ... | ... | @@ -279,15 +291,21 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 279 | 291 | repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); |
| 280 | 292 | } |
| 281 | 293 | |
| 282 | - private byte[] getModelSimpleJson(String id) { | |
| 283 | - return repositoryService.getModelEditorSourceExtra(id); | |
| 294 | + @SuppressWarnings("JavaExistingMethodCanBeUsed") | |
| 295 | + private String getModelSimpleJson(String id) { | |
| 296 | + byte[] bytes = repositoryService.getModelEditorSourceExtra(id); | |
| 297 | + if (ArrayUtil.isEmpty(bytes)) { | |
| 298 | + return null; | |
| 299 | + } | |
| 300 | + return StrUtil.utf8Str(bytes); | |
| 284 | 301 | } |
| 285 | 302 | |
| 286 | - private void saveModelSimpleJson(String id, byte[] jsonBytes) { | |
| 287 | - if (ArrayUtil.isEmpty(jsonBytes)) { | |
| 303 | + private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) { | |
| 304 | + if (node == null) { | |
| 288 | 305 | return; |
| 289 | 306 | } |
| 290 | - repositoryService.addModelEditorSourceExtra(id, jsonBytes); | |
| 307 | + byte[] bytes = JsonUtils.toJsonByte(node); | |
| 308 | + repositoryService.addModelEditorSourceExtra(id, bytes); | |
| 291 | 309 | } |
| 292 | 310 | |
| 293 | 311 | /** | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
| ... | ... | @@ -18,7 +18,7 @@ import java.util.Set; |
| 18 | 18 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 19 | 19 | |
| 20 | 20 | /** |
| 21 | - * Flowable流程定义接口 | |
| 21 | + * 流程定义接口 | |
| 22 | 22 | * |
| 23 | 23 | * @author yunlong.li |
| 24 | 24 | * @author ZJQ |
| ... | ... | @@ -48,12 +48,12 @@ public interface BpmProcessDefinitionService { |
| 48 | 48 | * @param model 流程模型 |
| 49 | 49 | * @param modelMetaInfo 流程模型元信息 |
| 50 | 50 | * @param bpmnBytes BPMN XML 字节数组 |
| 51 | - * @param simpleBytes SIMPLE Model JSON 字节数组 | |
| 51 | + * @param simpleJson SIMPLE Model JSON | |
| 52 | 52 | * @param form 表单 |
| 53 | 53 | * @return 流程编号 |
| 54 | 54 | */ |
| 55 | 55 | String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, |
| 56 | - byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form); | |
| 56 | + byte[] bpmnBytes, String simpleJson, BpmFormDO form); | |
| 57 | 57 | |
| 58 | 58 | /** |
| 59 | 59 | * 更新流程定义状态 |
| ... | ... | @@ -64,6 +64,14 @@ public interface BpmProcessDefinitionService { |
| 64 | 64 | void updateProcessDefinitionState(String id, Integer state); |
| 65 | 65 | |
| 66 | 66 | /** |
| 67 | + * 更新模型编号 | |
| 68 | + * | |
| 69 | + * @param modelId 流程定义编号 | |
| 70 | + * @param sort 排序 | |
| 71 | + */ | |
| 72 | + void updateProcessDefinitionSortByModelId(String modelId, Long sort); | |
| 73 | + | |
| 74 | + /** | |
| 67 | 75 | * 获得流程定义对应的 BPMN |
| 68 | 76 | * |
| 69 | 77 | * @param id 流程定义编号 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
| ... | ... | @@ -24,7 +24,6 @@ import org.flowable.engine.repository.ProcessDefinitionQuery; |
| 24 | 24 | import org.springframework.stereotype.Service; |
| 25 | 25 | import org.springframework.validation.annotation.Validated; |
| 26 | 26 | |
| 27 | -import java.nio.charset.StandardCharsets; | |
| 28 | 27 | import java.util.*; |
| 29 | 28 | |
| 30 | 29 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -120,7 +119,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ |
| 120 | 119 | |
| 121 | 120 | @Override |
| 122 | 121 | public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, |
| 123 | - byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form) { | |
| 122 | + byte[] bpmnBytes, String simpleJson, BpmFormDO form) { | |
| 124 | 123 | // 创建 Deployment 部署 |
| 125 | 124 | Deployment deploy = repositoryService.createDeployment() |
| 126 | 125 | .key(model.getKey()).name(model.getName()).category(model.getCategory()) |
| ... | ... | @@ -145,8 +144,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ |
| 145 | 144 | |
| 146 | 145 | // 插入拓展表 |
| 147 | 146 | BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) |
| 148 | - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()).setModelType(modelMetaInfo.getType()) | |
| 149 | - .setSimpleModel(StrUtil.str(simpleBytes, StandardCharsets.UTF_8)); | |
| 147 | + .setModelId(model.getId()).setProcessDefinitionId(definition.getId()) | |
| 148 | + .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson); | |
| 150 | 149 | |
| 151 | 150 | if (form != null) { |
| 152 | 151 | definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); |
| ... | ... | @@ -173,6 +172,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ |
| 173 | 172 | } |
| 174 | 173 | |
| 175 | 174 | @Override |
| 175 | + public void updateProcessDefinitionSortByModelId(String modelId, Long sort) { | |
| 176 | + processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort)); | |
| 177 | + } | |
| 178 | + | |
| 179 | + @Override | |
| 176 | 180 | public BpmnModel getProcessDefinitionBpmnModel(String id) { |
| 177 | 181 | return repositoryService.getBpmnModel(id); |
| 178 | 182 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.service.task; | |
| 2 | - | |
| 3 | -import org.flowable.engine.history.HistoricActivityInstance; | |
| 4 | - | |
| 5 | -import java.util.List; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * BPM 活动实例 Service 接口 | |
| 9 | - * | |
| 10 | - * @author 芋道源码 | |
| 11 | - */ | |
| 12 | -public interface BpmActivityService { | |
| 13 | - | |
| 14 | - /** | |
| 15 | - * 获得指定流程实例的活动实例列表 | |
| 16 | - * | |
| 17 | - * @param processInstanceId 流程实例的编号 | |
| 18 | - * @return 活动实例列表 | |
| 19 | - */ | |
| 20 | - List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId); | |
| 21 | - | |
| 22 | - /** | |
| 23 | - * 获得执行编号对应的活动实例 | |
| 24 | - * | |
| 25 | - * @param executionId 执行编号 | |
| 26 | - * @return 活动实例 | |
| 27 | - */ | |
| 28 | - List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId); | |
| 29 | - | |
| 30 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.service.task; | |
| 2 | - | |
| 3 | -import jakarta.annotation.Resource; | |
| 4 | -import lombok.extern.slf4j.Slf4j; | |
| 5 | -import org.flowable.engine.HistoryService; | |
| 6 | -import org.flowable.engine.history.HistoricActivityInstance; | |
| 7 | -import org.springframework.stereotype.Service; | |
| 8 | -import org.springframework.validation.annotation.Validated; | |
| 9 | - | |
| 10 | -import java.util.List; | |
| 11 | - | |
| 12 | - | |
| 13 | -/** | |
| 14 | - * BPM 活动实例 Service 实现类 | |
| 15 | - * | |
| 16 | - * @author 芋道源码 | |
| 17 | - */ | |
| 18 | -@Service | |
| 19 | -@Slf4j | |
| 20 | -@Validated | |
| 21 | -public class BpmActivityServiceImpl implements BpmActivityService { | |
| 22 | - | |
| 23 | - @Resource | |
| 24 | - private HistoryService historyService; | |
| 25 | - | |
| 26 | - @Override | |
| 27 | - public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) { | |
| 28 | - return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) | |
| 29 | - .orderByHistoricActivityInstanceStartTime().asc().list(); | |
| 30 | - } | |
| 31 | - | |
| 32 | - @Override | |
| 33 | - public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) { | |
| 34 | - return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); | |
| 35 | - } | |
| 36 | - | |
| 37 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
| ... | ... | @@ -3,9 +3,10 @@ package cn.iocoder.yudao.module.bpm.service.task; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| 6 | +import jakarta.validation.constraints.NotEmpty; | |
| 7 | +import org.flowable.bpmn.model.FlowNode; | |
| 6 | 8 | |
| 7 | 9 | import java.util.Collection; |
| 8 | -import java.util.Set; | |
| 9 | 10 | |
| 10 | 11 | /** |
| 11 | 12 | * 流程抄送 Service 接口 |
| ... | ... | @@ -15,24 +16,29 @@ import java.util.Set; |
| 15 | 16 | public interface BpmProcessInstanceCopyService { |
| 16 | 17 | |
| 17 | 18 | /** |
| 18 | - * 流程实例的抄送 | |
| 19 | + * 【管理员】流程实例的抄送 | |
| 19 | 20 | * |
| 20 | 21 | * @param userIds 抄送的用户编号 |
| 22 | + * @param reason 抄送意见 | |
| 21 | 23 | * @param taskId 流程任务编号 |
| 22 | 24 | */ |
| 23 | - void createProcessInstanceCopy(Collection<Long> userIds, String taskId); | |
| 25 | + void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId); | |
| 24 | 26 | |
| 25 | 27 | /** |
| 26 | - * 流程实例的抄送 | |
| 28 | + * 【自动抄送】流程实例的抄送 | |
| 27 | 29 | * |
| 28 | 30 | * @param userIds 抄送的用户编号 |
| 31 | + * @param reason 抄送意见 | |
| 29 | 32 | * @param processInstanceId 流程编号 |
| 30 | - * @param activityId 流程活动编号 id (对应 BPMN XML 节点 Id) | |
| 31 | - * // TODO 芋艿这个 taskId 是不是可以不要了 | |
| 32 | - * @param taskId 任务编号 | |
| 33 | - * @param taskName 任务名称 | |
| 33 | + * @param activityId 流程活动编号(对应 {@link FlowNode#getId()}) | |
| 34 | + * @param activityName 任务编号(对应 {@link FlowNode#getName()}) | |
| 35 | + * @param taskId 任务编号,允许空 | |
| 34 | 36 | */ |
| 35 | - void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName); | |
| 37 | + void createProcessInstanceCopy(Collection<Long> userIds, String reason, | |
| 38 | + @NotEmpty(message = "流程实例编号不能为空") String processInstanceId, | |
| 39 | + @NotEmpty(message = "流程活动编号不能为空") String activityId, | |
| 40 | + @NotEmpty(message = "流程活动名字不能为空") String activityName, | |
| 41 | + String taskId); | |
| 36 | 42 | |
| 37 | 43 | /** |
| 38 | 44 | * 获得抄送的流程的分页 |
| ... | ... | @@ -43,14 +49,5 @@ public interface BpmProcessInstanceCopyService { |
| 43 | 49 | */ |
| 44 | 50 | PageResult<BpmProcessInstanceCopyDO> getProcessInstanceCopyPage(Long userId, |
| 45 | 51 | BpmProcessInstanceCopyPageReqVO pageReqVO); |
| 46 | - // TODO @芋艿:重点在 review 下 | |
| 47 | - /** | |
| 48 | - * 通过流程实例和流程活动编号获取抄送人的 Id | |
| 49 | - * | |
| 50 | - * @param processInstanceId 流程实例 Id | |
| 51 | - * @param activityId 流程活动编号 Id | |
| 52 | - * @return 抄送人 Ids | |
| 53 | - */ | |
| 54 | - Set<Long> getCopyUserIds(String processInstanceId, String activityId); | |
| 55 | 52 | |
| 56 | 53 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
| ... | ... | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.ObjectUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 6 | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; |
| 7 | 6 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| 8 | 7 | import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper; |
| ... | ... | @@ -19,7 +18,6 @@ import org.springframework.validation.annotation.Validated; |
| 19 | 18 | |
| 20 | 19 | import java.util.Collection; |
| 21 | 20 | import java.util.List; |
| 22 | -import java.util.Set; | |
| 23 | 21 | |
| 24 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 25 | 23 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| ... | ... | @@ -49,17 +47,19 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy |
| 49 | 47 | private BpmProcessDefinitionService processDefinitionService; |
| 50 | 48 | |
| 51 | 49 | @Override |
| 52 | - public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) { | |
| 50 | + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) { | |
| 53 | 51 | Task task = taskService.getTask(taskId); |
| 54 | 52 | if (ObjectUtil.isNull(task)) { |
| 55 | 53 | throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); |
| 56 | 54 | } |
| 57 | - String processInstanceId = task.getProcessInstanceId(); | |
| 58 | - createProcessInstanceCopy(userIds, processInstanceId, task.getTaskDefinitionKey(), task.getId(), task.getName()); | |
| 55 | + // 执行抄送 | |
| 56 | + createProcessInstanceCopy(userIds, reason, | |
| 57 | + task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName()); | |
| 59 | 58 | } |
| 60 | 59 | |
| 61 | 60 | @Override |
| 62 | - public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName) { | |
| 61 | + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String processInstanceId, | |
| 62 | + String activityId, String activityName, String taskId) { | |
| 63 | 63 | // 1.1 校验流程实例存在 |
| 64 | 64 | ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); |
| 65 | 65 | if (processInstance == null) { |
| ... | ... | @@ -74,10 +74,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy |
| 74 | 74 | |
| 75 | 75 | // 2. 创建抄送流程 |
| 76 | 76 | List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() |
| 77 | - .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) | |
| 77 | + .setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId())) | |
| 78 | 78 | .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) |
| 79 | - .setCategory(processDefinition.getCategory()).setActivityId(activityId) | |
| 80 | - .setTaskId(taskId).setTaskName(taskName)); | |
| 79 | + .setCategory(processDefinition.getCategory()).setTaskId(taskId) | |
| 80 | + .setActivityId(activityId).setActivityName(activityName)); | |
| 81 | 81 | processInstanceCopyMapper.insertBatch(copyList); |
| 82 | 82 | } |
| 83 | 83 | |
| ... | ... | @@ -87,10 +87,4 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy |
| 87 | 87 | return processInstanceCopyMapper.selectPage(userId, pageReqVO); |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | - @Override | |
| 91 | - public Set<Long> getCopyUserIds(String processInstanceId, String activityId) { | |
| 92 | - return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcessInstanceIdAndActivityId(processInstanceId, activityId), | |
| 93 | - BpmProcessInstanceCopyDO::getUserId); | |
| 94 | - } | |
| 95 | - | |
| 96 | 90 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
| ... | ... | @@ -84,14 +84,6 @@ public interface BpmProcessInstanceService { |
| 84 | 84 | PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, |
| 85 | 85 | @Valid BpmProcessInstancePageReqVO pageReqVO); |
| 86 | 86 | |
| 87 | - /** | |
| 88 | - * 获得表单字段权限 | |
| 89 | - * | |
| 90 | - * @param reqVO 请求消息 | |
| 91 | - * @return 表单字段权限 | |
| 92 | - */ | |
| 93 | - Map<String, String> getFormFieldsPermission(@Valid BpmFormFieldsPermissionReqVO reqVO); | |
| 94 | - | |
| 95 | 87 | // TODO @芋艿:重点在 review 下 |
| 96 | 88 | /** |
| 97 | 89 | * 获取审批详情。 |
| ... | ... | @@ -104,6 +96,14 @@ public interface BpmProcessInstanceService { |
| 104 | 96 | */ |
| 105 | 97 | BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); |
| 106 | 98 | |
| 99 | + /** | |
| 100 | + * 获取流程实例的 BPMN 模型视图 | |
| 101 | + * | |
| 102 | + * @param id 流程实例的编号 | |
| 103 | + * @return BPMN 模型视图 | |
| 104 | + */ | |
| 105 | + BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id); | |
| 106 | + | |
| 107 | 107 | // ========== Update 写入相关方法 ========== |
| 108 | 108 | |
| 109 | 109 | /** |
| ... | ... | @@ -157,5 +157,4 @@ public interface BpmProcessInstanceService { |
| 157 | 157 | */ |
| 158 | 158 | void processProcessInstanceCompleted(ProcessInstance instance); |
| 159 | 159 | |
| 160 | - | |
| 161 | 160 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| 1 | -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.task.bo.AlreadyRunApproveNodeRespBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.User; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private ManagementService managementService; @Resource private BpmActivityService activityService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override public Map<String, String> getFormFieldsPermission(BpmFormFieldsPermissionReqVO reqVO) { // 1.1 获取流程定义 Id String processDefinitionId = reqVO.getProcessDefinitionId(); if (StrUtil.isEmpty(processDefinitionId) && StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processDefinitionId = processInstance.getProcessDefinitionId(); } // 1.2 获取流程活动编号 String activityId = reqVO.getActivityId(); if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(reqVO.getTaskId())) { // 流程活动 Id 为空。从流程任务中获取流程活动 Id activityId = Optional.ofNullable(taskService.getHistoricTask(reqVO.getTaskId())) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission( processDefinitionService.getProcessDefinitionBpmnModel(processDefinitionId), activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long startUserId, BpmApprovalDetailReqVO reqVO) { // 1. 审批详情 BpmApprovalDetailRespVO respVO = new BpmApprovalDetailRespVO(); String processDefinitionId = reqVO.getProcessDefinitionId(); ProcessInstance runProcessInstance = null; // 正在运行的流程实例 Set<String> runNodeIds = new HashSet<>(); // 已经运行的节点 Ids (BPMN XML 节点 Id) Map<String, ApprovalNodeInfo> runningApprovalNodes = new HashMap<>(); // 正在运行的节点的审批信息 List<ApprovalNodeInfo> approvalNodes = new ArrayList<>(); // 1.1 情况一:流程未发起 if (reqVO.getProcessInstanceId() == null) { respVO.setStatus(BpmProcessInstanceStatusEnum.NOT_START.getStatus()); // 1.2 情况二:流程已发起 } else { // 1.2.1 获取流程实例状态 HistoricProcessInstance processInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance); respVO.setStatus(processInstanceStatus); // 1.2.2 构建已运行节点的审批信息 List<HistoricActivityInstance> historicActivityList = activityService.getActivityListByProcessInstanceId(processInstance.getId()); AlreadyRunApproveNodeRespBO respBO = buildAlreadyRunApproveNodes(processInstance.getId(), processInstanceStatus, historicActivityList); approvalNodes = respBO.getApproveNodes(); runNodeIds = respBO.getRunNodeIds(); // 1.2.3 特殊:流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { respVO.setApproveNodes(approvalNodes); return respVO; } runningApprovalNodes = respBO.getRunningApprovalNodes(); processDefinitionId = processInstance.getProcessDefinitionId(); runProcessInstance = getProcessInstance(processInstance.getId()); startUserId = Long.valueOf(runProcessInstance.getStartUserId()); } // 2. 流程未结束,预测未运行节点的审批信息。需要区分 BPMN 设计器 和 SIMPLE 设计器 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); // 2.1 情况一:仿钉钉流程设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<ApprovalNodeInfo> notRunApproveNodes = new ArrayList<>(); traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, runProcessInstance, simpleModel, runNodeIds, runningApprovalNodes, notRunApproveNodes); approvalNodes.addAll(notRunApproveNodes); respVO.setApproveNodes(approvalNodes); // 会不会有极端的情况:对于依次审批来说,它是已经 running,但是当前节点也要计算其它审批人?(已修改) // 2.2 情况二:BPMN 流程设计器 } else if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { // TODO 芋艿:需要把 start 节点加出来 // TODO Bpmn 设计器,构建未运行节点的审批信息;未完全实现 respVO.setApproveNodes(approvalNodes); } return respVO; } /** * 遍历 SIMPLE 设计器模型 构建未运行节点的审批信息 * * @param startUserId 流程发起人编号 * @param processInstance 流程实例 * @param simpleModelNode SIMPLE 设计器模型 * @param runNodeIds 已经运行节点的 Ids * @param runningApprovalNodes 正在运行的节点的审批信息 * @param approveNodeList 未运行节点的审批信息列表 */ private void traverseSimpleModelNodeToBuildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO simpleModelNode, Set<String> runNodeIds, Map<String, ApprovalNodeInfo> runningApprovalNodes, List<ApprovalNodeInfo> approveNodeList) { if (!SimpleModelUtils.isValidNode(simpleModelNode)) { return; } buildNotRunApproveNodes(startUserId, processInstance, simpleModelNode, runNodeIds, runningApprovalNodes, approveNodeList); // 如果有子节点递归遍历子节点 traverseSimpleModelNodeToBuildNotRunApproveNodes( startUserId, processInstance, simpleModelNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); } private void buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set<String> runNodeIds, Map<String, ApprovalNodeInfo> runningApprovalNodes, List<ApprovalNodeInfo> approveNodeList) { // 情况一:节点未运行:需要进行预测 if (!runNodeIds.contains(node.getId())) { // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()) || START_USER_NODE.getType().equals(node.getType())) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUserList( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); approveNodeList.add(approvalNodeInfo); // 2. 对分支节点,进行预测 } else if (BpmSimpleModelNodeType.isBranchNode(node.getType())) { // 并行分支,不用预测条件。所有分支都需要遍历 if (PARALLEL_BRANCH_NODE.getType().equals(node.getType())) { node.getConditionNodes().forEach(conditionNode -> traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList)); } else if (CONDITION_BRANCH_NODE.getType().equals(node.getType())) { for (BpmSimpleModelNodeVO conditionNode : node.getConditionNodes()) { // 满足一个条件, 遍历该分支并 if ((processInstance != null && evalConditionExpress(processInstance, SimpleModelUtils.buildConditionExpression(conditionNode))) // 预测条件表达式的值 || BooleanUtil.isTrue(conditionNode.getDefaultFlow())) { // 是否默认的序列 traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode(), runNodeIds, runningApprovalNodes, approveNodeList); break; } } } // TODO 包容分支待实现 // 3. 结束节点 } else if (END_NODE.getType().equals(node.getType())) { ApprovalNodeInfo nodeProgress = new ApprovalNodeInfo(); nodeProgress.setNodeType(node.getType()); nodeProgress.setName(node.getName()); nodeProgress.setStatus(NOT_START.getStatus()); approveNodeList.add(nodeProgress); } } else { // 情况二:节点已经运行 // 如果是分支节点,需要检查分支节点的运行情况 if (BpmSimpleModelNodeType.isBranchNode(node.getType()) && ArrayUtil.isNotEmpty(node.getConditionNodes())) { node.getConditionNodes().forEach(conditionNode -> { // 只有运行的条件,才需要遍历 if (runNodeIds.contains(conditionNode.getId())) { traverseSimpleModelNodeToBuildNotRunApproveNodes(startUserId, processInstance, conditionNode.getChildNode() , runNodeIds, runningApprovalNodes, approveNodeList); } }); // 如果是依次审批, 需要加其它未审批候选人 } else if (SimpleModelUtils.isSequentialApproveNode(node) && runningApprovalNodes.containsKey(node.getId())) { ApprovalNodeInfo approvalNodeInfo = runningApprovalNodes.get(node.getId()); List<User> candidateUserList = getNotRunTaskCandidateUserList( startUserId, processInstance, node.getId(), node.getCandidateStrategy(), node.getCandidateParam()); // TODO @jason:这里的逻辑,可能可以简化成,直接拿已经审批过的人的 userId 集合,从 candidateUserList remove 下。一方面简单一点,方面 calculateUsers 返回的是 set,目前不是很有序。 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Long currentAssignedUserId = null; if (approvalTaskInfo != null && approvalTaskInfo.getAssigneeUser() != null) { currentAssignedUserId = approvalTaskInfo.getAssigneeUser().getId(); } // 找到当前审批人在候选人列表的位置 int index = 0; for (User user : candidateUserList) { if (user.getId().equals(currentAssignedUserId)) { break; } index++; } // 截取当前审批人位置后面的候选人, 不包含当前审批人 approvalNodeInfo.setCandidateUserList(CollUtil.sub(candidateUserList, ++index, candidateUserList.size())); } } } /** * 从已经运行活动节点构建的审批信息列表 * * @param processInstanceId 流程实例 Id * @param processInstanceStatus 流程实例状态 * @param historicActivityList 已经运行活动 */ private AlreadyRunApproveNodeRespBO buildAlreadyRunApproveNodes(String processInstanceId, Integer processInstanceStatus, List<HistoricActivityInstance> historicActivityList) { // 1.1 获取待处理活动:只有 "userTask" 和 "endEvent" 需要处理 List<HistoricActivityInstance> pendingActivityNodes = filterList(historicActivityList, item -> BpmSimpleModelNodeType.isRecordNode(item.getActivityType())); // 1.2 已运行节点的 activityId Set<String> runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 2.1 获取已运行的任务(包括运行中的任务) List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); Map<String, HistoricTaskInstance> taskMap = convertMap(taskList, HistoricTaskInstance::getId); // 2.2 获取加签的任务 Map<String, List<HistoricTaskInstance>> addSignTaskMap = convertMultiMap( filterList(taskList, task -> StrUtil.isNotEmpty(task.getParentTaskId())), HistoricTaskInstance::getParentTaskId); // 3.1 获取节点的用户信息 Set<Long> userIds = CollectionUtils.convertSetByFlatMap(pendingActivityNodes, activity -> { if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Set<Long> taskUsers = CollUtil.newHashSet(); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(task.getOwner(), null)); List<HistoricTaskInstance> addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { addSignTasks.forEach(item -> { CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getAssignee(), null)); CollUtil.addIfAbsent(taskUsers, NumberUtil.parseLong(item.getOwner(), null)); }); } return taskUsers.stream(); } else { return Stream.empty(); } }); Map<Long, AdminUserRespDTO> userMap = convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId); // 3.2 已经结束的任务转换为审批信息 final Multimap<String, HistoricActivityInstance> runningTask = ArrayListMultimap.create(); // 运行中的任务 List<ApprovalNodeInfo> approvalNodeList = convertList(pendingActivityNodes, activity -> { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo().setId(activity.getId()).setName(activity.getActivityName()) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); if (BPMN_USER_TASK_TYPE.equals(activity.getActivityType())) { // 用户任务 // nodeType approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(activity.getActivityId()) ? START_USER_NODE.getType() : APPROVE_NODE.getType()); // status HistoricTaskInstance task = taskMap.get(activity.getTaskId()); Integer taskStatus = FlowableUtils.getTaskStatus(task); // 运行中的任务, 会签,或签任务聚合在一起。 if (!BpmTaskStatusEnum.isEndStatus(taskStatus)) { runningTask.put(activity.getActivityId(), activity); return null; } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); List<ApprovalTaskInfo> approveTasks = CollUtil.newArrayList(approveTask); List<HistoricTaskInstance> addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } approvalNodeInfo.setStatus(taskStatus); approvalNodeInfo.setTasks(approveTasks); } else if (END_NODE.getBpmnType().equals(activity.getActivityType())) { approvalNodeInfo.setNodeType(END_NODE.getType()); approvalNodeInfo.setStatus(processInstanceStatus); } return approvalNodeInfo; }); // 3.3 运行中的任务转换为审批信息。 final Map<String, ApprovalNodeInfo> runningApprovalNodes = new HashMap<>(); // 正在运行节点的审批信息 runningTask.asMap().forEach((activityId, activities) -> { if (CollUtil.isNotEmpty(activities)) { ApprovalNodeInfo approvalNodeInfo = new ApprovalNodeInfo(); approvalNodeInfo.setNodeType(APPROVE_NODE.getType()); approvalNodeInfo.setStatus(RUNNING.getStatus()); List<ApprovalTaskInfo> approveTasks = CollUtil.newArrayList(); int i = 0; for (HistoricActivityInstance activity : activities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); // 取第一个任务, 会签/或签的任务。开始时间相同的 if (i == 0) { approvalNodeInfo.setId(activity.getId()).setName(activity.getActivityName()). setStartTime(DateUtils.of(activity.getStartTime())); } // tasks ApprovalTaskInfo approveTask = convertApproveTaskInfo(task, userMap); approveTasks.add(approveTask); List<HistoricTaskInstance> addSignTasks = addSignTaskMap.get(activity.getTaskId()); if (CollUtil.isNotEmpty(addSignTasks)) { // 处理加签任务 approveTasks.addAll(convertList(addSignTasks, item -> convertApproveTaskInfo(item, userMap))); } i++; } approvalNodeInfo.setTasks(approveTasks); approvalNodeList.add(approvalNodeInfo); runningApprovalNodes.put(activityId, approvalNodeInfo); } }); return new AlreadyRunApproveNodeRespBO().setApproveNodes(approvalNodeList).setRunNodeIds(runNodeIds) .setRunningApprovalNodes(runningApprovalNodes); } private ApprovalTaskInfo convertApproveTaskInfo(HistoricTaskInstance task, Map<Long, AdminUserRespDTO> userMap) { if (task == null) { return null; } ApprovalTaskInfo approveTask = BeanUtils.toBean(task, ApprovalTaskInfo.class); approveTask.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); Long taskAssignee = NumberUtil.parseLong(task.getAssignee(), null); if (taskAssignee != null) { approveTask.setAssigneeUser(BeanUtils.toBean(userMap.get(taskAssignee), User.class)); } Long taskOwner = NumberUtil.parseLong(task.getOwner(), null); if (taskOwner != null) { approveTask.setOwnerUser(BeanUtils.toBean(userMap.get(taskOwner), User.class)); } return approveTask; } private List<User> getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set<Long> userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map<Long, AdminUserRespDTO> adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), User.class)); } /** * 计算条件表达式的值 * * @param processInstance 流程实例 * @param express 条件表达式 */ private Boolean evalConditionExpress(ProcessInstance processInstance, String express) { if (express == null) { return Boolean.FALSE; } Object result = managementService.executeCommand(context -> { try { return FlowableUtils.getExpressionValue((VariableContainer) processInstance, express); } catch (FlowableException ex) { log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex); return Boolean.FALSE; } }); return Boolean.TRUE.equals(result); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } | |
| 2 | 1 | \ No newline at end of file |
| 2 | +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, String activityId, String taskId) { // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1.1 从 reqVO 中,读取公共变量 Long startUserId = loginUserId; // 流程发起人 HistoricProcessInstance historicProcessInstance = null; // 流程实例 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 if (reqVO.getProcessInstanceId() != null) { historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (historicProcessInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); processVariables = historicProcessInstance.getProcessVariables(); } // 1.3 读取其它相关数据 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); // 2.1 已结束 + 进行中的活动节点 List<ActivityNode> endActivityNodes = null; // 已结束的审批信息 List<ActivityNode> runActivityNodes = null; // 进行中的审批信息 List<HistoricActivityInstance> activities = null; // 流程实例列表 if (reqVO.getProcessInstanceId() != null) { activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId()); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true); endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo, historicProcessInstance, processInstanceStatus, activities, tasks); runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks); } // 2.2 流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, null, null); } // 3.1 计算当前登录用户的待办任务 // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。 BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); // 3.2 预测未运行节点的审批信息 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo, processVariables, activities); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); } /** * 拼接审批详情的最终数据 * <p> * 主要是,拼接审批人的用户信息、部门信息 */ private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, BpmnModel bpmnModel, ProcessDefinition processDefinition, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance processInstance, Integer processInstanceStatus, List<ActivityNode> endApprovalNodeInfos, List<ActivityNode> runningApprovalNodeInfos, List<ActivityNode> simulateApprovalNodeInfos, BpmTaskRespVO todoTask) { // 1. 获取所有需要读取用户信息的 userIds List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); // 2. 表单权限 Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId()); // 3. 拼接数据 return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance, processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap); } /** * 获得【已结束】的活动节点们 */ private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); List<ActivityNode> approvalNodes = convertList(endTasks, task -> { FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ? BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setStatus(FlowableUtils.getTaskStatus(task)) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); // 如果是取消状态,则跳过 if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { return null; } return activityNode; }); // 遍历 activities,只处理已结束的 StartEvent、EndEvent List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END))); endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID) .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); ActivityNode startNode = new ActivityNode().setId(startTask.getId()) .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType()) .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask)) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); approvalNodes.add(0, startNode); return; } // EndEvent if (ELEMENT_EVENT_END.equals(activity.getActivityType())) { if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) { // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示 return; } ActivityNode endNode = new ActivityNode().setId(activity.getId()) .setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance); if (StrUtil.isNotEmpty(reason)) { endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId()) .setStatus(endNode.getStatus()).setReason(reason))); } approvalNodes.add(endNode); } }); return approvalNodes; } /** * 获得【进行中】的活动节点们 */ private List<ActivityNode> getRunApproveNodeList(Long startUserId, BpmnModel bpmnModel, ProcessDefinition processDefinition, Map<String, Object> processVariables, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 构建运行中的任务,基于 activityId 分组 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId); // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId); return convertList(runningTaskMap.entrySet(), entry -> { String activityId = entry.getKey(); List<HistoricActivityInstance> taskActivities = entry.getValue(); // 构建活动节点 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName()) .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime())) .setTasks(new ArrayList<>()); // 处理每个任务的 tasks 属性 for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); // 加签子任务,需要过滤掉已经完成的加签子任务 List<HistoricTaskInstance> childrenTasks = filterList( taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), childTask -> childTask.getEndTime() == null); if (CollUtil.isNotEmpty(childrenTasks)) { activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); } } // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task if (BpmnModelUtils.isSequentialUserTask(flowNode)) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(), startUserId, processDefinition.getId(), processVariables); // 截取当前审批人位置后面的候选人,不包含当前审批人 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(), approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return activityNode; }); } /** * 获得【预测(未来)】的活动节点们 */ private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, List<HistoricActivityInstance> activities) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); // 情况一:BPMN 设计器 if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, processDefinitionInfo, processVariables, flowElement, runActivityIds)); } // 情况二:SIMPLE 设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, processDefinitionInfo, processVariables, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, BpmSimpleModelNodeVO node, Set<String> runActivityIds) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy()) .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点/审批节点 if (ObjectUtils.equalsAny(node.getType(), BpmSimpleModelNodeType.START_USER_NODE.getType(), BpmSimpleModelNodeType.APPROVE_NODE.getType())) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); activityNode.setCandidateUserIds(candidateUserIds); return activityNode; } // 2. 结束节点 if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) { return activityNode; } // 3. 抄送节点 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) { return activityNode; } return null; } private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, FlowElement node, Set<String> runActivityIds) { if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点 if (node instanceof StartEvent) { return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType()); } // 2. 审批节点 if (node instanceof UserTask) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) .setCandidateUserIds(candidateUserIds); } // 3. 结束节点 if (node instanceof EndEvent) { return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()); } return null; } private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, startUserId, processDefinitionId, processVariables); return new ArrayList<>(userIds); } @Override public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) { // 1.1 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获得流程定义 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); if (bpmnModel == null) { return null; } BpmSimpleModelNodeVO simpleModel = null; BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) { simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds); // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点 Set<String> rejectTaskActivityIds = CollUtil.newHashSet(); if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) { tasks.stream() .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task))) .max(Comparator.comparing(HistoricTaskInstance::getEndTime)) .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey())); finishedTaskActivityIds.removeAll(rejectTaskActivityIds); } // 2.2 拼接基础信息 Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel, unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds, userMap, deptMap); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel); if (CollUtil.isEmpty(tasks)) { return; } // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了 tasks.forEach(task -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } | |
| 3 | 3 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
| ... | ... | @@ -6,7 +6,9 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; |
| 7 | 7 | import jakarta.validation.Valid; |
| 8 | 8 | import org.flowable.bpmn.model.UserTask; |
| 9 | +import org.flowable.engine.history.HistoricActivityInstance; | |
| 9 | 10 | import org.flowable.task.api.Task; |
| 11 | +import org.flowable.task.api.TaskInfo; | |
| 10 | 12 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 11 | 13 | |
| 12 | 14 | import java.util.Collection; |
| ... | ... | @@ -33,6 +35,15 @@ public interface BpmTaskService { |
| 33 | 35 | PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO); |
| 34 | 36 | |
| 35 | 37 | /** |
| 38 | + * 获得用户在指定流程下,首个需要处理(待办)的任务 | |
| 39 | + * | |
| 40 | + * @param userId 用户编号 | |
| 41 | + * @param processInstanceId 流程实例编号 | |
| 42 | + * @return 待办任务 | |
| 43 | + */ | |
| 44 | + BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId); | |
| 45 | + | |
| 46 | + /** | |
| 36 | 47 | * 获得已办的流程任务分页 |
| 37 | 48 | * |
| 38 | 49 | * @param userId 用户编号 |
| ... | ... | @@ -73,9 +84,10 @@ public interface BpmTaskService { |
| 73 | 84 | * 获得指定流程实例的流程任务列表,包括所有状态的 |
| 74 | 85 | * |
| 75 | 86 | * @param processInstanceId 流程实例的编号 |
| 87 | + * @param asc 是否升序 | |
| 76 | 88 | * @return 流程任务列表 |
| 77 | 89 | */ |
| 78 | - List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId); | |
| 90 | + List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc); | |
| 79 | 91 | |
| 80 | 92 | /** |
| 81 | 93 | * 获取任务 |
| ... | ... | @@ -105,22 +117,31 @@ public interface BpmTaskService { |
| 105 | 117 | * 根据条件查询正在进行中的任务 |
| 106 | 118 | * |
| 107 | 119 | * @param processInstanceId 流程实例编号,不允许为空 |
| 108 | - * @param assigned 是否分配了审批人,允许空 | |
| 109 | - * @param taskDefineKey 任务定义 Key,允许空 | |
| 120 | + * @param assigned 是否分配了审批人,允许空 | |
| 121 | + * @param taskDefineKey 任务定义 Key,允许空 | |
| 110 | 122 | */ |
| 111 | 123 | List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, |
| 112 | 124 | Boolean assigned, |
| 113 | 125 | String taskDefineKey); |
| 114 | 126 | |
| 115 | 127 | /** |
| 116 | - * 获取当前任务的可回退的 UserTask 集合 | |
| 128 | + * 获取当前任务的可退回的 UserTask 集合 | |
| 117 | 129 | * |
| 118 | 130 | * @param id 当前的任务 ID |
| 119 | - * @return 可以回退的节点列表 | |
| 131 | + * @return 可以退回的节点列表 | |
| 120 | 132 | */ |
| 121 | 133 | List<UserTask> getUserTaskListByReturn(String id); |
| 122 | 134 | |
| 123 | 135 | /** |
| 136 | + * 获取指定任务的子任务列表(多层) | |
| 137 | + * | |
| 138 | + * @param parentTaskId 父任务 ID | |
| 139 | + * @param tasks 任务列表 | |
| 140 | + * @return 子任务列表 | |
| 141 | + */ | |
| 142 | + <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks); | |
| 143 | + | |
| 144 | + /** | |
| 124 | 145 | * 获取指定任务的子任务列表 |
| 125 | 146 | * |
| 126 | 147 | * @param parentTaskId 父任务ID |
| ... | ... | @@ -129,12 +150,20 @@ public interface BpmTaskService { |
| 129 | 150 | List<Task> getTaskListByParentTaskId(String parentTaskId); |
| 130 | 151 | |
| 131 | 152 | /** |
| 132 | - * 通过任务 ID,查询任务名 Map | |
| 153 | + * 获得指定流程实例的活动实例列表 | |
| 133 | 154 | * |
| 134 | - * @param taskIds 任务 ID | |
| 135 | - * @return 任务 ID 与名字的 Map | |
| 155 | + * @param processInstanceId 流程实例的编号 | |
| 156 | + * @return 活动实例列表 | |
| 136 | 157 | */ |
| 137 | - Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds); | |
| 158 | + List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId); | |
| 159 | + | |
| 160 | + /** | |
| 161 | + * 获得执行编号对应的活动实例 | |
| 162 | + * | |
| 163 | + * @param executionId 执行编号 | |
| 164 | + * @return 活动实例 | |
| 165 | + */ | |
| 166 | + List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId); | |
| 138 | 167 | |
| 139 | 168 | // ========== Update 写入相关方法 ========== |
| 140 | 169 | |
| ... | ... | @@ -170,10 +199,10 @@ public interface BpmTaskService { |
| 170 | 199 | void moveTaskToEnd(String processInstanceId); |
| 171 | 200 | |
| 172 | 201 | /** |
| 173 | - * 将任务回退到指定的 targetDefinitionKey 位置 | |
| 202 | + * 将任务退回到指定的 targetDefinitionKey 位置 | |
| 174 | 203 | * |
| 175 | 204 | * @param userId 用户编号 |
| 176 | - * @param reqVO 回退的任务key和当前所在的任务ID | |
| 205 | + * @param reqVO 退回的任务key和当前所在的任务ID | |
| 177 | 206 | */ |
| 178 | 207 | void returnTask(Long userId, BpmTaskReturnReqVO reqVO); |
| 179 | 208 | |
| ... | ... | @@ -201,14 +230,22 @@ public interface BpmTaskService { |
| 201 | 230 | */ |
| 202 | 231 | void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); |
| 203 | 232 | |
| 233 | + /** | |
| 234 | + * 抄送任务 | |
| 235 | + * | |
| 236 | + * @param userId 用户编号 | |
| 237 | + * @param reqVO 通过请求 | |
| 238 | + */ | |
| 239 | + void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); | |
| 240 | + | |
| 204 | 241 | // ========== Event 事件相关方法 ========== |
| 205 | 242 | |
| 206 | 243 | /** |
| 207 | 244 | * 处理 Task 创建事件,目前是 |
| 208 | - * | |
| 245 | + * <p> | |
| 209 | 246 | * 1. 更新它的状态为审批中 |
| 210 | 247 | * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 |
| 211 | - * | |
| 248 | + * <p> | |
| 212 | 249 | * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 |
| 213 | 250 | * |
| 214 | 251 | * @param task 任务实体 |
| ... | ... | @@ -233,8 +270,8 @@ public interface BpmTaskService { |
| 233 | 270 | * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 |
| 234 | 271 | * |
| 235 | 272 | * @param processInstanceId 流程示例编号 |
| 236 | - * @param taskDefineKey 任务 Key | |
| 237 | - * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} | |
| 273 | + * @param taskDefineKey 任务 Key | |
| 274 | + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} | |
| 238 | 275 | */ |
| 239 | 276 | void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); |
| 240 | 277 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| ... | ... | @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableCon |
| 21 | 21 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 22 | 22 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 23 | 23 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |
| 24 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; | |
| 24 | 25 | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; |
| 25 | 26 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; |
| 26 | 27 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| ... | ... | @@ -38,9 +39,11 @@ import org.flowable.engine.HistoryService; |
| 38 | 39 | import org.flowable.engine.ManagementService; |
| 39 | 40 | import org.flowable.engine.RuntimeService; |
| 40 | 41 | import org.flowable.engine.TaskService; |
| 42 | +import org.flowable.engine.history.HistoricActivityInstance; | |
| 41 | 43 | import org.flowable.engine.runtime.ProcessInstance; |
| 42 | 44 | import org.flowable.task.api.DelegationState; |
| 43 | 45 | import org.flowable.task.api.Task; |
| 46 | +import org.flowable.task.api.TaskInfo; | |
| 44 | 47 | import org.flowable.task.api.TaskQuery; |
| 45 | 48 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 46 | 49 | import org.flowable.task.api.history.HistoricTaskInstanceQuery; |
| ... | ... | @@ -81,6 +84,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 81 | 84 | @Resource |
| 82 | 85 | private BpmProcessInstanceService processInstanceService; |
| 83 | 86 | @Resource |
| 87 | + private BpmProcessDefinitionService bpmProcessDefinitionService; | |
| 88 | + @Resource | |
| 84 | 89 | private BpmProcessInstanceCopyService processInstanceCopyService; |
| 85 | 90 | @Resource |
| 86 | 91 | private BpmModelService modelService; |
| ... | ... | @@ -117,6 +122,41 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 117 | 122 | } |
| 118 | 123 | |
| 119 | 124 | @Override |
| 125 | + public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) { | |
| 126 | + if (processInstanceId == null) { | |
| 127 | + return null; | |
| 128 | + } | |
| 129 | + // 1. 查询所有任务 | |
| 130 | + List<Task> tasks = taskService.createTaskQuery() | |
| 131 | + .active() | |
| 132 | + .processInstanceId(processInstanceId) | |
| 133 | + .includeTaskLocalVariables() | |
| 134 | + .includeProcessVariables() | |
| 135 | + .orderByTaskCreateTime().asc() // 按创建时间升序 | |
| 136 | + .list(); | |
| 137 | + if (CollUtil.isEmpty(tasks)) { | |
| 138 | + return null; | |
| 139 | + } | |
| 140 | + | |
| 141 | + // 2.1 查询我的首个任务 | |
| 142 | + Task todoTask = CollUtil.findOne(tasks, task -> { | |
| 143 | + return isAssignUserTask(userId, task) // 当前用户为审批人 | |
| 144 | + || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签) | |
| 145 | + }); | |
| 146 | + if (todoTask == null) { | |
| 147 | + return null; | |
| 148 | + } | |
| 149 | + // 2.2 查询该任务的子任务 | |
| 150 | + List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks); | |
| 151 | + | |
| 152 | + // 3. 转换返回 | |
| 153 | + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId()); | |
| 154 | + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting( | |
| 155 | + bpmnModel, todoTask.getTaskDefinitionKey()); | |
| 156 | + return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting); | |
| 157 | + } | |
| 158 | + | |
| 159 | + @Override | |
| 120 | 160 | public PageResult<HistoricTaskInstance> getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) { |
| 121 | 161 | HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() |
| 122 | 162 | .finished() // 已完成 |
| ... | ... | @@ -170,16 +210,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 170 | 210 | } |
| 171 | 211 | |
| 172 | 212 | @Override |
| 173 | - public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) { | |
| 174 | - List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery() | |
| 213 | + public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) { | |
| 214 | + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() | |
| 175 | 215 | .includeTaskLocalVariables() |
| 176 | - .processInstanceId(processInstanceId) | |
| 177 | - .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序 | |
| 178 | - .list(); | |
| 179 | - if (CollUtil.isEmpty(tasks)) { | |
| 180 | - return Collections.emptyList(); | |
| 216 | + .processInstanceId(processInstanceId); | |
| 217 | + if (Boolean.TRUE.equals(asc)) { | |
| 218 | + query.orderByHistoricTaskInstanceStartTime().asc(); | |
| 219 | + } else { | |
| 220 | + query.orderByHistoricTaskInstanceStartTime().desc(); | |
| 181 | 221 | } |
| 182 | - return tasks; | |
| 222 | + return query.list(); | |
| 183 | 223 | } |
| 184 | 224 | |
| 185 | 225 | /** |
| ... | ... | @@ -254,11 +294,43 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 254 | 294 | if (CollUtil.isEmpty(previousUserList)) { |
| 255 | 295 | return Collections.emptyList(); |
| 256 | 296 | } |
| 257 | - // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 | |
| 297 | + // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 | |
| 258 | 298 | previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); |
| 259 | 299 | return previousUserList; |
| 260 | 300 | } |
| 261 | 301 | |
| 302 | + @Override | |
| 303 | + public <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks) { | |
| 304 | + if (CollUtil.isEmpty(tasks)) { | |
| 305 | + return Collections.emptyList(); | |
| 306 | + } | |
| 307 | + Map<String, List<T>> parentTaskMap = convertMultiMap( | |
| 308 | + filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId); | |
| 309 | + if (CollUtil.isEmpty(parentTaskMap)) { | |
| 310 | + return Collections.emptyList(); | |
| 311 | + } | |
| 312 | + | |
| 313 | + List<T> result = new ArrayList<>(); | |
| 314 | + // 1. 递归获取子级 | |
| 315 | + Stack<String> stack = new Stack<>(); | |
| 316 | + stack.push(parentTaskId); | |
| 317 | + // 2. 递归遍历 | |
| 318 | + for (int i = 0; i < Short.MAX_VALUE; i++) { | |
| 319 | + if (stack.isEmpty()) { | |
| 320 | + break; | |
| 321 | + } | |
| 322 | + // 2.1 获取子任务们 | |
| 323 | + String taskId = stack.pop(); | |
| 324 | + List<T> childTaskList = filterList(tasks, task -> StrUtil.equals(task.getParentTaskId(), taskId)); | |
| 325 | + // 2.2 如果非空,则添加到 stack 进一步递归 | |
| 326 | + if (CollUtil.isNotEmpty(childTaskList)) { | |
| 327 | + stack.addAll(convertList(childTaskList, TaskInfo::getId)); | |
| 328 | + result.addAll(childTaskList); | |
| 329 | + } | |
| 330 | + } | |
| 331 | + return result; | |
| 332 | + } | |
| 333 | + | |
| 262 | 334 | /** |
| 263 | 335 | * 获得所有子任务列表 |
| 264 | 336 | * |
| ... | ... | @@ -331,12 +403,50 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 331 | 403 | } |
| 332 | 404 | |
| 333 | 405 | @Override |
| 334 | - public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) { | |
| 335 | - if (CollUtil.isEmpty(taskIds)) { | |
| 336 | - return Collections.emptyMap(); | |
| 337 | - } | |
| 338 | - List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list(); | |
| 339 | - return convertMap(tasks, Task::getId, Task::getName); | |
| 406 | + public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) { | |
| 407 | + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) | |
| 408 | + .orderByHistoricActivityInstanceStartTime().asc().list(); | |
| 409 | + } | |
| 410 | + | |
| 411 | + @Override | |
| 412 | + public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) { | |
| 413 | + return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); | |
| 414 | + } | |
| 415 | + | |
| 416 | + /** | |
| 417 | + * 判断指定用户,是否是当前任务的审批人 | |
| 418 | + * | |
| 419 | + * @param userId 用户编号 | |
| 420 | + * @param task 任务 | |
| 421 | + * @return 是否 | |
| 422 | + */ | |
| 423 | + private boolean isAssignUserTask(Long userId, Task task) { | |
| 424 | + Long assignee = NumberUtil.parseLong(task.getAssignee(), null); | |
| 425 | + return ObjectUtil.equals(userId, assignee); | |
| 426 | + } | |
| 427 | + | |
| 428 | + /** | |
| 429 | + * 判断指定用户,是否是当前任务的拥有人 | |
| 430 | + * | |
| 431 | + * @param userId 用户编号 | |
| 432 | + * @param task 任务 | |
| 433 | + * @return 是否 | |
| 434 | + */ | |
| 435 | + private boolean isOwnerUserTask(Long userId, Task task) { | |
| 436 | + Long assignee = NumberUtil.parseLong(task.getOwner(), null); | |
| 437 | + return ObjectUtil.equal(userId, assignee); | |
| 438 | + } | |
| 439 | + | |
| 440 | + /** | |
| 441 | + * 判断指定用户,是否是当前任务的加签人 | |
| 442 | + * | |
| 443 | + * @param userId 用户 Id | |
| 444 | + * @param task 任务 | |
| 445 | + * @return 是否 | |
| 446 | + */ | |
| 447 | + private boolean isAddSignUserTask(Long userId, Task task) { | |
| 448 | + return (isAssignUserTask(userId, task) || isOwnerUserTask(userId, task)) | |
| 449 | + && BpmTaskSignTypeEnum.of(task.getScopeType()) != null; | |
| 340 | 450 | } |
| 341 | 451 | |
| 342 | 452 | // ========== Update 写入相关方法 ========== |
| ... | ... | @@ -352,11 +462,6 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 352 | 462 | throw exception(PROCESS_INSTANCE_NOT_EXISTS); |
| 353 | 463 | } |
| 354 | 464 | |
| 355 | - // 2. 抄送用户 | |
| 356 | - if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { | |
| 357 | - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); | |
| 358 | - } | |
| 359 | - | |
| 360 | 465 | // 情况一:被委派的任务,不调用 complete 去完成任务 |
| 361 | 466 | if (DelegationState.PENDING.equals(task.getDelegationState())) { |
| 362 | 467 | approveDelegateTask(reqVO, task); |
| ... | ... | @@ -370,12 +475,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 370 | 475 | } |
| 371 | 476 | |
| 372 | 477 | // 情况三:审批普通的任务。大多数情况下,都是这样 |
| 373 | - // 3.1 更新 task 状态、原因 | |
| 478 | + // 2.1 更新 task 状态、原因 | |
| 374 | 479 | updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason()); |
| 375 | - // 3.2 添加评论 | |
| 480 | + // 2.2 添加评论 | |
| 376 | 481 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), |
| 377 | 482 | BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); |
| 378 | - // 3.3 调用 BPM complete 去完成任务 | |
| 483 | + // 2.3 调用 BPM complete 去完成任务 | |
| 379 | 484 | // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 |
| 380 | 485 | if (CollUtil.isNotEmpty(reqVO.getVariables())) { |
| 381 | 486 | Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); |
| ... | ... | @@ -519,7 +624,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 519 | 624 | BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); |
| 520 | 625 | if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { |
| 521 | 626 | String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); |
| 522 | - Assert.notNull(returnTaskId, "回退的节点不能为空"); | |
| 627 | + Assert.notNull(returnTaskId, "退回的节点不能为空"); | |
| 523 | 628 | returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) |
| 524 | 629 | .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); |
| 525 | 630 | return; |
| ... | ... | @@ -563,12 +668,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 563 | 668 | FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), |
| 564 | 669 | reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); |
| 565 | 670 | |
| 566 | - // 2. 调用 Flowable 框架的回退逻辑 | |
| 671 | + // 2. 调用 Flowable 框架的退回逻辑 | |
| 567 | 672 | returnTask(task, targetElement, reqVO); |
| 568 | 673 | } |
| 569 | 674 | |
| 570 | 675 | /** |
| 571 | - * 回退流程节点时,校验目标任务节点是否可回退 | |
| 676 | + * 退回流程节点时,校验目标任务节点是否可退回 | |
| 572 | 677 | * |
| 573 | 678 | * @param sourceKey 当前任务节点 Key |
| 574 | 679 | * @param targetKey 目标任务节点 key |
| ... | ... | @@ -586,7 +691,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 586 | 691 | throw exception(TASK_TARGET_NODE_NOT_EXISTS); |
| 587 | 692 | } |
| 588 | 693 | |
| 589 | - // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 | |
| 694 | + // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 | |
| 590 | 695 | if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { |
| 591 | 696 | throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); |
| 592 | 697 | } |
| ... | ... | @@ -594,10 +699,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 594 | 699 | } |
| 595 | 700 | |
| 596 | 701 | /** |
| 597 | - * 执行回退逻辑 | |
| 702 | + * 执行退回逻辑 | |
| 598 | 703 | * |
| 599 | - * @param currentTask 当前回退的任务 | |
| 600 | - * @param targetElement 需要回退到的目标任务 | |
| 704 | + * @param currentTask 当前退回的任务 | |
| 705 | + * @param targetElement 需要退回到的目标任务 | |
| 601 | 706 | * @param reqVO 前端参数封装 |
| 602 | 707 | */ |
| 603 | 708 | public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { |
| ... | ... | @@ -610,9 +715,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 610 | 715 | List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); |
| 611 | 716 | List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); |
| 612 | 717 | |
| 613 | - // 2. 给当前要被回退的 task 数组,设置回退意见 | |
| 718 | + // 2. 给当前要被退回的 task 数组,设置退回意见 | |
| 614 | 719 | taskList.forEach(task -> { |
| 615 | - // 需要排除掉,不需要设置回退意见的任务 | |
| 720 | + // 需要排除掉,不需要设置退回意见的任务 | |
| 616 | 721 | if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { |
| 617 | 722 | return; |
| 618 | 723 | } |
| ... | ... | @@ -659,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 659 | 764 | taskService.setOwner(taskId, task.getAssignee()); |
| 660 | 765 | // 3.2 执行委派,将任务委派给 delegateUser |
| 661 | 766 | taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); |
| 662 | - // 3.3 更新 task 状态。 | |
| 663 | - // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用 | |
| 664 | - updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus()); | |
| 767 | + // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 | |
| 665 | 768 | } |
| 666 | 769 | |
| 667 | 770 | @Override |
| ... | ... | @@ -868,6 +971,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 868 | 971 | handleParentTaskIfSign(task.getParentTaskId()); |
| 869 | 972 | } |
| 870 | 973 | |
| 974 | + @Override | |
| 975 | + public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) { | |
| 976 | + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); | |
| 977 | + } | |
| 978 | + | |
| 871 | 979 | /** |
| 872 | 980 | * 校验任务是否能被减签 |
| 873 | 981 | * |
| ... | ... | @@ -920,8 +1028,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 920 | 1028 | if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) { |
| 921 | 1029 | return; |
| 922 | 1030 | } |
| 1031 | + // TODO 芋艿:可以后续优化成 getSelf(); | |
| 923 | 1032 | // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 |
| 924 | 1033 | if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { |
| 1034 | + // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝 | |
| 1035 | + if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) { | |
| 1036 | + return; | |
| 1037 | + } | |
| 925 | 1038 | if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { |
| 926 | 1039 | SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() |
| 927 | 1040 | .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); |
| ... | ... | @@ -987,7 +1100,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 987 | 1100 | } |
| 988 | 1101 | // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 |
| 989 | 1102 | if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { |
| 990 | - // 判断是否为回退或者驳回:如果是回退或者驳回不走这个策略 | |
| 1103 | + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 | |
| 991 | 1104 | // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 |
| 992 | 1105 | Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), |
| 993 | 1106 | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.service.task.bo; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | - | |
| 5 | -import java.util.List; | |
| 6 | -import java.util.Map; | |
| 7 | -import java.util.Set; | |
| 8 | - | |
| 9 | -import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo; | |
| 10 | - | |
| 11 | -/** | |
| 12 | - * 已经进行中的审批节点 Response BO | |
| 13 | - * | |
| 14 | - * @author jason | |
| 15 | - */ | |
| 16 | -@Data | |
| 17 | -public class AlreadyRunApproveNodeRespBO { | |
| 18 | - | |
| 19 | - /** | |
| 20 | - * 审批节点信息数组 | |
| 21 | - */ | |
| 22 | - private List<ApprovalNodeInfo> approveNodes; | |
| 23 | - | |
| 24 | - /** | |
| 25 | - * 已运行的节点 ID 数组 (对应 Bpmn XML 节点 id) | |
| 26 | - */ | |
| 27 | - private Set<String> runNodeIds; | |
| 28 | - | |
| 29 | - /** | |
| 30 | - * 正在运行的节点的审批信息(key: activityId, value: 审批信息) | |
| 31 | - * <p> | |
| 32 | - * 用于依次审批,需要加上候选人信息 | |
| 33 | - */ | |
| 34 | - private Map<String, ApprovalNodeInfo> runningApprovalNodes; | |
| 35 | - | |
| 36 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | +import cn.hutool.extra.spring.SpringUtil; | |
| 4 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | 6 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 6 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateUserStrategy; | |
| 7 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other.BpmTaskCandidateAssignEmptyStrategy; | |
| 9 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; | |
| 7 | 10 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 8 | 11 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 12 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 13 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 9 | 14 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 10 | 15 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 16 | +import org.flowable.bpmn.model.BpmnModel; | |
| 17 | +import org.flowable.bpmn.model.ExtensionElement; | |
| 18 | +import org.flowable.bpmn.model.FlowElement; | |
| 11 | 19 | import org.flowable.bpmn.model.UserTask; |
| 12 | 20 | import org.flowable.engine.delegate.DelegateExecution; |
| 21 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 13 | 22 | import org.junit.jupiter.api.BeforeEach; |
| 14 | 23 | import org.junit.jupiter.api.Test; |
| 15 | 24 | import org.mockito.Mock; |
| 25 | +import org.mockito.MockedStatic; | |
| 16 | 26 | import org.mockito.Spy; |
| 27 | +import org.mockito.internal.util.collections.Sets; | |
| 17 | 28 | |
| 18 | -import java.util.Collections; | |
| 19 | -import java.util.List; | |
| 20 | -import java.util.Map; | |
| 21 | -import java.util.Set; | |
| 29 | +import java.util.*; | |
| 22 | 30 | |
| 23 | 31 | import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; |
| 24 | 32 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| 33 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | |
| 34 | +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; | |
| 35 | +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; | |
| 25 | 36 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 26 | 37 | import static org.mockito.ArgumentMatchers.eq; |
| 27 | -import static org.mockito.Mockito.mock; | |
| 28 | -import static org.mockito.Mockito.when; | |
| 38 | +import static org.mockito.Mockito.*; | |
| 29 | 39 | |
| 30 | 40 | /** |
| 31 | 41 | * {@link BpmTaskCandidateInvoker} 的单元测试 |
| ... | ... | @@ -39,44 +49,207 @@ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest { |
| 39 | 49 | @Mock |
| 40 | 50 | private AdminUserApi adminUserApi; |
| 41 | 51 | |
| 52 | + @Mock | |
| 53 | + private BpmProcessInstanceService processInstanceService; | |
| 54 | + | |
| 42 | 55 | @Spy |
| 43 | - private BpmTaskCandidateStrategy strategy ; | |
| 56 | + private BpmTaskCandidateStrategy userStrategy; | |
| 57 | + @Mock | |
| 58 | + private BpmTaskCandidateAssignEmptyStrategy emptyStrategy; | |
| 44 | 59 | |
| 45 | 60 | @Spy |
| 46 | - private List<BpmTaskCandidateStrategy> strategyList ; | |
| 61 | + private List<BpmTaskCandidateStrategy> strategyList; | |
| 47 | 62 | |
| 48 | 63 | @BeforeEach |
| 49 | 64 | public void setUp() { |
| 50 | - strategy = new BpmTaskCandidateUserStrategy(adminUserApi); // 创建strategy实例 | |
| 51 | - strategyList = Collections.singletonList(strategy); // 创建strategyList | |
| 65 | + userStrategy = new BpmTaskCandidateUserStrategy(); // 创建 strategy 实例 | |
| 66 | + when(emptyStrategy.getStrategy()).thenReturn(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY); | |
| 67 | + strategyList = List.of(userStrategy, emptyStrategy); // 创建 strategyList | |
| 52 | 68 | taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi); |
| 53 | 69 | } |
| 54 | 70 | |
| 71 | + /** | |
| 72 | + * 场景:成功计算到候选人,但是移除了发起人的用户 | |
| 73 | + */ | |
| 55 | 74 | @Test |
| 56 | - public void testCalculateUsers() { | |
| 57 | - // 准备参数 | |
| 58 | - String param = "1,2"; | |
| 59 | - DelegateExecution execution = mock(DelegateExecution.class); | |
| 60 | - // mock 方法(DelegateExecution) | |
| 61 | - UserTask userTask = mock(UserTask.class); | |
| 62 | - when(execution.getCurrentFlowElement()).thenReturn(userTask); | |
| 63 | - when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY))) | |
| 64 | - .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString()); | |
| 65 | - when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM))) | |
| 66 | - .thenReturn(param); | |
| 67 | - // mock 方法(adminUserApi) | |
| 68 | - AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) | |
| 69 | - .setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 70 | - AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) | |
| 71 | - .setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 72 | - Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1) | |
| 73 | - .put(user2.getId(), user2).build(); | |
| 74 | - when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); | |
| 75 | + public void testCalculateUsersByTask_some() { | |
| 76 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 77 | + // 准备参数 | |
| 78 | + String param = "1,2"; | |
| 79 | + DelegateExecution execution = mock(DelegateExecution.class); | |
| 80 | + // mock 方法(DelegateExecution) | |
| 81 | + UserTask userTask = mock(UserTask.class); | |
| 82 | + String processInstanceId = randomString(); | |
| 83 | + when(execution.getProcessInstanceId()).thenReturn(processInstanceId); | |
| 84 | + when(execution.getCurrentFlowElement()).thenReturn(userTask); | |
| 85 | + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY))) | |
| 86 | + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString()); | |
| 87 | + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM))) | |
| 88 | + .thenReturn(param); | |
| 89 | + // mock 方法(adminUserApi) | |
| 90 | + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) | |
| 91 | + .setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 92 | + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) | |
| 93 | + .setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 94 | + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1) | |
| 95 | + .put(user2.getId(), user2).build(); | |
| 96 | + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); | |
| 97 | + // mock 移除发起人的用户 | |
| 98 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class)) | |
| 99 | + .thenReturn(processInstanceService); | |
| 100 | + ProcessInstance processInstance = mock(ProcessInstance.class); | |
| 101 | + when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance); | |
| 102 | + when(processInstance.getStartUserId()).thenReturn("1"); | |
| 103 | + mockFlowElementExtensionElement(userTask, BpmnModelConstants.USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, | |
| 104 | + String.valueOf(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())); | |
| 75 | 105 | |
| 76 | - // 调用 | |
| 77 | - Set<Long> results = taskCandidateInvoker.calculateUsers(execution); | |
| 78 | - // 断言 | |
| 79 | - assertEquals(asSet(1L, 2L), results); | |
| 106 | + // 调用 | |
| 107 | + Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution); | |
| 108 | + // 断言 | |
| 109 | + assertEquals(asSet(2L), results); | |
| 110 | + } | |
| 111 | + } | |
| 112 | + | |
| 113 | + /** | |
| 114 | + * 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配 | |
| 115 | + */ | |
| 116 | + @Test | |
| 117 | + public void testCalculateUsersByTask_none() { | |
| 118 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { | |
| 119 | + // 准备参数 | |
| 120 | + String param = "1,2"; | |
| 121 | + DelegateExecution execution = mock(DelegateExecution.class); | |
| 122 | + // mock 方法(DelegateExecution) | |
| 123 | + UserTask userTask = mock(UserTask.class); | |
| 124 | + String processInstanceId = randomString(); | |
| 125 | + when(execution.getProcessInstanceId()).thenReturn(processInstanceId); | |
| 126 | + when(execution.getCurrentFlowElement()).thenReturn(userTask); | |
| 127 | + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY))) | |
| 128 | + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString()); | |
| 129 | + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM))) | |
| 130 | + .thenReturn(param); | |
| 131 | + // mock 方法(adminUserApi) | |
| 132 | + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) | |
| 133 | + .setStatus(CommonStatusEnum.DISABLE.getStatus())); | |
| 134 | + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) | |
| 135 | + .setStatus(CommonStatusEnum.DISABLE.getStatus())); | |
| 136 | + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1) | |
| 137 | + .put(user2.getId(), user2).build(); | |
| 138 | + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); | |
| 139 | + // mock 方法(empty) | |
| 140 | + when(emptyStrategy.calculateUsersByTask(same(execution), same(param))) | |
| 141 | + .thenReturn(Sets.newSet(2L)); | |
| 142 | + // mock 移除发起人的用户 | |
| 143 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class)) | |
| 144 | + .thenReturn(processInstanceService); | |
| 145 | + ProcessInstance processInstance = mock(ProcessInstance.class); | |
| 146 | + when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance); | |
| 147 | + when(processInstance.getStartUserId()).thenReturn("1"); | |
| 148 | + | |
| 149 | + // 调用 | |
| 150 | + Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution); | |
| 151 | + // 断言 | |
| 152 | + assertEquals(asSet(2L), results); | |
| 153 | + } | |
| 154 | + } | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配 | |
| 158 | + */ | |
| 159 | + @Test | |
| 160 | + public void testCalculateUsersByActivity_some() { | |
| 161 | + try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { | |
| 162 | + // 准备参数 | |
| 163 | + String param = "1,2"; | |
| 164 | + BpmnModel bpmnModel = mock(BpmnModel.class); | |
| 165 | + String activityId = randomString(); | |
| 166 | + Long startUserId = 1L; | |
| 167 | + String processDefinitionId = randomString(); | |
| 168 | + Map<String, Object> processVariables = new HashMap<>(); | |
| 169 | + // mock 方法(DelegateExecution) | |
| 170 | + UserTask userTask = mock(UserTask.class); | |
| 171 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask))) | |
| 172 | + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy()); | |
| 173 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask))) | |
| 174 | + .thenReturn(param); | |
| 175 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask); | |
| 176 | + // mock 方法(adminUserApi) | |
| 177 | + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) | |
| 178 | + .setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 179 | + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) | |
| 180 | + .setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 181 | + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1) | |
| 182 | + .put(user2.getId(), user2).build(); | |
| 183 | + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); | |
| 184 | + // mock 移除发起人的用户 | |
| 185 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignStartUserHandlerType(same(userTask))) | |
| 186 | + .thenReturn(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType()); | |
| 187 | + | |
| 188 | + // 调用 | |
| 189 | + Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, | |
| 190 | + startUserId, processDefinitionId, processVariables); | |
| 191 | + // 断言 | |
| 192 | + assertEquals(asSet(2L), results); | |
| 193 | + } | |
| 194 | + } | |
| 195 | + | |
| 196 | + /** | |
| 197 | + * 场景:成功计算到候选人,但是移除了发起人的用户 | |
| 198 | + */ | |
| 199 | + @Test | |
| 200 | + public void testCalculateUsersByActivity_none() { | |
| 201 | + try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { | |
| 202 | + // 准备参数 | |
| 203 | + String param = "1,2"; | |
| 204 | + BpmnModel bpmnModel = mock(BpmnModel.class); | |
| 205 | + String activityId = randomString(); | |
| 206 | + Long startUserId = 1L; | |
| 207 | + String processDefinitionId = randomString(); | |
| 208 | + Map<String, Object> processVariables = new HashMap<>(); | |
| 209 | + // mock 方法(DelegateExecution) | |
| 210 | + UserTask userTask = mock(UserTask.class); | |
| 211 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask))) | |
| 212 | + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy()); | |
| 213 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask))) | |
| 214 | + .thenReturn(param); | |
| 215 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask); | |
| 216 | + // mock 方法(adminUserApi) | |
| 217 | + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) | |
| 218 | + .setStatus(CommonStatusEnum.DISABLE.getStatus())); | |
| 219 | + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) | |
| 220 | + .setStatus(CommonStatusEnum.DISABLE.getStatus())); | |
| 221 | + Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1) | |
| 222 | + .put(user2.getId(), user2).build(); | |
| 223 | + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); | |
| 224 | + // mock 方法(empty) | |
| 225 | + when(emptyStrategy.calculateUsersByActivity(same(bpmnModel), eq(activityId), | |
| 226 | + eq(param), same(startUserId), same(processDefinitionId), same(processVariables))) | |
| 227 | + .thenReturn(Sets.newSet(2L)); | |
| 228 | + | |
| 229 | + // 调用 | |
| 230 | + Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, | |
| 231 | + startUserId, processDefinitionId, processVariables); | |
| 232 | + // 断言 | |
| 233 | + assertEquals(asSet(2L), results); | |
| 234 | + } | |
| 235 | + } | |
| 236 | + | |
| 237 | + private static void mockFlowElementExtensionElement(FlowElement element, String name, String value) { | |
| 238 | + if (value == null) { | |
| 239 | + return; | |
| 240 | + } | |
| 241 | + ExtensionElement extensionElement = new ExtensionElement(); | |
| 242 | + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); | |
| 243 | + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); | |
| 244 | + extensionElement.setElementText(value); | |
| 245 | + extensionElement.setName(name); | |
| 246 | + // mock | |
| 247 | + Map<String, List<ExtensionElement>> extensionElements = element.getExtensionElements(); | |
| 248 | + if (extensionElements == null) { | |
| 249 | + extensionElements = new LinkedHashMap<>(); | |
| 250 | + } | |
| 251 | + extensionElements.put(name, Collections.singletonList(extensionElement)); | |
| 252 | + when(element.getExtensionElements()).thenReturn(extensionElements); | |
| 80 | 253 | } |
| 81 | 254 | |
| 82 | 255 | @Test | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 5 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 6 | +import org.assertj.core.util.Sets; | |
| 7 | +import org.junit.jupiter.api.Test; | |
| 8 | +import org.mockito.InjectMocks; | |
| 9 | +import org.mockito.Mock; | |
| 10 | +import org.mockito.stubbing.Answer; | |
| 11 | + | |
| 12 | +import java.util.Set; | |
| 13 | + | |
| 14 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 15 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 16 | +import static org.mockito.ArgumentMatchers.any; | |
| 17 | +import static org.mockito.Mockito.when; | |
| 18 | + | |
| 19 | +public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest { | |
| 20 | + | |
| 21 | + @InjectMocks | |
| 22 | + private BpmTaskCandidateDeptLeaderMultiStrategy strategy; | |
| 23 | + | |
| 24 | + @Mock | |
| 25 | + private DeptApi deptApi; | |
| 26 | + | |
| 27 | + @Test | |
| 28 | + public void testCalculateUsers() { | |
| 29 | + // 准备参数 | |
| 30 | + String param = "10,20|2"; | |
| 31 | + // mock 方法 | |
| 32 | + when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> { | |
| 33 | + Long deptId = invocationOnMock.getArgument(0); | |
| 34 | + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)); | |
| 35 | + }); | |
| 36 | + | |
| 37 | + // 调用 | |
| 38 | + Set<Long> userIds = strategy.calculateUsers(param); | |
| 39 | + // 断言结果 | |
| 40 | + assertEquals(Sets.newLinkedHashSet(11L, 1001L, 21L, 2001L), userIds); | |
| 41 | + } | |
| 42 | + | |
| 43 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 3 | 4 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 4 | 5 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| 5 | 6 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 6 | -import org.junit.jupiter.api.Disabled; | |
| 7 | +import org.assertj.core.util.Sets; | |
| 7 | 8 | import org.junit.jupiter.api.Test; |
| 8 | 9 | import org.mockito.InjectMocks; |
| 9 | 10 | import org.mockito.Mock; |
| 10 | 11 | |
| 11 | 12 | import java.util.Set; |
| 12 | 13 | |
| 13 | -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | |
| 14 | 14 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| 15 | 15 | import static java.util.Arrays.asList; |
| 16 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 17 | 17 | import static org.mockito.ArgumentMatchers.eq; |
| 18 | 18 | import static org.mockito.Mockito.when; |
| 19 | 19 | |
| 20 | -@Disabled // TODO 芋艿:临时注释 | |
| 21 | 20 | public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest { |
| 22 | 21 | |
| 23 | 22 | @InjectMocks |
| ... | ... | @@ -29,16 +28,16 @@ public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest |
| 29 | 28 | @Test |
| 30 | 29 | public void testCalculateUsers() { |
| 31 | 30 | // 准备参数 |
| 32 | - String param = "1,2"; | |
| 31 | + String param = "10,20"; | |
| 33 | 32 | // mock 方法 |
| 34 | - DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L)); | |
| 35 | - DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L)); | |
| 36 | - when(deptApi.getDeptList(eq(asSet(1L, 2L)))).thenReturn(asList(dept1, dept2)); | |
| 33 | + when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList( | |
| 34 | + randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)), | |
| 35 | + randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L)))); | |
| 37 | 36 | |
| 38 | 37 | // 调用 |
| 39 | - Set<Long> results = strategy.calculateUsers(null, param); | |
| 40 | - // 断言 | |
| 41 | - assertEquals(asSet(11L, 22L), results); | |
| 38 | + Set<Long> userIds = strategy.calculateUsers(param); | |
| 39 | + // 断言结果 | |
| 40 | + assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds); | |
| 42 | 41 | } |
| 43 | 42 | |
| 44 | 43 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 3 | 4 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 5 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 4 | 6 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 5 | 7 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 6 | -import org.junit.jupiter.api.Disabled; | |
| 8 | +import org.assertj.core.util.Sets; | |
| 7 | 9 | import org.junit.jupiter.api.Test; |
| 8 | 10 | import org.mockito.InjectMocks; |
| 9 | 11 | import org.mockito.Mock; |
| 10 | 12 | |
| 11 | -import java.util.List; | |
| 12 | 13 | import java.util.Set; |
| 13 | 14 | |
| 14 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 15 | -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | |
| 15 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 16 | +import static java.util.Arrays.asList; | |
| 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 17 | 18 | import static org.mockito.ArgumentMatchers.eq; |
| 18 | 19 | import static org.mockito.Mockito.when; |
| 19 | 20 | |
| 20 | -@Disabled // TODO 芋艿:临时注释 | |
| 21 | 21 | public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest { |
| 22 | 22 | |
| 23 | 23 | @InjectMocks |
| 24 | 24 | private BpmTaskCandidateDeptMemberStrategy strategy; |
| 25 | 25 | |
| 26 | 26 | @Mock |
| 27 | + private DeptApi deptApi; | |
| 28 | + @Mock | |
| 27 | 29 | private AdminUserApi adminUserApi; |
| 28 | 30 | |
| 29 | 31 | @Test |
| 30 | 32 | public void testCalculateUsers() { |
| 31 | 33 | // 准备参数 |
| 32 | - String param = "11,22"; | |
| 34 | + String param = "10,20"; | |
| 33 | 35 | // mock 方法 |
| 34 | - List<AdminUserRespDTO> users = convertList(asSet(11L, 22L), | |
| 35 | - id -> new AdminUserRespDTO().setId(id)); | |
| 36 | - when(adminUserApi.getUserListByDeptIds(eq(asSet(11L, 22L)))).thenReturn(users); | |
| 36 | + when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList( | |
| 37 | + randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)), | |
| 38 | + randomPojo(AdminUserRespDTO.class, o -> o.setId(21L)))); | |
| 37 | 39 | |
| 38 | 40 | // 调用 |
| 39 | - Set<Long> results = strategy.calculateUsers(null, param); | |
| 40 | - // 断言 | |
| 41 | - assertEquals(asSet(11L, 22L), results); | |
| 41 | + Set<Long> userIds = strategy.calculateUsers(param); | |
| 42 | + // 断言结果 | |
| 43 | + assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds); | |
| 42 | 44 | } |
| 43 | 45 | |
| 44 | 46 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 5 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 6 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 8 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 9 | +import org.assertj.core.util.Sets; | |
| 10 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 11 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 12 | +import org.junit.jupiter.api.Test; | |
| 13 | +import org.mockito.InjectMocks; | |
| 14 | +import org.mockito.Mock; | |
| 15 | +import org.mockito.stubbing.Answer; | |
| 16 | + | |
| 17 | +import java.util.Set; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 20 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 21 | +import static org.mockito.ArgumentMatchers.any; | |
| 22 | +import static org.mockito.ArgumentMatchers.eq; | |
| 23 | +import static org.mockito.Mockito.mock; | |
| 24 | +import static org.mockito.Mockito.when; | |
| 25 | + | |
| 26 | +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest { | |
| 27 | + | |
| 28 | + @InjectMocks | |
| 29 | + private BpmTaskCandidateStartUserDeptLeaderMultiStrategy strategy; | |
| 30 | + | |
| 31 | + @Mock | |
| 32 | + private BpmProcessInstanceService processInstanceService; | |
| 33 | + | |
| 34 | + @Mock | |
| 35 | + private AdminUserApi adminUserApi; | |
| 36 | + @Mock | |
| 37 | + private DeptApi deptApi; | |
| 38 | + | |
| 39 | + @Test | |
| 40 | + public void testCalculateUsersByTask() { | |
| 41 | + // 准备参数 | |
| 42 | + String param = "2"; | |
| 43 | + // mock 方法(获得流程发起人) | |
| 44 | + Long startUserId = 1L; | |
| 45 | + ProcessInstance processInstance = mock(ProcessInstance.class); | |
| 46 | + DelegateExecution execution = mock(DelegateExecution.class); | |
| 47 | + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); | |
| 48 | + when(processInstance.getStartUserId()).thenReturn(startUserId.toString()); | |
| 49 | + // mock 方法(获取发起人的 multi 部门负责人) | |
| 50 | + mockGetStartUserDept(startUserId); | |
| 51 | + | |
| 52 | + // 调用 | |
| 53 | + Set<Long> userIds = strategy.calculateUsersByTask(execution, param); | |
| 54 | + // 断言 | |
| 55 | + assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Test | |
| 59 | + public void testCalculateUsersByActivity() { | |
| 60 | + // 准备参数 | |
| 61 | + String param = "2"; | |
| 62 | + // mock 方法 | |
| 63 | + Long startUserId = 1L; | |
| 64 | + mockGetStartUserDept(startUserId); | |
| 65 | + | |
| 66 | + // 调用 | |
| 67 | + Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param, | |
| 68 | + startUserId, null, null); | |
| 69 | + // 断言 | |
| 70 | + assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds); | |
| 71 | + } | |
| 72 | + | |
| 73 | + private void mockGetStartUserDept(Long startUserId) { | |
| 74 | + when(adminUserApi.getUser(eq(startUserId))).thenReturn( | |
| 75 | + randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))); | |
| 76 | + when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> { | |
| 77 | + Long deptId = invocationOnMock.getArgument(0); | |
| 78 | + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)); | |
| 79 | + }); | |
| 80 | + } | |
| 81 | + | |
| 82 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 5 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 6 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 8 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 9 | +import org.assertj.core.util.Sets; | |
| 10 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 11 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 12 | +import org.junit.jupiter.api.Test; | |
| 13 | +import org.mockito.InjectMocks; | |
| 14 | +import org.mockito.Mock; | |
| 15 | +import org.mockito.stubbing.Answer; | |
| 16 | + | |
| 17 | +import java.util.Set; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 20 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 21 | +import static org.mockito.ArgumentMatchers.any; | |
| 22 | +import static org.mockito.ArgumentMatchers.eq; | |
| 23 | +import static org.mockito.Mockito.mock; | |
| 24 | +import static org.mockito.Mockito.when; | |
| 25 | + | |
| 26 | +public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockitoUnitTest { | |
| 27 | + | |
| 28 | + @InjectMocks | |
| 29 | + private BpmTaskCandidateStartUserDeptLeaderStrategy strategy; | |
| 30 | + | |
| 31 | + @Mock | |
| 32 | + private BpmProcessInstanceService processInstanceService; | |
| 33 | + | |
| 34 | + @Mock | |
| 35 | + private AdminUserApi adminUserApi; | |
| 36 | + @Mock | |
| 37 | + private DeptApi deptApi; | |
| 38 | + | |
| 39 | + @Test | |
| 40 | + public void testCalculateUsersByTask() { | |
| 41 | + // 准备参数 | |
| 42 | + String param = "2"; | |
| 43 | + // mock 方法(获得流程发起人) | |
| 44 | + Long startUserId = 1L; | |
| 45 | + ProcessInstance processInstance = mock(ProcessInstance.class); | |
| 46 | + DelegateExecution execution = mock(DelegateExecution.class); | |
| 47 | + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); | |
| 48 | + when(processInstance.getStartUserId()).thenReturn(startUserId.toString()); | |
| 49 | + // mock 方法(获取发起人的部门负责人) | |
| 50 | + mockGetStartUserDeptLeader(startUserId); | |
| 51 | + | |
| 52 | + // 调用 | |
| 53 | + Set<Long> userIds = strategy.calculateUsersByTask(execution, param); | |
| 54 | + // 断言 | |
| 55 | + assertEquals(Sets.newLinkedHashSet(1001L), userIds); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Test | |
| 59 | + public void testGetStartUserDeptLeader() { | |
| 60 | + // 准备参数 | |
| 61 | + String param = "2"; | |
| 62 | + // mock 方法 | |
| 63 | + Long startUserId = 1L; | |
| 64 | + mockGetStartUserDeptLeader(startUserId); | |
| 65 | + | |
| 66 | + // 调用 | |
| 67 | + Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param, | |
| 68 | + startUserId, null, null); | |
| 69 | + // 断言 | |
| 70 | + assertEquals(Sets.newLinkedHashSet(1001L), userIds); | |
| 71 | + } | |
| 72 | + | |
| 73 | + private void mockGetStartUserDeptLeader(Long startUserId) { | |
| 74 | + when(adminUserApi.getUser(eq(startUserId))).thenReturn( | |
| 75 | + randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))); | |
| 76 | + when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> { | |
| 77 | + Long deptId = invocationOnMock.getArgument(0); | |
| 78 | + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)); | |
| 79 | + }); | |
| 80 | + } | |
| 81 | + | |
| 82 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
| 2 | + | |
| 3 | +import cn.hutool.core.map.MapUtil; | |
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; | |
| 6 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 7 | +import org.assertj.core.util.Sets; | |
| 8 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 9 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 10 | +import org.junit.jupiter.api.Test; | |
| 11 | +import org.mockito.InjectMocks; | |
| 12 | +import org.mockito.Mock; | |
| 13 | + | |
| 14 | +import java.util.HashMap; | |
| 15 | +import java.util.List; | |
| 16 | +import java.util.Map; | |
| 17 | +import java.util.Set; | |
| 18 | + | |
| 19 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 20 | +import static org.mockito.ArgumentMatchers.eq; | |
| 21 | +import static org.mockito.Mockito.mock; | |
| 22 | +import static org.mockito.Mockito.when; | |
| 23 | + | |
| 24 | +public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnitTest { | |
| 25 | + | |
| 26 | + @InjectMocks | |
| 27 | + private BpmTaskCandidateStartUserSelectStrategy strategy; | |
| 28 | + | |
| 29 | + @Mock | |
| 30 | + private BpmProcessInstanceService processInstanceService; | |
| 31 | + | |
| 32 | + @Test | |
| 33 | + public void testCalculateUsersByTask() { | |
| 34 | + // 准备参数 | |
| 35 | + String param = "2"; | |
| 36 | + // mock 方法(获得流程发起人) | |
| 37 | + ProcessInstance processInstance = mock(ProcessInstance.class); | |
| 38 | + DelegateExecution execution = mock(DelegateExecution.class); | |
| 39 | + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); | |
| 40 | + when(execution.getCurrentActivityId()).thenReturn("activity_001"); | |
| 41 | + // mock 方法(FlowableUtils) | |
| 42 | + Map<String, Object> processVariables = new HashMap<>(); | |
| 43 | + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, | |
| 44 | + MapUtil.of("activity_001", List.of(1L, 2L))); | |
| 45 | + when(processInstance.getProcessVariables()).thenReturn(processVariables); | |
| 46 | + | |
| 47 | + // 调用 | |
| 48 | + Set<Long> userIds = strategy.calculateUsersByTask(execution, param); | |
| 49 | + // 断言 | |
| 50 | + assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Test | |
| 54 | + public void testCalculateUsersByActivity() { | |
| 55 | + // 准备参数 | |
| 56 | + String activityId = "activity_001"; | |
| 57 | + Map<String, Object> processVariables = new HashMap<>(); | |
| 58 | + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, | |
| 59 | + MapUtil.of("activity_001", List.of(1L, 2L))); | |
| 60 | + | |
| 61 | + // 调用 | |
| 62 | + Set<Long> userIds = strategy.calculateUsersByActivity(null, activityId, null, | |
| 63 | + null, null, processVariables); | |
| 64 | + // 断言 | |
| 65 | + assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds); | |
| 66 | + } | |
| 67 | + | |
| 68 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategyTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.ListUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 5 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 6 | +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; | |
| 7 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 9 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | |
| 10 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; | |
| 11 | +import org.flowable.bpmn.model.BpmnModel; | |
| 12 | +import org.flowable.bpmn.model.FlowElement; | |
| 13 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 14 | +import org.junit.jupiter.api.Test; | |
| 15 | +import org.mockito.InjectMocks; | |
| 16 | +import org.mockito.Mock; | |
| 17 | +import org.mockito.MockedStatic; | |
| 18 | + | |
| 19 | +import java.util.Set; | |
| 20 | + | |
| 21 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 22 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | |
| 23 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 24 | +import static org.mockito.Mockito.*; | |
| 25 | + | |
| 26 | +public class BpmTaskCandidateAssignEmptyStrategyTest extends BaseMockitoUnitTest { | |
| 27 | + | |
| 28 | + @InjectMocks | |
| 29 | + private BpmTaskCandidateAssignEmptyStrategy strategy; | |
| 30 | + | |
| 31 | + @Mock | |
| 32 | + private BpmProcessDefinitionService processDefinitionService; | |
| 33 | + | |
| 34 | + @Test | |
| 35 | + public void testCalculateUsersByTask() { | |
| 36 | + try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class); | |
| 37 | + MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { | |
| 38 | + // 准备参数 | |
| 39 | + DelegateExecution execution = mock(DelegateExecution.class); | |
| 40 | + String param = randomString(); | |
| 41 | + // mock 方法(execution) | |
| 42 | + String processDefinitionId = randomString(); | |
| 43 | + when(execution.getProcessDefinitionId()).thenReturn(processDefinitionId); | |
| 44 | + FlowElement flowElement = mock(FlowElement.class); | |
| 45 | + when(execution.getCurrentFlowElement()).thenReturn(flowElement); | |
| 46 | + // mock 方法(parseAssignEmptyHandlerType) | |
| 47 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement))) | |
| 48 | + .thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType()); | |
| 49 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerUserIds(same(flowElement))) | |
| 50 | + .thenReturn(ListUtil.of(1L, 2L)); | |
| 51 | + | |
| 52 | + // 调用 | |
| 53 | + Set<Long> userIds = strategy.calculateUsersByTask(execution, param); | |
| 54 | + // 断言 | |
| 55 | + assertEquals(SetUtils.asSet(1L, 2L), userIds); | |
| 56 | + } | |
| 57 | + | |
| 58 | + } | |
| 59 | + | |
| 60 | + @Test | |
| 61 | + public void testCalculateUsersByActivity() { | |
| 62 | + try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { | |
| 63 | + // 准备参数 | |
| 64 | + String processDefinitionId = randomString(); | |
| 65 | + String activityId = randomString(); | |
| 66 | + String param = randomString(); | |
| 67 | + // mock 方法(getFlowElementById) | |
| 68 | + FlowElement flowElement = mock(FlowElement.class); | |
| 69 | + BpmnModel bpmnModel = mock(BpmnModel.class); | |
| 70 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))) | |
| 71 | + .thenReturn(flowElement); | |
| 72 | + // mock 方法(parseAssignEmptyHandlerType) | |
| 73 | + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement))) | |
| 74 | + .thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType()); | |
| 75 | + // mock 方法(getProcessDefinitionInfo) | |
| 76 | + BpmProcessDefinitionInfoDO processDefinition = randomPojo(BpmProcessDefinitionInfoDO.class, | |
| 77 | + o -> o.setManagerUserIds(ListUtil.of(1L, 2L))); | |
| 78 | + when(processDefinitionService.getProcessDefinitionInfo(eq(processDefinitionId))).thenReturn(processDefinition); | |
| 79 | + | |
| 80 | + // 调用 | |
| 81 | + Set<Long> userIds = strategy.calculateUsersByActivity(bpmnModel, activityId, param, | |
| 82 | + null, processDefinitionId, null); | |
| 83 | + // 断言 | |
| 84 | + assertEquals(SetUtils.asSet(1L, 2L), userIds); | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategyTest.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| ... | ... | @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; |
| 8 | 8 | import org.mockito.InjectMocks; |
| 9 | 9 | import org.mockito.MockedStatic; |
| 10 | 10 | |
| 11 | +import java.util.HashMap; | |
| 12 | +import java.util.Map; | |
| 11 | 13 | import java.util.Set; |
| 12 | 14 | |
| 13 | 15 | import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; |
| ... | ... | @@ -22,7 +24,7 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest |
| 22 | 24 | private BpmTaskCandidateExpressionStrategy strategy; |
| 23 | 25 | |
| 24 | 26 | @Test |
| 25 | - public void testCalculateUsers() { | |
| 27 | + public void testCalculateUsersByTask() { | |
| 26 | 28 | try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) { |
| 27 | 29 | // 准备参数 |
| 28 | 30 | String param = "1,2"; |
| ... | ... | @@ -32,7 +34,25 @@ public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest |
| 32 | 34 | .thenReturn(asSet(1L, 2L)); |
| 33 | 35 | |
| 34 | 36 | // 调用 |
| 35 | - Set<Long> results = strategy.calculateUsers(execution, param); | |
| 37 | + Set<Long> results = strategy.calculateUsersByTask(execution, param); | |
| 38 | + // 断言 | |
| 39 | + assertEquals(asSet(1L, 2L), results); | |
| 40 | + } | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Test | |
| 44 | + public void testCalculateUsersByActivity() { | |
| 45 | + try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) { | |
| 46 | + // 准备参数 | |
| 47 | + String param = "1,2"; | |
| 48 | + Map<String, Object> processVariables = new HashMap<>(); | |
| 49 | + // mock 方法 | |
| 50 | + flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(processVariables), eq(param))) | |
| 51 | + .thenReturn(asSet(1L, 2L)); | |
| 52 | + | |
| 53 | + // 调用 | |
| 54 | + Set<Long> results = strategy.calculateUsersByActivity(null, null, param, | |
| 55 | + null, null, processVariables); | |
| 36 | 56 | // 断言 |
| 37 | 57 | assertEquals(asSet(1L, 2L), results); |
| 38 | 58 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategyTest.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; |
| ... | ... | @@ -36,9 +36,9 @@ public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest { |
| 36 | 36 | when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2)); |
| 37 | 37 | |
| 38 | 38 | // 调用 |
| 39 | - Set<Long> results = strategy.calculateUsers(null, param); | |
| 39 | + Set<Long> userIds = strategy.calculateUsersByTask(null, param); | |
| 40 | 40 | // 断言 |
| 41 | - assertEquals(asSet(11L, 12L, 21L, 22L), results); | |
| 41 | + assertEquals(asSet(11L, 12L, 21L, 22L), userIds); | |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 4 | 4 | import cn.iocoder.yudao.module.system.api.dept.PostApi; |
| ... | ... | @@ -39,9 +39,9 @@ public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest { |
| 39 | 39 | when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(users); |
| 40 | 40 | |
| 41 | 41 | // 调用 |
| 42 | - Set<Long> results = strategy.calculateUsers(null, param); | |
| 42 | + Set<Long> userIds = strategy.calculateUsersByTask(null, param); | |
| 43 | 43 | // 断言 |
| 44 | - assertEquals(asSet(11L, 22L), results); | |
| 44 | + assertEquals(asSet(11L, 22L), userIds); | |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategyTest.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategyTest.java
| 1 | -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user; | |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 4 | 4 | import cn.iocoder.yudao.module.system.api.permission.PermissionApi; |
| ... | ... | @@ -35,9 +35,9 @@ public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest { |
| 35 | 35 | .thenReturn(asSet(11L, 22L)); |
| 36 | 36 | |
| 37 | 37 | // 调用 |
| 38 | - Set<Long> results = strategy.calculateUsers(null, param); | |
| 38 | + Set<Long> userIds = strategy.calculateUsersByTask(null, param); | |
| 39 | 39 | // 断言 |
| 40 | - assertEquals(asSet(11L, 22L), results); | |
| 40 | + assertEquals(asSet(11L, 22L), userIds); | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | } | ... | ... |
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 | 3 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 4 | 4 | import org.junit.jupiter.api.Disabled; |
| ... | ... | @@ -17,14 +17,15 @@ public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest { |
| 17 | 17 | private BpmTaskCandidateUserStrategy strategy; |
| 18 | 18 | |
| 19 | 19 | @Test |
| 20 | - public void testCalculateUsers() { | |
| 20 | + public void test() { | |
| 21 | 21 | // 准备参数 |
| 22 | 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 | 89 | |
| 90 | 90 | @GetMapping("/page") |
| 91 | 91 | @Operation(summary = "获得用户分页列表") |
| 92 | - @PreAuthorize("@ss.hasPermission('system:user:list')") | |
| 92 | + @PreAuthorize("@ss.hasPermission('system:user:query')") | |
| 93 | 93 | public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO pageReqVO) { |
| 94 | 94 | // 获得用户分页列表 |
| 95 | 95 | PageResult<AdminUserDO> pageResult = userService.getUserPage(pageReqVO); | ... | ... |