Commit 76e1d0e6f789f23877915477ca4f073e28105b33
Committed by
Gitee
!978 BPM 仿钉钉/飞书模式的草稿 PR
Merge pull request !978 from 芋道源码/feature/bpm
Showing
97 changed files
with
3810 additions
and
657 deletions
pom.xml
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | <module>yudao-module-system</module> |
| 17 | 17 | <module>yudao-module-infra</module> |
| 18 | 18 | <!-- <module>yudao-module-member</module>--> |
| 19 | -<!-- <module>yudao-module-bpm</module>--> | |
| 19 | + <module>yudao-module-bpm</module> | |
| 20 | 20 | <!-- <module>yudao-module-report</module>--> |
| 21 | 21 | <!-- <module>yudao-module-mp</module>--> |
| 22 | 22 | <!-- <module>yudao-module-pay</module>--> | ... | ... |
sql/mysql/bpm_update.sql
0 → 100644
| 1 | +-- ---------------------------- | |
| 2 | +-- 流程抄送表新加流程活动编号 | |
| 3 | +-- ---------------------------- | |
| 4 | +ALTER TABLE `pro-test`.`bpm_process_instance_copy` | |
| 5 | + ADD COLUMN `activity_id` varchar(64) NULL COMMENT '流程活动编号' AFTER `category`, | |
| 6 | + MODIFY COLUMN `task_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '任务编号' AFTER `category`; | |
| 7 | + | |
| 8 | +ALTER TABLE `pro-test`.`bpm_process_definition_info` | |
| 9 | + ADD COLUMN `model_type` tinyint NOT NULL DEFAULT 10 COMMENT '流程模型的类型' AFTER `model_id`, | |
| 10 | + ADD COLUMN `simple_model` json NULL COMMENT 'SIMPLE 设计器模型数据' AFTER `form_custom_view_path`, | |
| 11 | + ADD COLUMN `visible` bit(1) NOT NULL DEFAULT 1 COMMENT '是否可见' AFTER `simple_model`; | |
| 0 | 12 | \ No newline at end of file | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/BeanUtils.java
| ... | ... | @@ -59,4 +59,11 @@ public class BeanUtils { |
| 59 | 59 | return new PageResult<>(list, source.getTotal()); |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | + public static void copyProperties(Object source, Object target) { | |
| 63 | + if (source == null || target == null) { | |
| 64 | + return; | |
| 65 | + } | |
| 66 | + BeanUtil.copyProperties(source, target, false); | |
| 67 | + } | |
| 68 | + | |
| 62 | 69 | } |
| 63 | 70 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
| ... | ... | @@ -23,6 +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 | 27 | |
| 27 | 28 | // ========== 流程定义 1-009-003-000 ========== |
| 28 | 29 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); |
| ... | ... | @@ -36,6 +37,7 @@ public interface ErrorCodeConstants { |
| 36 | 37 | ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); |
| 37 | 38 | ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置"); |
| 38 | 39 | ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在"); |
| 40 | + ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); | |
| 39 | 41 | |
| 40 | 42 | // ========== 流程任务 1-009-005-000 ========== |
| 41 | 43 | ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Getter; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * BPM 边界事件 (boundary event) 自定义类型枚举 | |
| 9 | + * | |
| 10 | + * @author jason | |
| 11 | + */ | |
| 12 | +@Getter | |
| 13 | +@AllArgsConstructor | |
| 14 | +public enum BpmBoundaryEventType { | |
| 15 | + | |
| 16 | + USER_TASK_TIMEOUT(1,"用户任务超时"); | |
| 17 | + | |
| 18 | + private final Integer type; | |
| 19 | + private final String name; | |
| 20 | + | |
| 21 | + public static BpmBoundaryEventType typeOf(Integer type) { | |
| 22 | + return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); | |
| 23 | + } | |
| 24 | + | |
| 25 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Getter; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * BPM 表单权限的枚举 | |
| 9 | + * | |
| 10 | + * @author jason | |
| 11 | + */ | |
| 12 | +@Getter | |
| 13 | +@AllArgsConstructor | |
| 14 | +public enum BpmFieldPermissionEnum { | |
| 15 | + | |
| 16 | + READ(1, "只读"), | |
| 17 | + WRITE(2, "可编辑"), | |
| 18 | + NONE(3, "隐藏"); | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 权限 | |
| 22 | + */ | |
| 23 | + private final Integer permission; | |
| 24 | + /** | |
| 25 | + * 名字 | |
| 26 | + */ | |
| 27 | + private final String name; | |
| 28 | + | |
| 29 | + public static BpmFieldPermissionEnum valueOf(Integer permission) { | |
| 30 | + return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values()); | |
| 31 | + } | |
| 32 | + | |
| 33 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Getter; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * BPM 模型的类型的枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Getter | |
| 15 | +@AllArgsConstructor | |
| 16 | +public enum BpmModelTypeEnum implements IntArrayValuable { | |
| 17 | + | |
| 18 | + BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/ | |
| 19 | + SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器 | |
| 20 | + | |
| 21 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray(); | |
| 22 | + | |
| 23 | + private final Integer type; | |
| 24 | + private final String name; | |
| 25 | + | |
| 26 | + @Override | |
| 27 | + public int[] array() { | |
| 28 | + return ARRAYS; | |
| 29 | + } | |
| 30 | + | |
| 31 | +} | |
| 0 | 32 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModeConditionType.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 5 | +import lombok.AllArgsConstructor; | |
| 6 | +import lombok.Getter; | |
| 7 | + | |
| 8 | +import java.util.Arrays; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 仿钉钉的流程器设计器条件节点的条件类型 | |
| 12 | + * | |
| 13 | + * @author jason | |
| 14 | + */ | |
| 15 | +@Getter | |
| 16 | +@AllArgsConstructor | |
| 17 | +public enum BpmSimpleModeConditionType implements IntArrayValuable { | |
| 18 | + | |
| 19 | + EXPRESSION(1, "条件表达式"), | |
| 20 | + RULE(2, "条件规则"); | |
| 21 | + | |
| 22 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray(); | |
| 23 | + | |
| 24 | + private final Integer type; | |
| 25 | + | |
| 26 | + private final String name; | |
| 27 | + | |
| 28 | + public static BpmSimpleModeConditionType valueOf(Integer type) { | |
| 29 | + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); | |
| 30 | + } | |
| 31 | + | |
| 32 | + @Override | |
| 33 | + public int[] array() { | |
| 34 | + return ARRAYS; | |
| 35 | + } | |
| 36 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 5 | +import lombok.AllArgsConstructor; | |
| 6 | +import lombok.Getter; | |
| 7 | + | |
| 8 | +import java.util.Arrays; | |
| 9 | +import java.util.Objects; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 仿钉钉的流程器设计器的模型节点类型 | |
| 13 | + * | |
| 14 | + * @author jason | |
| 15 | + */ | |
| 16 | +@Getter | |
| 17 | +@AllArgsConstructor | |
| 18 | +public enum BpmSimpleModelNodeType implements IntArrayValuable { | |
| 19 | + | |
| 20 | + // 0 ~ 1 开始和结束 | |
| 21 | + START_NODE(0, "startEvent", "开始节点"), | |
| 22 | + END_NODE(1, "endEvent", "结束节点"), | |
| 23 | + | |
| 24 | + // 10 ~ 49 各种节点 | |
| 25 | + START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点。前端的开始节点,Id 固定 | |
| 26 | + APPROVE_NODE(11, "userTask", "审批人节点"), | |
| 27 | + COPY_NODE(12, "serviceTask", "抄送人节点"), | |
| 28 | + | |
| 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 @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。 | |
| 36 | + ; | |
| 37 | + | |
| 38 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); | |
| 39 | + | |
| 40 | + public static final String BPMN_USER_TASK_TYPE = "userTask"; | |
| 41 | + | |
| 42 | + private final Integer type; | |
| 43 | + private final String bpmnType; | |
| 44 | + private final String name; | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 判断是否为分支节点 | |
| 48 | + * | |
| 49 | + * @param type 节点类型 | |
| 50 | + */ | |
| 51 | + public static boolean isBranchNode(Integer type) { | |
| 52 | + return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) | |
| 53 | + || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) | |
| 54 | + || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type); | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * 判断是否需要记录的节点 | |
| 59 | + * | |
| 60 | + * @param bpmnType bpmn节点类型 | |
| 61 | + */ | |
| 62 | + public static boolean isRecordNode(String bpmnType) { | |
| 63 | + return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType) | |
| 64 | + || Objects.equals(END_NODE.getBpmnType(), bpmnType); | |
| 65 | + } | |
| 66 | + | |
| 67 | + public static BpmSimpleModelNodeType valueOf(Integer type) { | |
| 68 | + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Override | |
| 72 | + public int[] array() { | |
| 73 | + return ARRAYS; | |
| 74 | + } | |
| 75 | + | |
| 76 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 5 | +import lombok.AllArgsConstructor; | |
| 6 | +import lombok.Getter; | |
| 7 | + | |
| 8 | +import java.util.Arrays; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * BPM 多人审批方式的枚举 | |
| 12 | + * | |
| 13 | + * @author jason | |
| 14 | + */ | |
| 15 | +@Getter | |
| 16 | +@AllArgsConstructor | |
| 17 | +public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable { | |
| 18 | + | |
| 19 | + RANDOM(1, "随机挑选一人审批"), | |
| 20 | + RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例) | |
| 21 | + ANY(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人) | |
| 22 | + SEQUENTIAL(4, "依次审批"); // 依次审批 | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 审批方式 | |
| 26 | + */ | |
| 27 | + private final Integer method; | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 名字 | |
| 31 | + */ | |
| 32 | + private final String name; | |
| 33 | + | |
| 34 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray(); | |
| 35 | + | |
| 36 | + public static BpmUserTaskApproveMethodEnum valueOf(Integer method) { | |
| 37 | + return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public int[] array() { | |
| 42 | + return ARRAYS; | |
| 43 | + } | |
| 44 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Getter; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 用户任务的审批类型枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Getter | |
| 15 | +@AllArgsConstructor | |
| 16 | +public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable { | |
| 17 | + | |
| 18 | + USER(1), // 人工审批 | |
| 19 | + AUTO_APPROVE(2), // 自动通过 | |
| 20 | + AUTO_REJECT(3); // 自动拒绝 | |
| 21 | + | |
| 22 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray(); | |
| 23 | + | |
| 24 | + private final Integer type; | |
| 25 | + | |
| 26 | + @Override | |
| 27 | + public int[] array() { | |
| 28 | + return ARRAYS; | |
| 29 | + } | |
| 30 | + | |
| 31 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | +import lombok.Getter; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * BPM 用户任务的审批人为空时,处理类型枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@RequiredArgsConstructor | |
| 15 | +@Getter | |
| 16 | +public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable { | |
| 17 | + | |
| 18 | + APPROVE(1), // 自动通过 | |
| 19 | + REJECT(2), // 自动拒绝 | |
| 20 | + ASSIGN_USER(3), // 指定人员审批 | |
| 21 | + ASSIGN_ADMIN(4), // 转交给流程管理员 | |
| 22 | + ; | |
| 23 | + | |
| 24 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray(); | |
| 25 | + | |
| 26 | + private final Integer type; | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public int[] array() { | |
| 30 | + return ARRAYS; | |
| 31 | + } | |
| 32 | + | |
| 33 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | +import lombok.Getter; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * BPM 用户任务的审批人与发起人相同时,处理类型枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@RequiredArgsConstructor | |
| 15 | +@Getter | |
| 16 | +public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable { | |
| 17 | + | |
| 18 | + START_USER_AUDIT(1), // 由发起人对自己审批 | |
| 19 | + SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 | |
| 20 | + TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 | |
| 21 | + | |
| 22 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray(); | |
| 23 | + | |
| 24 | + private final Integer type; | |
| 25 | + | |
| 26 | + @Override | |
| 27 | + public int[] array() { | |
| 28 | + return ARRAYS; | |
| 29 | + } | |
| 30 | + | |
| 31 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 5 | +import lombok.AllArgsConstructor; | |
| 6 | +import lombok.Getter; | |
| 7 | + | |
| 8 | +import java.util.Arrays; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * BPM 用户任务拒绝处理类型枚举 | |
| 12 | + * | |
| 13 | + * @author jason | |
| 14 | + */ | |
| 15 | +@Getter | |
| 16 | +@AllArgsConstructor | |
| 17 | +public enum BpmUserTaskRejectHandlerType implements IntArrayValuable { | |
| 18 | + | |
| 19 | + FINISH_PROCESS_INSTANCE(1, "终止流程"), | |
| 20 | + RETURN_USER_TASK(2, "驳回到指定任务节点"); | |
| 21 | + | |
| 22 | + private final Integer type; | |
| 23 | + private final String name; | |
| 24 | + | |
| 25 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray(); | |
| 26 | + | |
| 27 | + public static BpmUserTaskRejectHandlerType typeOf(Integer type) { | |
| 28 | + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); | |
| 29 | + } | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + public int[] array() { | |
| 33 | + return ARRAYS; | |
| 34 | + } | |
| 35 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Getter; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 用户任务超时处理类型枚举 | |
| 11 | + * | |
| 12 | + * @author jason | |
| 13 | + */ | |
| 14 | +@Getter | |
| 15 | +@AllArgsConstructor | |
| 16 | +public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable { | |
| 17 | + | |
| 18 | + REMINDER(1,"自动提醒"), | |
| 19 | + APPROVE(2, "自动同意"), | |
| 20 | + REJECT(3, "自动拒绝"); | |
| 21 | + | |
| 22 | + private final Integer type; | |
| 23 | + private final String name; | |
| 24 | + | |
| 25 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray(); | |
| 26 | + | |
| 27 | + @Override | |
| 28 | + public int[] array() { | |
| 29 | + return ARRAYS; | |
| 30 | + } | |
| 31 | + | |
| 32 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
| ... | ... | @@ -14,7 +14,8 @@ public enum BpmMessageEnum { |
| 14 | 14 | |
| 15 | 15 | PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 |
| 16 | 16 | PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 |
| 17 | - TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 | |
| 17 | + TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 | |
| 18 | + TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 | |
| 18 | 19 | |
| 19 | 20 | /** |
| 20 | 21 | * 短信模板的标识 | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.enums.task; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; |
| 4 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | |
| 4 | 5 | import lombok.AllArgsConstructor; |
| 5 | 6 | import lombok.Getter; |
| 6 | 7 | |
| ... | ... | @@ -15,6 +16,7 @@ import java.util.Arrays; |
| 15 | 16 | @AllArgsConstructor |
| 16 | 17 | public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { |
| 17 | 18 | |
| 19 | + NOT_START(-1, "未开始"), | |
| 18 | 20 | RUNNING(1, "审批中"), |
| 19 | 21 | APPROVE(2, "审批通过"), |
| 20 | 22 | REJECT(3, "审批不通过"), |
| ... | ... | @@ -36,4 +38,9 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { |
| 36 | 38 | return ARRAYS; |
| 37 | 39 | } |
| 38 | 40 | |
| 41 | + public static boolean isProcessEndStatus(Integer status) { | |
| 42 | + return ObjectUtils.equalsAny(status, | |
| 43 | + APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); | |
| 44 | + } | |
| 45 | + | |
| 39 | 46 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java renamed to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
| ... | ... | @@ -5,13 +5,13 @@ import lombok.AllArgsConstructor; |
| 5 | 5 | import lombok.Getter; |
| 6 | 6 | |
| 7 | 7 | /** |
| 8 | - * 流程实例/任务的删除原因枚举 | |
| 8 | + * 流程实例/任务的的处理原因枚举 | |
| 9 | 9 | * |
| 10 | 10 | * @author 芋道源码 |
| 11 | 11 | */ |
| 12 | 12 | @Getter |
| 13 | 13 | @AllArgsConstructor |
| 14 | -public enum BpmDeleteReasonEnum { | |
| 14 | +public enum BpmReasonEnum { | |
| 15 | 15 | |
| 16 | 16 | // ========== 流程实例的独有原因 ========== |
| 17 | 17 | |
| ... | ... | @@ -22,6 +22,16 @@ public enum BpmDeleteReasonEnum { |
| 22 | 22 | // ========== 流程任务的独有原因 ========== |
| 23 | 23 | |
| 24 | 24 | CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 |
| 25 | + TIMEOUT_APPROVE("审批超时,系统自动通过"), | |
| 26 | + TIMEOUT_REJECT("审批超时,系统自动不通过"), | |
| 27 | + ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), | |
| 28 | + ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), | |
| 29 | + ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), | |
| 30 | + ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), | |
| 31 | + ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), | |
| 32 | + ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"), | |
| 33 | + APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), | |
| 34 | + APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), | |
| 25 | 35 | ; |
| 26 | 36 | |
| 27 | 37 | private final String reason; |
| ... | ... | @@ -36,10 +46,4 @@ public enum BpmDeleteReasonEnum { |
| 36 | 46 | return StrUtil.format(reason, args); |
| 37 | 47 | } |
| 38 | 48 | |
| 39 | - // ========== 逻辑 ========== | |
| 40 | - | |
| 41 | - public static boolean isRejectReason(String reason) { | |
| 42 | - return StrUtil.startWith(reason, "审批不通过任务,原因:"); | |
| 43 | - } | |
| 44 | - | |
| 45 | 49 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/package-info.java
0 → 100644
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.base.user; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +@Schema(description = "用户精简信息 VO") | |
| 7 | +@Data | |
| 8 | +public class UserSimpleBaseVO { | |
| 9 | + | |
| 10 | + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 11 | + private Long id; | |
| 12 | + | |
| 13 | + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") | |
| 14 | + private String nickname; | |
| 15 | + | |
| 16 | + @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") | |
| 17 | + private String avatar; | |
| 18 | + | |
| 19 | +} | |
| 0 | 20 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
| ... | ... | @@ -4,10 +4,9 @@ import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 7 | -import cn.iocoder.yudao.framework.common.util.io.IoUtils; | |
| 8 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 9 | -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 10 | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; |
| 8 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | |
| 11 | 10 | import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert; |
| 12 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 13 | 12 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| ... | ... | @@ -15,7 +14,8 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; |
| 15 | 14 | import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; |
| 16 | 15 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |
| 17 | 16 | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| 18 | -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; | |
| 17 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 18 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 19 | 19 | import io.swagger.v3.oas.annotations.Operation; |
| 20 | 20 | import io.swagger.v3.oas.annotations.Parameter; |
| 21 | 21 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -28,15 +28,15 @@ import org.springframework.security.access.prepost.PreAuthorize; |
| 28 | 28 | import org.springframework.validation.annotation.Validated; |
| 29 | 29 | import org.springframework.web.bind.annotation.*; |
| 30 | 30 | |
| 31 | -import java.io.IOException; | |
| 32 | 31 | import java.util.HashSet; |
| 33 | 32 | import java.util.List; |
| 34 | 33 | import java.util.Map; |
| 35 | 34 | import java.util.Set; |
| 35 | +import java.util.stream.Stream; | |
| 36 | 36 | |
| 37 | 37 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 38 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 39 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 38 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | |
| 39 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | |
| 40 | 40 | |
| 41 | 41 | @Tag(name = "管理后台 - 流程模型") |
| 42 | 42 | @RestController |
| ... | ... | @@ -53,6 +53,9 @@ public class BpmModelController { |
| 53 | 53 | @Resource |
| 54 | 54 | private BpmProcessDefinitionService processDefinitionService; |
| 55 | 55 | |
| 56 | + @Resource | |
| 57 | + private AdminUserApi adminUserApi; | |
| 58 | + | |
| 56 | 59 | @GetMapping("/page") |
| 57 | 60 | @Operation(summary = "获得模型分页") |
| 58 | 61 | public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) { |
| ... | ... | @@ -64,7 +67,7 @@ public class BpmModelController { |
| 64 | 67 | // 拼接数据 |
| 65 | 68 | // 获得 Form 表单 |
| 66 | 69 | Set<Long> formIds = convertSet(pageResult.getList(), model -> { |
| 67 | - BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); | |
| 70 | + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | |
| 68 | 71 | return metaInfo != null ? metaInfo.getFormId() : null; |
| 69 | 72 | }); |
| 70 | 73 | Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); |
| ... | ... | @@ -78,7 +81,14 @@ public class BpmModelController { |
| 78 | 81 | // 获得 ProcessDefinition Map |
| 79 | 82 | List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); |
| 80 | 83 | Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); |
| 81 | - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap)); | |
| 84 | + // 获得 User Map | |
| 85 | + Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), model -> { | |
| 86 | + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | |
| 87 | + return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); | |
| 88 | + }); | |
| 89 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 90 | + return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, | |
| 91 | + formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); | |
| 82 | 92 | } |
| 83 | 93 | |
| 84 | 94 | @GetMapping("/get") |
| ... | ... | @@ -97,34 +107,24 @@ public class BpmModelController { |
| 97 | 107 | @PostMapping("/create") |
| 98 | 108 | @Operation(summary = "新建模型") |
| 99 | 109 | @PreAuthorize("@ss.hasPermission('bpm:model:create')") |
| 100 | - public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) { | |
| 101 | - return success(modelService.createModel(createRetVO, null)); | |
| 110 | + public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) { | |
| 111 | + return success(modelService.createModel(createRetVO)); | |
| 102 | 112 | } |
| 103 | 113 | |
| 104 | 114 | @PutMapping("/update") |
| 105 | 115 | @Operation(summary = "修改模型") |
| 106 | 116 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| 107 | - public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) { | |
| 108 | - modelService.updateModel(modelVO); | |
| 117 | + public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) { | |
| 118 | + modelService.updateModel(getLoginUserId(), modelVO); | |
| 109 | 119 | return success(true); |
| 110 | 120 | } |
| 111 | 121 | |
| 112 | - @PostMapping("/import") | |
| 113 | - @Operation(summary = "导入模型") | |
| 114 | - @PreAuthorize("@ss.hasPermission('bpm:model:import')") | |
| 115 | - public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException { | |
| 116 | - BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class); | |
| 117 | - // 读取文件 | |
| 118 | - String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false); | |
| 119 | - return success(modelService.createModel(createReqVO, bpmnXml)); | |
| 120 | - } | |
| 121 | - | |
| 122 | 122 | @PostMapping("/deploy") |
| 123 | 123 | @Operation(summary = "部署模型") |
| 124 | 124 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| 125 | 125 | @PreAuthorize("@ss.hasPermission('bpm:model:deploy')") |
| 126 | 126 | public CommonResult<Boolean> deployModel(@RequestParam("id") String id) { |
| 127 | - modelService.deployModel(id); | |
| 127 | + modelService.deployModel(getLoginUserId(), id); | |
| 128 | 128 | return success(true); |
| 129 | 129 | } |
| 130 | 130 | |
| ... | ... | @@ -132,7 +132,15 @@ public class BpmModelController { |
| 132 | 132 | @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态") |
| 133 | 133 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| 134 | 134 | public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) { |
| 135 | - modelService.updateModelState(reqVO.getId(), reqVO.getState()); | |
| 135 | + modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState()); | |
| 136 | + return success(true); | |
| 137 | + } | |
| 138 | + | |
| 139 | + @PutMapping("/update-bpmn") | |
| 140 | + @Operation(summary = "修改模型的 BPMN") | |
| 141 | + @PreAuthorize("@ss.hasPermission('bpm:model:update')") | |
| 142 | + public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) { | |
| 143 | + modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml()); | |
| 136 | 144 | return success(true); |
| 137 | 145 | } |
| 138 | 146 | |
| ... | ... | @@ -141,8 +149,25 @@ public class BpmModelController { |
| 141 | 149 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| 142 | 150 | @PreAuthorize("@ss.hasPermission('bpm:model:delete')") |
| 143 | 151 | public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) { |
| 144 | - modelService.deleteModel(id); | |
| 152 | + modelService.deleteModel(getLoginUserId(), id); | |
| 145 | 153 | return success(true); |
| 146 | 154 | } |
| 147 | 155 | |
| 156 | + // ========== 仿钉钉/飞书的精简模型 ========= | |
| 157 | + | |
| 158 | + @GetMapping("/simple/get") | |
| 159 | + @Operation(summary = "获得仿钉钉流程设计模型") | |
| 160 | + @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") | |
| 161 | + public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){ | |
| 162 | + return success(modelService.getSimpleModel(modelId)); | |
| 163 | + } | |
| 164 | + | |
| 165 | + @PostMapping("/simple/update") | |
| 166 | + @Operation(summary = "保存仿钉钉流程设计模型") | |
| 167 | + @PreAuthorize("@ss.hasPermission('bpm:model:update')") | |
| 168 | + public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) { | |
| 169 | + modelService.updateSimpleModel(getLoginUserId(), reqVO); | |
| 170 | + return success(Boolean.TRUE); | |
| 171 | + } | |
| 172 | + | |
| 148 | 173 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
| ... | ... | @@ -34,6 +34,7 @@ import java.util.Map; |
| 34 | 34 | |
| 35 | 35 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 36 | 36 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 37 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | |
| 37 | 38 | |
| 38 | 39 | @Tag(name = "管理后台 - 流程定义") |
| 39 | 40 | @RestController |
| ... | ... | @@ -79,14 +80,23 @@ public class BpmProcessDefinitionController { |
| 79 | 80 | @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举 |
| 80 | 81 | public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList( |
| 81 | 82 | @RequestParam("suspensionState") Integer suspensionState) { |
| 83 | + // 1.1 获得开启的流程定义 | |
| 82 | 84 | List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState); |
| 83 | 85 | if (CollUtil.isEmpty(list)) { |
| 84 | 86 | return success(Collections.emptyList()); |
| 85 | 87 | } |
| 86 | - | |
| 87 | - // 获得 BpmProcessDefinitionInfoDO Map | |
| 88 | + // 1.2 移除不可见的流程定义 | |
| 88 | 89 | Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap( |
| 89 | 90 | convertSet(list, ProcessDefinition::getId)); |
| 91 | + Long userId = getLoginUserId(); | |
| 92 | + list.removeIf(processDefinition -> { | |
| 93 | + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId()); | |
| 94 | + return processDefinitionInfo == null // 不存在 | |
| 95 | + || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见 | |
| 96 | + || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起 | |
| 97 | + }); | |
| 98 | + | |
| 99 | + // 2. 拼接 VO 返回 | |
| 90 | 100 | return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList( |
| 91 | 101 | list, null, processDefinitionMap, null, null)); |
| 92 | 102 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; | |
| 2 | - | |
| 3 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.EqualsAndHashCode; | |
| 6 | -import lombok.ToString; | |
| 7 | -import org.springframework.web.multipart.MultipartFile; | |
| 8 | - | |
| 9 | -import jakarta.validation.constraints.NotNull; | |
| 10 | - | |
| 11 | -@Schema(description = "管理后台 - 流程模型的导入 Request VO 相比流程模型的新建来说,只是多了一个 bpmnFile 文件") | |
| 12 | -@Data | |
| 13 | -public class BpmModeImportReqVO extends BpmModelCreateReqVO { | |
| 14 | - | |
| 15 | - @Schema(description = "BPMN 文件", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 16 | - @NotNull(message = "BPMN 文件不能为空") | |
| 17 | - private MultipartFile bpmnFile; | |
| 18 | - | |
| 19 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotEmpty; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO") | |
| 8 | +@Data | |
| 9 | +public class BpmModeUpdateBpmnReqVO { | |
| 10 | + | |
| 11 | + @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 12 | + @NotEmpty(message = "流程编号不能为空") | |
| 13 | + private String id; | |
| 14 | + | |
| 15 | + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 16 | + @NotEmpty(message = "BPMN XML 不能为空") | |
| 17 | + private String bpmnXml; | |
| 18 | + | |
| 19 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; | |
| 5 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; | |
| 6 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 7 | +import jakarta.validation.constraints.NotEmpty; | |
| 8 | +import jakarta.validation.constraints.NotNull; | |
| 9 | +import lombok.Data; | |
| 10 | +import org.hibernate.validator.constraints.URL; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * BPM 流程 MetaInfo Response DTO | |
| 16 | + * 主要用于 { Model#setMetaInfo(String)} 的存储 | |
| 17 | + * | |
| 18 | + * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 | |
| 19 | + * | |
| 20 | + * @author 芋道源码 | |
| 21 | + */ | |
| 22 | +@Data | |
| 23 | +public class BpmModelMetaInfoVO { | |
| 24 | + | |
| 25 | + @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") | |
| 26 | + @NotEmpty(message = "流程图标不能为空") | |
| 27 | + @URL(message = "流程图标格式不正确") | |
| 28 | + private String icon; | |
| 29 | + | |
| 30 | + @Schema(description = "流程描述", example = "我是描述") | |
| 31 | + private String description; | |
| 32 | + | |
| 33 | + @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") | |
| 34 | + @InEnum(BpmModelTypeEnum.class) | |
| 35 | + @NotNull(message = "流程类型不能为空") | |
| 36 | + private Integer type; | |
| 37 | + | |
| 38 | + @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") | |
| 39 | + @InEnum(BpmModelFormTypeEnum.class) | |
| 40 | + @NotNull(message = "表单类型不能为空") | |
| 41 | + private Integer formType; | |
| 42 | + @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 时,必须非空 | |
| 50 | + | |
| 51 | + @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 52 | + @NotNull(message = "是否可见不能为空") | |
| 53 | + private Boolean visible; | |
| 54 | + | |
| 55 | + @Schema(description = "可发起用户编号数组", example = "[1,2,3]") | |
| 56 | + private List<Long> startUserIds; | |
| 57 | + | |
| 58 | + @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") | |
| 59 | + @NotEmpty(message = "可管理用户编号数组不能为空") | |
| 60 | + private List<Long> managerUserIds; | |
| 61 | + | |
| 62 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; |
| 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; |
| 6 | 7 | |
| 7 | 8 | import java.time.LocalDateTime; |
| 9 | +import java.util.List; | |
| 8 | 10 | |
| 9 | 11 | @Schema(description = "管理后台 - 流程模型 Response VO") |
| 10 | 12 | @Data |
| 11 | -public class BpmModelRespVO { | |
| 13 | +public class BpmModelRespVO extends BpmModelMetaInfoVO { | |
| 12 | 14 | |
| 13 | 15 | @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 14 | 16 | private String id; |
| ... | ... | @@ -22,33 +24,23 @@ public class BpmModelRespVO { |
| 22 | 24 | @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") |
| 23 | 25 | private String icon; |
| 24 | 26 | |
| 25 | - @Schema(description = "流程描述", example = "我是描述") | |
| 26 | - private String description; | |
| 27 | - | |
| 28 | 27 | @Schema(description = "流程分类编码", example = "1") |
| 29 | 28 | private String category; |
| 30 | 29 | @Schema(description = "流程分类名字", example = "请假") |
| 31 | 30 | private String categoryName; |
| 32 | 31 | |
| 33 | - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") | |
| 34 | - private Integer formType; | |
| 35 | - | |
| 36 | - @Schema(description = "表单编号", example = "1024") | |
| 37 | - private Long formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 | |
| 38 | 32 | @Schema(description = "表单名字", example = "请假表单") |
| 39 | 33 | private String formName; |
| 40 | 34 | |
| 41 | - @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create") | |
| 42 | - private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 | |
| 43 | - @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view") | |
| 44 | - private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 | |
| 45 | - | |
| 46 | 35 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 47 | 36 | private LocalDateTime createTime; |
| 48 | 37 | |
| 49 | 38 | @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) |
| 50 | 39 | private String bpmnXml; |
| 51 | 40 | |
| 41 | + @Schema(description = "可发起的用户数组") | |
| 42 | + private List<UserSimpleBaseVO> startUsers; | |
| 43 | + | |
| 52 | 44 | /** |
| 53 | 45 | * 最新部署的流程定义 |
| 54 | 46 | */ | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; |
| 2 | 2 | |
| 3 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.EqualsAndHashCode; | |
| 6 | -import lombok.ToString; | |
| 7 | - | |
| 8 | 4 | import jakarta.validation.constraints.NotEmpty; |
| 5 | +import lombok.Data; | |
| 9 | 6 | |
| 10 | -@Schema(description = "管理后台 - 流程模型的创建 Request VO") | |
| 7 | +@Schema(description = "管理后台 - 流程模型的保存 Request VO") | |
| 11 | 8 | @Data |
| 12 | -public class BpmModelCreateReqVO { | |
| 9 | +public class BpmModelSaveReqVO extends BpmModelMetaInfoVO { | |
| 10 | + | |
| 11 | + @Schema(description = "编号", example = "1024") | |
| 12 | + private String id; | |
| 13 | 13 | |
| 14 | 14 | @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") |
| 15 | 15 | @NotEmpty(message = "流程标识不能为空") |
| ... | ... | @@ -19,7 +19,7 @@ public class BpmModelCreateReqVO { |
| 19 | 19 | @NotEmpty(message = "流程名称不能为空") |
| 20 | 20 | private String name; |
| 21 | 21 | |
| 22 | - @Schema(description = "流程描述", example = "我是描述") | |
| 23 | - private String description; | |
| 22 | + @Schema(description = "流程分类", example = "1") | |
| 23 | + private String category; | |
| 24 | 24 | |
| 25 | 25 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; | |
| 5 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 6 | -import jakarta.validation.constraints.NotEmpty; | |
| 7 | -import lombok.Data; | |
| 8 | -import org.hibernate.validator.constraints.URL; | |
| 9 | - | |
| 10 | -@Schema(description = "管理后台 - 流程模型的更新 Request VO") | |
| 11 | -@Data | |
| 12 | -public class BpmModelUpdateReqVO { | |
| 13 | - | |
| 14 | - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 15 | - @NotEmpty(message = "编号不能为空") | |
| 16 | - private String id; | |
| 17 | - | |
| 18 | - @Schema(description = "流程名称", example = "芋道") | |
| 19 | - private String name; | |
| 20 | - | |
| 21 | - @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") | |
| 22 | - @URL(message = "流程图标格式不正确") | |
| 23 | - private String icon; | |
| 24 | - | |
| 25 | - @Schema(description = "流程描述", example = "我是描述") | |
| 26 | - private String description; | |
| 27 | - | |
| 28 | - @Schema(description = "流程分类", example = "1") | |
| 29 | - private String category; | |
| 30 | - | |
| 31 | - @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 32 | - private String bpmnXml; | |
| 33 | - | |
| 34 | - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") | |
| 35 | - @InEnum(BpmModelFormTypeEnum.class) | |
| 36 | - private Integer formType; | |
| 37 | - @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024") | |
| 38 | - private Long formId; | |
| 39 | - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", | |
| 40 | - example = "/bpm/oa/leave/create") | |
| 41 | - private String formCustomCreatePath; | |
| 42 | - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", | |
| 43 | - example = "/bpm/oa/leave/view") | |
| 44 | - private String formCustomViewPath; | |
| 45 | - | |
| 46 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | +import cn.iocoder.yudao.module.bpm.enums.definition.*; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 6 | +import com.fasterxml.jackson.annotation.JsonIgnore; | |
| 7 | +import com.fasterxml.jackson.annotation.JsonInclude; | |
| 8 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 9 | +import jakarta.validation.Valid; | |
| 10 | +import jakarta.validation.constraints.NotEmpty; | |
| 11 | +import jakarta.validation.constraints.NotNull; | |
| 12 | +import lombok.Data; | |
| 13 | + | |
| 14 | +import java.util.List; | |
| 15 | +import java.util.Map; | |
| 16 | + | |
| 17 | +@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO") | |
| 18 | +@Data | |
| 19 | +@JsonInclude(JsonInclude.Include.NON_NULL) | |
| 20 | +public class BpmSimpleModelNodeVO { | |
| 21 | + | |
| 22 | + @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1") | |
| 23 | + @NotEmpty(message = "模型节点编号不能为空") | |
| 24 | + private String id; | |
| 25 | + | |
| 26 | + @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 27 | + @NotNull(message = "模型节点类型不能为空") | |
| 28 | + @InEnum(BpmSimpleModelNodeType.class) | |
| 29 | + private Integer type; | |
| 30 | + | |
| 31 | + @Schema(description = "模型节点名称", example = "领导审批") | |
| 32 | + private String name; | |
| 33 | + | |
| 34 | + // TODO @jason:和 gpt 大模型对了下这个字段的命名,貌似叫 displayText 合适点。可以等最后我们全局替换下。(优先级:低) | |
| 35 | + @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") | |
| 36 | + private String showText; | |
| 37 | + | |
| 38 | + @Schema(description = "子节点") | |
| 39 | + private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个 | |
| 40 | + | |
| 41 | + @Schema(description = "条件节点") | |
| 42 | + private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用 | |
| 43 | + | |
| 44 | + @Schema(description = "条件类型", example = "1") | |
| 45 | + @InEnum(BpmSimpleModeConditionType.class) | |
| 46 | + private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE | |
| 47 | + | |
| 48 | + @Schema(description = "条件表达式", example = "${day>3}") | |
| 49 | + private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE | |
| 50 | + | |
| 51 | + @Schema(description = "是否默认条件", example = "true") | |
| 52 | + private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE | |
| 53 | + /** | |
| 54 | + * 条件组 | |
| 55 | + */ | |
| 56 | + private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE | |
| 57 | + | |
| 58 | + @Schema(description = "候选人策略", example = "30") | |
| 59 | + @InEnum(BpmTaskCandidateStrategyEnum.class) | |
| 60 | + private Integer candidateStrategy; // 用于审批,抄送节点 | |
| 61 | + | |
| 62 | + @Schema(description = "候选人参数") | |
| 63 | + private String candidateParam; // 用于审批,抄送节点 | |
| 64 | + | |
| 65 | + @Schema(description = "审批节点类型", example = "1") | |
| 66 | + @InEnum(BpmUserTaskApproveTypeEnum.class) | |
| 67 | + private Integer approveType; // 用于审批节点 | |
| 68 | + | |
| 69 | + @Schema(description = "多人审批方式", example = "1") | |
| 70 | + @InEnum(BpmUserTaskApproveMethodEnum.class) | |
| 71 | + private Integer approveMethod; // 用于审批节点 | |
| 72 | + | |
| 73 | + @Schema(description = "通过比例", example = "100") | |
| 74 | + private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置 | |
| 75 | + | |
| 76 | + @Schema(description = "表单权限", example = "[]") | |
| 77 | + private List<Map<String, String>> fieldsPermission; | |
| 78 | + | |
| 79 | + @Schema(description = "操作按钮设置", example = "[]") | |
| 80 | + private List<OperationButtonSetting> buttonsSetting; // 用于审批节点 | |
| 81 | + | |
| 82 | + // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到 | |
| 83 | + /** | |
| 84 | + * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 | |
| 85 | + */ | |
| 86 | + @JsonIgnore | |
| 87 | + private String attachNodeId; | |
| 88 | + /** | |
| 89 | + * 审批节点拒绝处理 | |
| 90 | + */ | |
| 91 | + private RejectHandler rejectHandler; | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * 审批节点超时处理 | |
| 95 | + */ | |
| 96 | + private TimeoutHandler timeoutHandler; | |
| 97 | + | |
| 98 | + @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") | |
| 99 | + @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) | |
| 100 | + private Integer assignStartUserHandlerType; | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * 空处理策略 | |
| 104 | + */ | |
| 105 | + private AssignEmptyHandler assignEmptyHandler; | |
| 106 | + | |
| 107 | + @Schema(description = "审批节点拒绝处理策略") | |
| 108 | + @Data | |
| 109 | + public static class RejectHandler { | |
| 110 | + | |
| 111 | + @Schema(description = "拒绝处理类型", example = "1") | |
| 112 | + @InEnum(BpmUserTaskRejectHandlerType.class) | |
| 113 | + private Integer type; | |
| 114 | + | |
| 115 | + @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1") | |
| 116 | + private String returnNodeId; | |
| 117 | + } | |
| 118 | + | |
| 119 | + @Schema(description = "审批节点超时处理策略") | |
| 120 | + @Valid | |
| 121 | + @Data | |
| 122 | + public static class TimeoutHandler { | |
| 123 | + | |
| 124 | + @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | |
| 125 | + @NotNull(message = "是否开启超时处理不能为空") | |
| 126 | + private Boolean enable; | |
| 127 | + | |
| 128 | + @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 129 | + @NotNull(message = "任务超时未处理的行为不能为空") | |
| 130 | + @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class) | |
| 131 | + private Integer type; | |
| 132 | + | |
| 133 | + @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H") | |
| 134 | + @NotEmpty(message = "超时时间不能为空") | |
| 135 | + private String timeDuration; | |
| 136 | + | |
| 137 | + @Schema(description = "最大提醒次数", example = "1") | |
| 138 | + private Integer maxRemindCount; | |
| 139 | + | |
| 140 | + } | |
| 141 | + | |
| 142 | + @Schema(description = "空处理策略") | |
| 143 | + @Data | |
| 144 | + @Valid | |
| 145 | + public static class AssignEmptyHandler { | |
| 146 | + | |
| 147 | + @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 148 | + @NotNull(message = "空处理类型不能为空") | |
| 149 | + @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class) | |
| 150 | + private Integer type; | |
| 151 | + | |
| 152 | + @Schema(description = "指定人员审批的用户编号数组", example = "1") | |
| 153 | + private List<Long> userIds; | |
| 154 | + | |
| 155 | + } | |
| 156 | + | |
| 157 | + @Schema(description = "操作按钮设置") | |
| 158 | + @Data | |
| 159 | + @Valid | |
| 160 | + public static class OperationButtonSetting { | |
| 161 | + | |
| 162 | + // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。 | |
| 163 | + @Schema(description = "按钮 Id", example = "1") | |
| 164 | + private Integer id; | |
| 165 | + | |
| 166 | + @Schema(description = "显示名称", example = "审批") | |
| 167 | + private String displayName; | |
| 168 | + | |
| 169 | + @Schema(description = "是否启用", example = "true") | |
| 170 | + private Boolean enable; | |
| 171 | + } | |
| 172 | + | |
| 173 | + @Schema(description = "条件组") | |
| 174 | + @Data | |
| 175 | + @Valid | |
| 176 | + public static class ConditionGroups { | |
| 177 | + | |
| 178 | + @Schema(description = "条件组下的条件关系是否为与关系", example = "true") | |
| 179 | + @NotNull(message = "条件关系不能为空") | |
| 180 | + private Boolean and; | |
| 181 | + | |
| 182 | + @Schema(description = "条件组下的条件", example = "[]") | |
| 183 | + @NotEmpty(message = "条件不能为空") | |
| 184 | + private List<Condition> conditions; | |
| 185 | + } | |
| 186 | + | |
| 187 | + @Schema(description = "条件") | |
| 188 | + @Data | |
| 189 | + @Valid | |
| 190 | + public static class Condition { | |
| 191 | + | |
| 192 | + @Schema(description = "条件下的规则关系是否为与关系", example = "true") | |
| 193 | + @NotNull(message = "规则关系不能为空") | |
| 194 | + private Boolean and; | |
| 195 | + | |
| 196 | + @Schema(description = "条件下的规则", example = "[]") | |
| 197 | + @NotEmpty(message = "规则不能为空") | |
| 198 | + private List<ConditionRule> rules; | |
| 199 | + } | |
| 200 | + | |
| 201 | + @Schema(description = "条件规则") | |
| 202 | + @Data | |
| 203 | + @Valid | |
| 204 | + public static class ConditionRule { | |
| 205 | + | |
| 206 | + @Schema(description = "运行符号", example = "==") | |
| 207 | + @NotEmpty(message = "运行符号不能为空") | |
| 208 | + private String opCode; | |
| 209 | + | |
| 210 | + @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId") | |
| 211 | + @NotEmpty(message = "运算符左边的值不能为空") | |
| 212 | + private String leftSide; | |
| 213 | + | |
| 214 | + @Schema(description = "运算符右边的值", example = "1") | |
| 215 | + @NotEmpty(message = "运算符右边的值不能为空") | |
| 216 | + private String rightSide; | |
| 217 | + } | |
| 218 | + | |
| 219 | + // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 | |
| 220 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.Valid; | |
| 5 | +import jakarta.validation.constraints.NotEmpty; | |
| 6 | +import jakarta.validation.constraints.NotNull; | |
| 7 | +import lombok.Data; | |
| 8 | + | |
| 9 | +// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下; | |
| 10 | +@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") | |
| 11 | +@Data | |
| 12 | +public class BpmSimpleModelUpdateReqVO { | |
| 13 | + | |
| 14 | + @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 15 | + @NotEmpty(message = "流程模型编号不能为空") | |
| 16 | + private String id; // 对应 Flowable act_re_model 表 ID_ 字段 | |
| 17 | + | |
| 18 | + @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 19 | + @NotNull(message = "仿钉钉流程设计模型对象不能为空") | |
| 20 | + @Valid | |
| 21 | + private BpmSimpleModelNodeVO simpleModel; | |
| 22 | + | |
| 23 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; |
| 5 | +import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert; | |
| 5 | 6 | import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; |
| 6 | 7 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 7 | 8 | import io.swagger.v3.oas.annotations.Parameter; |
| ... | ... | @@ -34,6 +35,6 @@ public class BpmActivityController { |
| 34 | 35 | @PreAuthorize("@ss.hasPermission('bpm:task:query')") |
| 35 | 36 | public CommonResult<List<BpmActivityRespVO>> getActivityList( |
| 36 | 37 | @RequestParam("processInstanceId") String processInstanceId) { |
| 37 | - return success(activityService.getActivityListByProcessInstanceId(processInstanceId)); | |
| 38 | + return success(BpmActivityConvert.INSTANCE.convertList(activityService.getActivityListByProcessInstanceId(processInstanceId))); | |
| 38 | 39 | } |
| 39 | 40 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
| ... | ... | @@ -4,10 +4,7 @@ import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 7 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; | |
| 8 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; | |
| 9 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; | |
| 10 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | |
| 7 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; | |
| 11 | 8 | import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; |
| 12 | 9 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 13 | 10 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| ... | ... | @@ -160,4 +157,20 @@ public class BpmProcessInstanceController { |
| 160 | 157 | return success(true); |
| 161 | 158 | } |
| 162 | 159 | |
| 160 | + @GetMapping("/get-form-fields-permission") | |
| 161 | + @Operation(summary = "获得表单字段权限") | |
| 162 | + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") | |
| 163 | + public CommonResult<Map<String, String>> getFormFieldsPermission( | |
| 164 | + @Valid BpmFormFieldsPermissionReqVO reqVO) { | |
| 165 | + return success(processInstanceService.getFormFieldsPermission(reqVO)); | |
| 166 | + } | |
| 167 | + | |
| 168 | + @GetMapping("/get-approval-detail") | |
| 169 | + @Operation(summary = "获得审批详情") | |
| 170 | + @Parameter(name = "id", description = "流程实例的编号", required = true) | |
| 171 | + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") | |
| 172 | + public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { | |
| 173 | + return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); | |
| 174 | + } | |
| 175 | + | |
| 163 | 176 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java
| ... | ... | @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI |
| 11 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| 12 | 12 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; |
| 13 | 13 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 14 | -import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; | |
| 15 | 14 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 16 | 15 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 17 | 16 | import io.swagger.v3.oas.annotations.Operation; |
| ... | ... | @@ -43,8 +42,6 @@ public class BpmProcessInstanceCopyController { |
| 43 | 42 | private BpmProcessInstanceCopyService processInstanceCopyService; |
| 44 | 43 | @Resource |
| 45 | 44 | private BpmProcessInstanceService processInstanceService; |
| 46 | - @Resource | |
| 47 | - private BpmTaskService taskService; | |
| 48 | 45 | |
| 49 | 46 | @Resource |
| 50 | 47 | private AdminUserApi adminUserApi; |
| ... | ... | @@ -61,8 +58,6 @@ public class BpmProcessInstanceCopyController { |
| 61 | 58 | } |
| 62 | 59 | |
| 63 | 60 | // 拼接返回 |
| 64 | - Map<String, String> taskNameMap = taskService.getTaskNameByTaskIds( | |
| 65 | - convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId)); | |
| 66 | 61 | Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( |
| 67 | 62 | convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); |
| 68 | 63 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), |
| ... | ... | @@ -70,7 +65,6 @@ public class BpmProcessInstanceCopyController { |
| 70 | 65 | return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> { |
| 71 | 66 | MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname())); |
| 72 | 67 | MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname())); |
| 73 | - MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName); | |
| 74 | 68 | MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), |
| 75 | 69 | processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); |
| 76 | 70 | })); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
| ... | ... | @@ -8,6 +8,7 @@ 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; | |
| 11 | 12 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 12 | 13 | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| 13 | 14 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| ... | ... | @@ -19,6 +20,7 @@ import io.swagger.v3.oas.annotations.Parameter; |
| 19 | 20 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 20 | 21 | import jakarta.annotation.Resource; |
| 21 | 22 | import jakarta.validation.Valid; |
| 23 | +import org.flowable.bpmn.model.BpmnModel; | |
| 22 | 24 | import org.flowable.bpmn.model.UserTask; |
| 23 | 25 | import org.flowable.engine.history.HistoricProcessInstance; |
| 24 | 26 | import org.flowable.engine.runtime.ProcessInstance; |
| ... | ... | @@ -50,6 +52,8 @@ public class BpmTaskController { |
| 50 | 52 | private BpmProcessInstanceService processInstanceService; |
| 51 | 53 | @Resource |
| 52 | 54 | private BpmFormService formService; |
| 55 | + @Resource | |
| 56 | + private BpmProcessDefinitionService bpmProcessDefinitionService; | |
| 53 | 57 | |
| 54 | 58 | @Resource |
| 55 | 59 | private AdminUserApi adminUserApi; |
| ... | ... | @@ -134,8 +138,10 @@ public class BpmTaskController { |
| 134 | 138 | // 获得 Form Map |
| 135 | 139 | Map<Long, BpmFormDO> formMap = formService.getFormMap( |
| 136 | 140 | convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); |
| 141 | + // 获得 BpmnModel | |
| 142 | + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); | |
| 137 | 143 | return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, |
| 138 | - formMap, userMap, deptMap)); | |
| 144 | + formMap, userMap, deptMap, bpmnModel)); | |
| 139 | 145 | } |
| 140 | 146 | |
| 141 | 147 | @PutMapping("/approve") | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java
| ... | ... | @@ -24,6 +24,8 @@ public class BpmProcessInstanceCopyRespVO { |
| 24 | 24 | @Schema(description = "流程实例的发起时间") |
| 25 | 25 | private LocalDateTime processInstanceStartTime; |
| 26 | 26 | |
| 27 | + @Schema(description = "抄送的节点的活动编号") | |
| 28 | + private String activityId; | |
| 27 | 29 | @Schema(description = "发起抄送的任务编号") |
| 28 | 30 | private String taskId; |
| 29 | 31 | @Schema(description = "发起抄送的任务名称") | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
0 → 100644
| 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 | +// TODO @jason:这个可以简化下,使用 @RequestParam。嘿嘿,主要 VO 项不要太多 | |
| 10 | +@Schema(description = "管理后台 - 审批详情 Request VO") | |
| 11 | +@Data | |
| 12 | +public class BpmApprovalDetailReqVO { | |
| 13 | + | |
| 14 | + @Schema(description = "流程定义的编号", example = "1024") | |
| 15 | + private String processDefinitionId; | |
| 16 | + | |
| 17 | + @Schema(description = "流程实例的编号", example = "1024") | |
| 18 | + private String processInstanceId; | |
| 19 | + | |
| 20 | + @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") | |
| 21 | + @JsonIgnore | |
| 22 | + public boolean isValidProcessParam() { | |
| 23 | + return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId); | |
| 24 | + } | |
| 25 | + | |
| 26 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | + | |
| 10 | +@Schema(description = "管理后台 - 审批详情 Response VO") | |
| 11 | +@Data | |
| 12 | +public class BpmApprovalDetailRespVO { | |
| 13 | + | |
| 14 | + @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 15 | + private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 | |
| 16 | + | |
| 17 | + @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 18 | + private List<ApprovalNodeInfo> approveNodes; | |
| 19 | + | |
| 20 | + @Schema(description = "审批节点信息") | |
| 21 | + @Data | |
| 22 | + public static class ApprovalNodeInfo { | |
| 23 | + | |
| 24 | + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") | |
| 25 | + private String id; | |
| 26 | + | |
| 27 | + @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") | |
| 28 | + private String name; | |
| 29 | + | |
| 30 | + @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 31 | + private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 | |
| 32 | + | |
| 33 | + @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 34 | + private Integer status; // 参见 BpmTaskStatusEnum 枚举 | |
| 35 | + | |
| 36 | + @Schema(description = "节点的开始时间") | |
| 37 | + private LocalDateTime startTime; | |
| 38 | + @Schema(description = "节点的结束时间") | |
| 39 | + private LocalDateTime endTime; | |
| 40 | + | |
| 41 | + @Schema(description = "审批节点的任务信息") | |
| 42 | + private List<ApprovalTaskInfo> tasks; | |
| 43 | + | |
| 44 | + @Schema(description = "候选人用户列表") | |
| 45 | + // TODO @jason:candidateUserList => candidateUsers,保持和 tasks 的命名风格一致哈 | |
| 46 | + private List<User> candidateUserList; // 用于未运行任务节点 | |
| 47 | + | |
| 48 | + } | |
| 49 | + | |
| 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; | |
| 63 | + | |
| 64 | + } | |
| 65 | + | |
| 66 | + @Schema(description = "审批任务信息") | |
| 67 | + @Data | |
| 68 | + public static class ApprovalTaskInfo { | |
| 69 | + | |
| 70 | + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 71 | + private String id; | |
| 72 | + | |
| 73 | + @Schema(description = "任务所属人", example = "1024") | |
| 74 | + private User ownerUser; | |
| 75 | + | |
| 76 | + @Schema(description = "任务分配人", example = "2048") | |
| 77 | + private User assigneeUser; | |
| 78 | + | |
| 79 | + @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 80 | + private Integer status; // 参见 BpmTaskStatusEnum 枚举 | |
| 81 | + | |
| 82 | + @Schema(description = "审批意见", example = "同意") | |
| 83 | + private String reason; | |
| 84 | + | |
| 85 | + } | |
| 86 | + | |
| 87 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java
0 → 100644
| 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/task/BpmTaskRespVO.java
| ... | ... | @@ -61,12 +61,17 @@ public class BpmTaskRespVO { |
| 61 | 61 | private Long formId; |
| 62 | 62 | @Schema(description = "表单名字", example = "请假表单") |
| 63 | 63 | private String formName; |
| 64 | - @Schema(description = "表单的配置-JSON 字符串") | |
| 64 | + @Schema(description = "表单的配置,JSON 字符串") | |
| 65 | 65 | private String formConf; |
| 66 | 66 | @Schema(description = "表单项的数组") |
| 67 | 67 | private List<String> formFields; |
| 68 | 68 | @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) |
| 69 | 69 | private Map<String, Object> formVariables; |
| 70 | + // @芋艿 都改成了 fieldsPermission。 buttonsSetting。和 BpmSimpleModelNodeVO 统一 | |
| 71 | + @Schema(description = "表单字段权限值") | |
| 72 | + private Map<String, String> fieldsPermission; | |
| 73 | + @Schema(description = "操作按钮设置值") | |
| 74 | + private Map<Integer, OperationButtonSetting> buttonsSetting; | |
| 70 | 75 | |
| 71 | 76 | @Data |
| 72 | 77 | @Schema(description = "流程实例") |
| ... | ... | @@ -91,4 +96,15 @@ public class BpmTaskRespVO { |
| 91 | 96 | |
| 92 | 97 | } |
| 93 | 98 | |
| 99 | + @Data | |
| 100 | + @Schema(description = "操作按钮设置") | |
| 101 | + public static class OperationButtonSetting { | |
| 102 | + | |
| 103 | + @Schema(description = "显示名称", example = "审批") | |
| 104 | + private String displayName; | |
| 105 | + | |
| 106 | + @Schema(description = "是否启用", example = "true") | |
| 107 | + private Boolean enable; | |
| 108 | + } | |
| 109 | + | |
| 94 | 110 | } | ... | ... |
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.hutool.core.util.StrUtil; | |
| 5 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 7 | 5 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 8 | 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 9 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; | |
| 8 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | |
| 11 | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; |
| 12 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; | |
| 11 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | |
| 13 | 12 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| 14 | 13 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 15 | 14 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 16 | 15 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 17 | -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; | |
| 16 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 18 | 17 | import org.flowable.common.engine.impl.db.SuspensionState; |
| 19 | 18 | import org.flowable.engine.repository.Deployment; |
| 20 | 19 | import org.flowable.engine.repository.Model; |
| ... | ... | @@ -22,9 +21,11 @@ import org.flowable.engine.repository.ProcessDefinition; |
| 22 | 21 | import org.mapstruct.Mapper; |
| 23 | 22 | import org.mapstruct.factory.Mappers; |
| 24 | 23 | |
| 24 | +import java.util.Collections; | |
| 25 | 25 | import java.util.List; |
| 26 | 26 | import java.util.Map; |
| 27 | -import java.util.Objects; | |
| 27 | + | |
| 28 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 28 | 29 | |
| 29 | 30 | /** |
| 30 | 31 | * 流程模型 Convert |
| ... | ... | @@ -39,22 +40,24 @@ public interface BpmModelConvert { |
| 39 | 40 | default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult, |
| 40 | 41 | Map<Long, BpmFormDO> formMap, |
| 41 | 42 | Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap, |
| 42 | - Map<String, ProcessDefinition> processDefinitionMap) { | |
| 43 | - List<BpmModelRespVO> list = CollectionUtils.convertList(pageResult.getList(), model -> { | |
| 44 | - BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); | |
| 43 | + Map<String, ProcessDefinition> processDefinitionMap, | |
| 44 | + Map<Long, AdminUserRespDTO> userMap) { | |
| 45 | + List<BpmModelRespVO> list = convertList(pageResult.getList(), model -> { | |
| 46 | + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); | |
| 45 | 47 | BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; |
| 46 | 48 | BpmCategoryDO category = categoryMap.get(model.getCategory()); |
| 47 | 49 | Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; |
| 48 | 50 | ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; |
| 49 | - return buildModel0(model, metaInfo, form, category, deployment, processDefinition); | |
| 51 | + List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; | |
| 52 | + return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); | |
| 50 | 53 | }); |
| 51 | 54 | return new PageResult<>(list, pageResult.getTotal()); |
| 52 | 55 | } |
| 53 | 56 | |
| 54 | 57 | default BpmModelRespVO buildModel(Model model, |
| 55 | 58 | byte[] bpmnBytes) { |
| 56 | - BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); | |
| 57 | - BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null); | |
| 59 | + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); | |
| 60 | + BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); | |
| 58 | 61 | if (ArrayUtil.isNotEmpty(bpmnBytes)) { |
| 59 | 62 | modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); |
| 60 | 63 | } |
| ... | ... | @@ -62,20 +65,16 @@ public interface BpmModelConvert { |
| 62 | 65 | } |
| 63 | 66 | |
| 64 | 67 | default BpmModelRespVO buildModel0(Model model, |
| 65 | - BpmModelMetaInfoRespDTO metaInfo, BpmFormDO form, BpmCategoryDO category, | |
| 66 | - Deployment deployment, ProcessDefinition processDefinition) { | |
| 68 | + BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, | |
| 69 | + Deployment deployment, ProcessDefinition processDefinition, | |
| 70 | + List<AdminUserRespDTO> startUsers) { | |
| 67 | 71 | BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) |
| 68 | 72 | .setKey(model.getKey()).setCategory(model.getCategory()) |
| 69 | 73 | .setCreateTime(DateUtils.of(model.getCreateTime())); |
| 70 | 74 | // Form |
| 71 | - if (metaInfo != null) { | |
| 72 | - modelRespVO.setFormType(metaInfo.getFormType()).setFormId(metaInfo.getFormId()) | |
| 73 | - .setFormCustomCreatePath(metaInfo.getFormCustomCreatePath()) | |
| 74 | - .setFormCustomViewPath(metaInfo.getFormCustomViewPath()); | |
| 75 | - modelRespVO.setIcon(metaInfo.getIcon()).setDescription(metaInfo.getDescription()); | |
| 76 | - } | |
| 75 | + BeanUtils.copyProperties(metaInfo, modelRespVO); | |
| 77 | 76 | if (form != null) { |
| 78 | - modelRespVO.setFormId(form.getId()).setFormName(form.getName()); | |
| 77 | + modelRespVO.setFormName(form.getName()); | |
| 79 | 78 | } |
| 80 | 79 | // Category |
| 81 | 80 | if (category != null) { |
| ... | ... | @@ -90,49 +89,30 @@ public interface BpmModelConvert { |
| 90 | 89 | modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); |
| 91 | 90 | } |
| 92 | 91 | } |
| 92 | + // User | |
| 93 | + modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)); | |
| 93 | 94 | return modelRespVO; |
| 94 | 95 | } |
| 95 | 96 | |
| 96 | - default void copyToCreateModel(Model model, BpmModelCreateReqVO bean) { | |
| 97 | - model.setName(bean.getName()); | |
| 98 | - model.setKey(bean.getKey()); | |
| 99 | - model.setMetaInfo(buildMetaInfoStr(null, | |
| 100 | - null, bean.getDescription(), | |
| 101 | - null, null, null, null)); | |
| 102 | - } | |
| 103 | - | |
| 104 | - default void copyToUpdateModel(Model model, BpmModelUpdateReqVO bean) { | |
| 105 | - model.setName(bean.getName()); | |
| 106 | - model.setCategory(bean.getCategory()); | |
| 107 | - model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model), | |
| 108 | - bean.getIcon(), bean.getDescription(), | |
| 109 | - bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath())); | |
| 97 | + default void copyToModel(Model model, BpmModelSaveReqVO reqVO) { | |
| 98 | + model.setName(reqVO.getName()); | |
| 99 | + model.setKey(reqVO.getKey()); | |
| 100 | + model.setCategory(reqVO.getCategory()); | |
| 101 | + model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); | |
| 110 | 102 | } |
| 111 | 103 | |
| 112 | - default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, | |
| 113 | - String icon, String description, | |
| 114 | - Integer formType, Long formId, String formCustomCreatePath, String formCustomViewPath) { | |
| 115 | - if (metaInfo == null) { | |
| 116 | - metaInfo = new BpmModelMetaInfoRespDTO(); | |
| 104 | + default BpmModelMetaInfoVO parseMetaInfo(Model model) { | |
| 105 | + BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); | |
| 106 | + if (vo == null) { | |
| 107 | + return null; | |
| 117 | 108 | } |
| 118 | - // 只有非空,才进行设置,避免更新时的覆盖 | |
| 119 | - if (StrUtil.isNotEmpty(icon)) { | |
| 120 | - metaInfo.setIcon(icon); | |
| 109 | + if (vo.getManagerUserIds() == null) { | |
| 110 | + vo.setManagerUserIds(Collections.emptyList()); | |
| 121 | 111 | } |
| 122 | - if (StrUtil.isNotEmpty(description)) { | |
| 123 | - metaInfo.setDescription(description); | |
| 112 | + if (vo.getStartUserIds() == null) { | |
| 113 | + vo.setStartUserIds(Collections.emptyList()); | |
| 124 | 114 | } |
| 125 | - if (Objects.nonNull(formType)) { | |
| 126 | - metaInfo.setFormType(formType); | |
| 127 | - metaInfo.setFormId(formId); | |
| 128 | - metaInfo.setFormCustomCreatePath(formCustomCreatePath); | |
| 129 | - metaInfo.setFormCustomViewPath(formCustomViewPath); | |
| 130 | - } | |
| 131 | - return JsonUtils.toJsonString(metaInfo); | |
| 132 | - } | |
| 133 | - | |
| 134 | - default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) { | |
| 135 | - return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); | |
| 115 | + return vo; | |
| 136 | 116 | } |
| 137 | 117 | |
| 138 | 118 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
| ... | ... | @@ -89,11 +89,6 @@ public interface BpmProcessInstanceConvert { |
| 89 | 89 | @Mapping(source = "from.id", target = "to.id", ignore = true) |
| 90 | 90 | void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); |
| 91 | 91 | |
| 92 | - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, HistoricProcessInstance instance, Integer status) { | |
| 93 | - return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) | |
| 94 | - .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); | |
| 95 | - } | |
| 96 | - | |
| 97 | 92 | default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {; |
| 98 | 93 | return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) |
| 99 | 94 | .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
| ... | ... | @@ -9,10 +9,13 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| 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 | +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; | |
| 13 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 12 | 14 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 13 | 15 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 14 | 16 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 15 | 17 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 18 | +import org.flowable.bpmn.model.BpmnModel; | |
| 16 | 19 | import org.flowable.engine.history.HistoricProcessInstance; |
| 17 | 20 | import org.flowable.engine.runtime.ProcessInstance; |
| 18 | 21 | import org.flowable.task.api.Task; |
| ... | ... | @@ -81,10 +84,12 @@ public interface BpmTaskConvert { |
| 81 | 84 | HistoricProcessInstance processInstance, |
| 82 | 85 | Map<Long, BpmFormDO> formMap, |
| 83 | 86 | Map<Long, AdminUserRespDTO> userMap, |
| 84 | - Map<Long, DeptRespDTO> deptMap) { | |
| 87 | + Map<Long, DeptRespDTO> deptMap, | |
| 88 | + BpmnModel bpmnModel) { | |
| 85 | 89 | List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> { |
| 86 | 90 | BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); |
| 87 | - taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); | |
| 91 | + Integer taskStatus = FlowableUtils.getTaskStatus(task); | |
| 92 | + taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); | |
| 88 | 93 | // 流程实例 |
| 89 | 94 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 90 | 95 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| ... | ... | @@ -106,7 +111,15 @@ public interface BpmTaskConvert { |
| 106 | 111 | taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); |
| 107 | 112 | findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); |
| 108 | 113 | } |
| 109 | - return taskVO; | |
| 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; | |
| 110 | 123 | }); |
| 111 | 124 | |
| 112 | 125 | // 拼接父子关系 |
| ... | ... | @@ -151,12 +164,12 @@ public interface BpmTaskConvert { |
| 151 | 164 | |
| 152 | 165 | /** |
| 153 | 166 | * 将父任务的属性,拷贝到子任务(加签任务) |
| 154 | - * | |
| 167 | + * <p> | |
| 155 | 168 | * 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。 |
| 156 | 169 | * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。 |
| 157 | 170 | * |
| 158 | 171 | * @param parentTask 父任务 |
| 159 | - * @param childTask 加签任务 | |
| 172 | + * @param childTask 加签任务 | |
| 160 | 173 | */ |
| 161 | 174 | default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) { |
| 162 | 175 | childTask.setName(parentTask.getName()); |
| ... | ... | @@ -165,7 +178,6 @@ public interface BpmTaskConvert { |
| 165 | 178 | childTask.setParentTaskId(parentTask.getId()); |
| 166 | 179 | childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); |
| 167 | 180 | childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); |
| 168 | -// childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错) | |
| 169 | 181 | childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); |
| 170 | 182 | childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); |
| 171 | 183 | childTask.setPriority(parentTask.getPriority()); | ... | ... |
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.StringListTypeHandler; | |
| 4 | 5 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| 6 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; | |
| 7 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 5 | 8 | import com.baomidou.mybatisplus.annotation.TableField; |
| 6 | 9 | import com.baomidou.mybatisplus.annotation.TableId; |
| 7 | 10 | import com.baomidou.mybatisplus.annotation.TableName; |
| 8 | 11 | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| 9 | -import lombok.*; | |
| 12 | +import lombok.AllArgsConstructor; | |
| 13 | +import lombok.Builder; | |
| 14 | +import lombok.Data; | |
| 15 | +import lombok.NoArgsConstructor; | |
| 16 | +import org.flowable.engine.repository.Model; | |
| 17 | +import org.flowable.engine.repository.ProcessDefinition; | |
| 10 | 18 | |
| 11 | 19 | import java.util.List; |
| 12 | 20 | |
| ... | ... | @@ -31,15 +39,21 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { |
| 31 | 39 | /** |
| 32 | 40 | * 流程定义的编号 |
| 33 | 41 | * |
| 34 | - * 关联 ProcessDefinition 的 id 属性 | |
| 42 | + * 关联 {@link ProcessDefinition#getId()} 属性 | |
| 35 | 43 | */ |
| 36 | 44 | private String processDefinitionId; |
| 37 | 45 | /** |
| 38 | 46 | * 流程模型的编号 |
| 39 | 47 | * |
| 40 | - * 关联 Model 的 id 属性 | |
| 48 | + * 关联 {@link Model#getId()} 属性 | |
| 41 | 49 | */ |
| 42 | 50 | private String modelId; |
| 51 | + /** | |
| 52 | + * 流程模型的类型 | |
| 53 | + * | |
| 54 | + * 枚举 {@link BpmModelTypeEnum} | |
| 55 | + */ | |
| 56 | + private Integer modelType; | |
| 43 | 57 | |
| 44 | 58 | /** |
| 45 | 59 | * 图标 |
| ... | ... | @@ -53,11 +67,12 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { |
| 53 | 67 | /** |
| 54 | 68 | * 表单类型 |
| 55 | 69 | * |
| 56 | - * 关联 {@link BpmModelFormTypeEnum} | |
| 70 | + * 枚举 {@link BpmModelFormTypeEnum} | |
| 57 | 71 | */ |
| 58 | 72 | private Integer formType; |
| 59 | 73 | /** |
| 60 | 74 | * 动态表单编号 |
| 75 | + * | |
| 61 | 76 | * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 |
| 62 | 77 | * |
| 63 | 78 | * 关联 {@link BpmFormDO#getId()} |
| ... | ... | @@ -65,6 +80,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { |
| 65 | 80 | private Long formId; |
| 66 | 81 | /** |
| 67 | 82 | * 表单的配置 |
| 83 | + * | |
| 68 | 84 | * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 |
| 69 | 85 | * |
| 70 | 86 | * 冗余 {@link BpmFormDO#getConf()} |
| ... | ... | @@ -72,21 +88,59 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { |
| 72 | 88 | private String formConf; |
| 73 | 89 | /** |
| 74 | 90 | * 表单项的数组 |
| 91 | + * | |
| 75 | 92 | * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 |
| 76 | 93 | * |
| 77 | - * 冗余 {@link BpmFormDO#getFields()} ()} | |
| 94 | + * 冗余 {@link BpmFormDO#getFields()} | |
| 78 | 95 | */ |
| 79 | 96 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 80 | 97 | private List<String> formFields; |
| 81 | 98 | /** |
| 82 | 99 | * 自定义表单的提交路径,使用 Vue 的路由地址 |
| 100 | + * | |
| 83 | 101 | * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 |
| 84 | 102 | */ |
| 85 | 103 | private String formCustomCreatePath; |
| 86 | 104 | /** |
| 87 | 105 | * 自定义表单的查看路径,使用 Vue 的路由地址 |
| 106 | + * | |
| 88 | 107 | * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 |
| 89 | 108 | */ |
| 90 | 109 | private String formCustomViewPath; |
| 91 | 110 | |
| 111 | + /** | |
| 112 | + * SIMPLE 设计器模型数据 json 格式 | |
| 113 | + * | |
| 114 | + * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。 | |
| 115 | + */ | |
| 116 | + private String simpleModel; | |
| 117 | + /** | |
| 118 | + * 是否可见 | |
| 119 | + * | |
| 120 | + * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 | |
| 121 | + */ | |
| 122 | + private Boolean visible; | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * 可发起用户编号数组 | |
| 126 | + * | |
| 127 | + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 | |
| 128 | + * | |
| 129 | + * 如果为空,则表示“全部可以发起”! | |
| 130 | + * | |
| 131 | + * 它和 {@link #visible} 的区别在于: | |
| 132 | + * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起 | |
| 133 | + * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的 | |
| 134 | + */ | |
| 135 | + @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 | |
| 136 | + private List<Long> startUserIds; | |
| 137 | + | |
| 138 | + /** | |
| 139 | + * 可管理用户编号数组 | |
| 140 | + * | |
| 141 | + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 | |
| 142 | + */ | |
| 143 | + @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 | |
| 144 | + private List<Long> managerUserIds; | |
| 145 | + | |
| 92 | 146 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
| ... | ... | @@ -48,10 +48,15 @@ public class BpmProcessInstanceCopyDO extends BaseDO { |
| 48 | 48 | * 冗余 ProcessInstance 的 category 字段 |
| 49 | 49 | */ |
| 50 | 50 | private String category; |
| 51 | - | |
| 51 | + /** | |
| 52 | + * 流程活动编号 | |
| 53 | + * <p/> | |
| 54 | + * 对应 BPMN XML 节点编号,用于查询抄送节点的表单字段权限 | |
| 55 | + * 这里冗余的原因:如果是钉钉易搭的抄送节点 (ServiceTask),使用 taskId 可能查不到对应的 activityId | |
| 56 | + */ | |
| 57 | + private String activityId; | |
| 52 | 58 | /** |
| 53 | 59 | * 任务主键 |
| 54 | - * | |
| 55 | 60 | * 关联 Task 的 id 属性 |
| 56 | 61 | */ |
| 57 | 62 | private String taskId; | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
| ... | ... | @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| 8 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 9 | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 10 | 12 | @Mapper |
| 11 | 13 | public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInstanceCopyDO> { |
| 12 | 14 | |
| ... | ... | @@ -18,4 +20,9 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInst |
| 18 | 20 | .orderByDesc(BpmProcessInstanceCopyDO::getId)); |
| 19 | 21 | } |
| 20 | 22 | |
| 23 | + default List<BpmProcessInstanceCopyDO> selectListByProcessInstanceIdAndActivityId(String processInstanceId, String activityId) { | |
| 24 | + return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId, | |
| 25 | + BpmProcessInstanceCopyDO::getActivityId, activityId); | |
| 26 | + } | |
| 27 | + | |
| 21 | 28 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; |
| 8 | 8 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 9 | +import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; | |
| 9 | 10 | import org.flowable.common.engine.api.delegate.event.FlowableEventListener; |
| 10 | 11 | import org.flowable.spring.SpringProcessEngineConfiguration; |
| 11 | 12 | import org.flowable.spring.boot.EngineConfigurationConfigurer; |
| ... | ... | @@ -56,12 +57,15 @@ public class BpmFlowableConfiguration { |
| 56 | 57 | @Bean |
| 57 | 58 | public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer( |
| 58 | 59 | ObjectProvider<FlowableEventListener> listeners, |
| 60 | + ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates, | |
| 59 | 61 | BpmActivityBehaviorFactory bpmActivityBehaviorFactory) { |
| 60 | 62 | return configuration -> { |
| 61 | 63 | // 注册监听器,例如说 BpmActivityEventListener |
| 62 | 64 | configuration.setEventListeners(ListUtil.toList(listeners.iterator())); |
| 63 | 65 | // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义 |
| 64 | 66 | configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); |
| 67 | + // 设置自定义的函数 | |
| 68 | + configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); | |
| 65 | 69 | }; |
| 66 | 70 | } |
| 67 | 71 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 5 | 7 | import lombok.Setter; |
| 6 | 8 | import org.flowable.bpmn.model.Activity; |
| 7 | 9 | import org.flowable.engine.delegate.DelegateExecution; |
| ... | ... | @@ -48,12 +50,17 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav |
| 48 | 50 | super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); |
| 49 | 51 | |
| 50 | 52 | // 第二步,获取任务的所有处理人 |
| 51 | - // 由于每次审批(会签、或签等情况)后都会执行一次,所以 variable 已经有结果,不重复计算 | |
| 52 | 53 | @SuppressWarnings("unchecked") |
| 53 | 54 | Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); |
| 54 | 55 | if (assigneeUserIds == null) { |
| 55 | 56 | assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); |
| 56 | 57 | execution.setVariable(super.collectionVariable, assigneeUserIds); |
| 58 | + if (CollUtil.isEmpty(assigneeUserIds)) { | |
| 59 | + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! | |
| 60 | + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 | |
| 61 | + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 | |
| 62 | + assigneeUserIds = SetUtils.asSet((Long) null); | |
| 63 | + } | |
| 57 | 64 | } |
| 58 | 65 | return assigneeUserIds.size(); |
| 59 | 66 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 3 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| 4 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 5 | 7 | import lombok.Setter; |
| ... | ... | @@ -41,12 +43,17 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB |
| 41 | 43 | super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); |
| 42 | 44 | |
| 43 | 45 | // 第二步,获取任务的所有处理人 |
| 44 | - // 由于每次审批(会签、或签等情况)后都会执行一次,所以 variable 已经有结果,不重复计算 | |
| 45 | 46 | @SuppressWarnings("unchecked") |
| 46 | 47 | Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); |
| 47 | 48 | if (assigneeUserIds == null) { |
| 48 | 49 | assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); |
| 49 | 50 | execution.setVariable(super.collectionVariable, assigneeUserIds); |
| 51 | + if (CollUtil.isEmpty(assigneeUserIds)) { | |
| 52 | + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! | |
| 53 | + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 | |
| 54 | + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 | |
| 55 | + assigneeUserIds = SetUtils.asSet((Long) null); | |
| 56 | + } | |
| 50 | 57 | } |
| 51 | 58 | return assigneeUserIds.size(); |
| 52 | 59 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | -import cn.hutool.core.lang.Assert; | |
| 5 | 4 | import cn.hutool.core.util.RandomUtil; |
| 6 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| 7 | 6 | import lombok.Setter; |
| ... | ... | @@ -14,6 +13,7 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; |
| 14 | 13 | import org.flowable.engine.impl.util.TaskHelper; |
| 15 | 14 | import org.flowable.task.service.TaskService; |
| 16 | 15 | import org.flowable.task.service.impl.persistence.entity.TaskEntity; |
| 16 | +import org.springframework.transaction.annotation.Transactional; | |
| 17 | 17 | |
| 18 | 18 | import java.util.List; |
| 19 | 19 | import java.util.Set; |
| ... | ... | @@ -36,14 +36,16 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | 38 | @Override |
| 39 | + @Transactional(rollbackFor = Exception.class) | |
| 39 | 40 | protected void handleAssignments(TaskService taskService, String assignee, String owner, |
| 40 | 41 | List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, |
| 41 | 42 | DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { |
| 42 | 43 | // 第一步,获得任务的候选用户 |
| 43 | 44 | Long assigneeUserId = calculateTaskCandidateUsers(execution); |
| 44 | - Assert.notNull(assigneeUserId, "任务处理人不能为空"); | |
| 45 | 45 | // 第二步,设置作为负责人 |
| 46 | - TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); | |
| 46 | + if (assigneeUserId != null) { | |
| 47 | + TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); | |
| 48 | + } | |
| 47 | 49 | } |
| 48 | 50 | |
| 49 | 51 | private Long calculateTaskCandidateUsers(DelegateExecution execution) { |
| ... | ... | @@ -56,6 +58,9 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { |
| 56 | 58 | // 情况二,如果非多实例的任务,则计算任务处理人 |
| 57 | 59 | // 第一步,先计算可处理该任务的处理人们 |
| 58 | 60 | Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution); |
| 61 | + if (CollUtil.isEmpty(candidateUserIds)) { | |
| 62 | + return null; | |
| 63 | + } | |
| 59 | 64 | // 第二步,后随机选择一个任务的处理人 |
| 60 | 65 | // 疑问:为什么一定要选择一个任务处理人? |
| 61 | 66 | // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
| ... | ... | @@ -2,11 +2,17 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; |
| 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; | |
| 5 | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | +import cn.hutool.extra.spring.SpringUtil; | |
| 6 | 8 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 9 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | |
| 7 | 10 | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; |
| 11 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; | |
| 12 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | |
| 8 | 13 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 9 | 14 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 15 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 10 | 16 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 11 | 17 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 12 | 18 | import com.google.common.annotations.VisibleForTesting; |
| ... | ... | @@ -14,15 +20,12 @@ import lombok.extern.slf4j.Slf4j; |
| 14 | 20 | import org.flowable.bpmn.model.BpmnModel; |
| 15 | 21 | import org.flowable.bpmn.model.UserTask; |
| 16 | 22 | import org.flowable.engine.delegate.DelegateExecution; |
| 23 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 17 | 24 | |
| 18 | -import java.util.HashMap; | |
| 19 | -import java.util.List; | |
| 20 | -import java.util.Map; | |
| 21 | -import java.util.Set; | |
| 25 | +import java.util.*; | |
| 22 | 26 | |
| 23 | 27 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 24 | 28 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; |
| 25 | -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER; | |
| 26 | 29 | |
| 27 | 30 | /** |
| 28 | 31 | * {@link BpmTaskCandidateStrategy} 的调用者,用于调用对应的策略,实现任务的候选人的计算 |
| ... | ... | @@ -57,7 +60,14 @@ public class BpmTaskCandidateInvoker { |
| 57 | 60 | List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); |
| 58 | 61 | // 遍历所有的 UserTask,校验审批人配置 |
| 59 | 62 | userTaskList.forEach(userTask -> { |
| 60 | - // 1. 非空校验 | |
| 63 | + // 1.1 非人工审批,无需校验审批人配置 | |
| 64 | + Integer approveType = BpmnModelUtils.parseApproveType(userTask); | |
| 65 | + if (ObjectUtils.equalsAny(approveType, | |
| 66 | + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), | |
| 67 | + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { | |
| 68 | + return; | |
| 69 | + } | |
| 70 | + // 1.2 非空校验 | |
| 61 | 71 | Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask); |
| 62 | 72 | String param = BpmnModelUtils.parseCandidateParam(userTask); |
| 63 | 73 | if (strategy == null) { |
| ... | ... | @@ -80,19 +90,31 @@ public class BpmTaskCandidateInvoker { |
| 80 | 90 | */ |
| 81 | 91 | @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 |
| 82 | 92 | public Set<Long> calculateUsers(DelegateExecution execution) { |
| 93 | + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 | |
| 94 | + Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement()); | |
| 95 | + if (ObjectUtils.equalsAny(approveType, | |
| 96 | + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), | |
| 97 | + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { | |
| 98 | + return new HashSet<>(); | |
| 99 | + } | |
| 100 | + | |
| 83 | 101 | Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement()); |
| 84 | 102 | String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); |
| 85 | 103 | // 1.1 计算任务的候选人 |
| 86 | 104 | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); |
| 105 | + removeDisableUsers(userIds); | |
| 87 | 106 | // 1.2 移除被禁用的用户 |
| 88 | 107 | removeDisableUsers(userIds); |
| 89 | 108 | |
| 90 | - // 2. 校验是否有候选人 | |
| 109 | + // 2. 候选人为空时,根据“审批人为空”的配置补充 | |
| 91 | 110 | if (CollUtil.isEmpty(userIds)) { |
| 92 | - log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(), | |
| 93 | - execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param); | |
| 94 | - throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER); | |
| 111 | + userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) | |
| 112 | + .calculateUsers(execution, param); | |
| 113 | + // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! | |
| 95 | 114 | } |
| 115 | + | |
| 116 | + // 3. 移除发起人的用户 | |
| 117 | + removeStartUserIfSkip(execution, userIds); | |
| 96 | 118 | return userIds; |
| 97 | 119 | } |
| 98 | 120 | |
| ... | ... | @@ -108,7 +130,30 @@ public class BpmTaskCandidateInvoker { |
| 108 | 130 | }); |
| 109 | 131 | } |
| 110 | 132 | |
| 111 | - private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { | |
| 133 | + /** | |
| 134 | + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 | |
| 135 | + * | |
| 136 | + * 注意:如果只有一个候选人,则不处理,避免无法审批 | |
| 137 | + * | |
| 138 | + * @param execution 执行中的任务 | |
| 139 | + * @param assigneeUserIds 当前分配的候选人 | |
| 140 | + */ | |
| 141 | + @VisibleForTesting | |
| 142 | + void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) { | |
| 143 | + if (CollUtil.size(assigneeUserIds) <= 1) { | |
| 144 | + return; | |
| 145 | + } | |
| 146 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); | |
| 147 | + if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | |
| 148 | + return; | |
| 149 | + } | |
| 150 | + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) | |
| 151 | + .getProcessInstance(execution.getProcessInstanceId()); | |
| 152 | + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); | |
| 153 | + assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); | |
| 154 | + } | |
| 155 | + | |
| 156 | + public BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { | |
| 112 | 157 | BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); |
| 113 | 158 | Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); |
| 114 | 159 | 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
| ... | ... | @@ -2,12 +2,14 @@ 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 | 4 | import org.flowable.engine.delegate.DelegateExecution; |
| 5 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 5 | 6 | |
| 7 | +import java.util.Collections; | |
| 6 | 8 | import java.util.Set; |
| 7 | 9 | |
| 8 | 10 | /** |
| 9 | 11 | * BPM 任务的候选人的策略接口 |
| 10 | - * | |
| 12 | + * <p> | |
| 11 | 13 | * 例如说:分配审批人 |
| 12 | 14 | * |
| 13 | 15 | * @author 芋道源码 |
| ... | ... | @@ -29,20 +31,58 @@ public interface BpmTaskCandidateStrategy { |
| 29 | 31 | void validateParam(String param); |
| 30 | 32 | |
| 31 | 33 | /** |
| 34 | + * 是否一定要输入参数 | |
| 35 | + * | |
| 36 | + * @return 是否 | |
| 37 | + */ | |
| 38 | + default boolean isParamRequired() { | |
| 39 | + return true; | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * 基于候选人参数,获得任务的候选用户们 | |
| 44 | + * | |
| 45 | + * @param param 执行任务 | |
| 46 | + * @return 用户编号集合 | |
| 47 | + */ | |
| 48 | + default Set<Long> calculateUsers(String param) { | |
| 49 | + return Collections.emptySet(); | |
| 50 | + } | |
| 51 | + | |
| 52 | + /** | |
| 32 | 53 | * 基于执行任务,获得任务的候选用户们 |
| 33 | 54 | * |
| 34 | 55 | * @param execution 执行任务 |
| 35 | 56 | * @return 用户编号集合 |
| 36 | 57 | */ |
| 37 | - Set<Long> calculateUsers(DelegateExecution execution, String param); | |
| 58 | + default Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 59 | + Set<Long> users = calculateUsers(param); | |
| 60 | + removeDisableUsers(users); | |
| 61 | + return users; | |
| 62 | + } | |
| 38 | 63 | |
| 39 | 64 | /** |
| 40 | - * 是否一定要输入参数 | |
| 65 | + * 基于流程实例,获得任务的候选用户们 | |
| 66 | + * <p> | |
| 67 | + * 目的:用于获取未执行节点的候选用户们 | |
| 41 | 68 | * |
| 42 | - * @return 是否 | |
| 69 | + * @param startUserId 流程发起人编号 | |
| 70 | + * @param processInstance 流程实例编号 | |
| 71 | + * @param activityId 活动 Id (对应 Bpmn XML id) | |
| 72 | + * @param param 节点的参数 | |
| 73 | + * @return 用户编号集合 | |
| 43 | 74 | */ |
| 44 | - default boolean isParamRequired() { | |
| 45 | - return true; | |
| 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; | |
| 46 | 79 | } |
| 47 | 80 | |
| 81 | + /** | |
| 82 | + * 移除被禁用的用户 | |
| 83 | + * | |
| 84 | + * @param users 用户 Ids | |
| 85 | + */ | |
| 86 | + void removeDisableUsers(Set<Long> users); | |
| 87 | + | |
| 48 | 88 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 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.system.api.dept.DeptApi; | |
| 7 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 8 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 9 | + | |
| 10 | +import java.util.*; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类 | |
| 14 | + * | |
| 15 | + * @author jason | |
| 16 | + */ | |
| 17 | +public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 18 | + | |
| 19 | + protected DeptApi deptApi; | |
| 20 | + | |
| 21 | + public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 22 | + super(adminUserApi); | |
| 23 | + this.deptApi = deptApi; | |
| 24 | + } | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 获得指定层级的部门负责人,只有第 level 的负责人 | |
| 28 | + * | |
| 29 | + * @param dept 指定部门 | |
| 30 | + * @param level 第几级 | |
| 31 | + * @return 部门负责人的编号 | |
| 32 | + */ | |
| 33 | + protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) { | |
| 34 | + Assert.isTrue(level > 0, "level 必须大于 0"); | |
| 35 | + if (dept == null) { | |
| 36 | + return null; | |
| 37 | + } | |
| 38 | + DeptRespDTO currentDept = dept; | |
| 39 | + for (int i = 1; i < level; i++) { | |
| 40 | + DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()); | |
| 41 | + if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人 | |
| 42 | + break; | |
| 43 | + } | |
| 44 | + currentDept = parentDept; | |
| 45 | + } | |
| 46 | + return currentDept.getLeaderUserId(); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 获得连续层级的部门负责人,包含 [1, level] 的负责人 | |
| 51 | + * | |
| 52 | + * @param deptIds 指定部门编号数组 | |
| 53 | + * @param level 最大层级 | |
| 54 | + * @return 连续部门负责人 Id | |
| 55 | + */ | |
| 56 | + protected Set<Long> getMultiLevelDeptLeaderIds(List<Long> deptIds, Integer level) { | |
| 57 | + Assert.isTrue(level > 0, "level 必须大于 0"); | |
| 58 | + if (CollUtil.isEmpty(deptIds)) { | |
| 59 | + return new HashSet<>(); | |
| 60 | + } | |
| 61 | + Set<Long> deptLeaderIds = new LinkedHashSet<>(); // 保证有序 | |
| 62 | + for (Long deptId : deptIds) { | |
| 63 | + DeptRespDTO dept = deptApi.getDept(deptId); | |
| 64 | + for (int i = 0; i < level; i++) { | |
| 65 | + if (dept.getLeaderUserId() != null) { | |
| 66 | + deptLeaderIds.add(dept.getLeaderUserId()); | |
| 67 | + } | |
| 68 | + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); | |
| 69 | + if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 | |
| 70 | + break; | |
| 71 | + } | |
| 72 | + dept = parentDept; | |
| 73 | + } | |
| 74 | + } | |
| 75 | + return deptLeaderIds; | |
| 76 | + } | |
| 77 | + | |
| 78 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java
0 → 100644
| 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/BpmTaskCandidateAssignEmptyStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | + | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; | |
| 5 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 7 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 8 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 9 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; | |
| 10 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | +import jakarta.annotation.Resource; | |
| 12 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 13 | +import org.springframework.context.annotation.Lazy; | |
| 14 | +import org.springframework.stereotype.Component; | |
| 15 | + | |
| 16 | +import java.util.HashSet; | |
| 17 | +import java.util.Objects; | |
| 18 | +import java.util.Set; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类 | |
| 22 | + * | |
| 23 | + * @author kyle | |
| 24 | + */ | |
| 25 | +@Component | |
| 26 | +public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 27 | + | |
| 28 | + @Resource | |
| 29 | + @Lazy // 延迟加载,避免循环依赖 | |
| 30 | + private BpmProcessDefinitionService processDefinitionService; | |
| 31 | + | |
| 32 | + public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) { | |
| 33 | + super(adminUserApi); | |
| 34 | + } | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 38 | + return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY; | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public void validateParam(String param) { | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 47 | + // 情况一:指定人员审批 | |
| 48 | + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement()); | |
| 49 | + if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { | |
| 50 | + Set<Long> users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement())); | |
| 51 | + removeDisableUsers(users); | |
| 52 | + return users; | |
| 53 | + } | |
| 54 | + | |
| 55 | + // 情况二:流程管理员 | |
| 56 | + if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { | |
| 57 | + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId()); | |
| 58 | + Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId()); | |
| 59 | + return new HashSet<>(processDefinition.getManagerUserIds()); | |
| 60 | + } | |
| 61 | + | |
| 62 | + // 都不满足,还是返回空 | |
| 63 | + return new HashSet<>(); | |
| 64 | + } | |
| 65 | + | |
| 66 | +} | |
| 0 | 67 | \ 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/BpmTaskCandidateDeptLeaderMultiStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | + | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.string.StrUtils; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 7 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 8 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 9 | +import org.springframework.stereotype.Component; | |
| 10 | + | |
| 11 | +import java.util.Set; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 | |
| 15 | + * | |
| 16 | + * @author jason | |
| 17 | + */ | |
| 18 | +@Component | |
| 19 | +public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { | |
| 20 | + | |
| 21 | + public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 22 | + super(adminUserApi, deptApi); | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 27 | + return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI; | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public void validateParam(String param) { | |
| 32 | + // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级 | |
| 33 | + String[] params = param.split("\\|"); | |
| 34 | + Assert.isTrue(params.length == 2, "参数格式不匹配"); | |
| 35 | + deptApi.validateDeptList(StrUtils.splitToLong(params[0], ",")); | |
| 36 | + Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0"); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public Set<Long> calculateUsers(String param) { | |
| 41 | + String[] params = param.split("\\|"); | |
| 42 | + return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1])); | |
| 43 | + } | |
| 44 | + | |
| 45 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java
| ... | ... | @@ -5,8 +5,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand |
| 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 jakarta.annotation.Resource; | |
| 9 | -import org.flowable.engine.delegate.DelegateExecution; | |
| 8 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 10 | 9 | import org.springframework.stereotype.Component; |
| 11 | 10 | |
| 12 | 11 | import java.util.List; |
| ... | ... | @@ -20,10 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 20 | 19 | * @author kyle |
| 21 | 20 | */ |
| 22 | 21 | @Component |
| 23 | -public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { | |
| 22 | +public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 24 | 23 | |
| 25 | - @Resource | |
| 26 | - private DeptApi deptApi; | |
| 24 | + private final DeptApi deptApi; | |
| 25 | + | |
| 26 | + public BpmTaskCandidateDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 27 | + super(adminUserApi); | |
| 28 | + this.deptApi = deptApi; | |
| 29 | + } | |
| 27 | 30 | |
| 28 | 31 | @Override |
| 29 | 32 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -37,7 +40,7 @@ public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrat |
| 37 | 40 | } |
| 38 | 41 | |
| 39 | 42 | @Override |
| 40 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 43 | + public Set<Long> calculateUsers(String param) { | |
| 41 | 44 | Set<Long> deptIds = StrUtils.splitToLongSet(param); |
| 42 | 45 | List<DeptRespDTO> depts = deptApi.getDeptList(deptIds); |
| 43 | 46 | return convertSet(depts, DeptRespDTO::getLeaderUserId); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java
| ... | ... | @@ -6,8 +6,6 @@ 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; | |
| 10 | -import org.flowable.engine.delegate.DelegateExecution; | |
| 11 | 9 | import org.springframework.stereotype.Component; |
| 12 | 10 | |
| 13 | 11 | import java.util.List; |
| ... | ... | @@ -21,12 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 21 | 19 | * @author kyle |
| 22 | 20 | */ |
| 23 | 21 | @Component |
| 24 | -public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { | |
| 22 | +public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 25 | 23 | |
| 26 | - @Resource | |
| 27 | - private DeptApi deptApi; | |
| 28 | - @Resource | |
| 29 | - private AdminUserApi adminUserApi; | |
| 24 | + private final DeptApi deptApi; | |
| 25 | + | |
| 26 | + public BpmTaskCandidateDeptMemberStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 27 | + super(adminUserApi); | |
| 28 | + this.deptApi = deptApi; | |
| 29 | + } | |
| 30 | 30 | |
| 31 | 31 | @Override |
| 32 | 32 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -40,7 +40,7 @@ public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrat |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | @Override |
| 43 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 43 | + public Set<Long> calculateUsers(String param) { | |
| 44 | 44 | Set<Long> deptIds = StrUtils.splitToLongSet(param); |
| 45 | 45 | List<AdminUserRespDTO> users = adminUserApi.getUserListByDeptIds(deptIds); |
| 46 | 46 | return convertSet(users, AdminUserRespDTO::getId); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java
| ... | ... | @@ -4,6 +4,7 @@ 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 | 8 | import org.flowable.engine.delegate.DelegateExecution; |
| 8 | 9 | import org.springframework.stereotype.Component; |
| 9 | 10 | |
| ... | ... | @@ -15,7 +16,11 @@ import java.util.Set; |
| 15 | 16 | * @author 芋道源码 |
| 16 | 17 | */ |
| 17 | 18 | @Component |
| 18 | -public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy { | |
| 19 | +public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 20 | + | |
| 21 | + public BpmTaskCandidateExpressionStrategy(AdminUserApi adminUserApi) { | |
| 22 | + super(adminUserApi); | |
| 23 | + } | |
| 19 | 24 | |
| 20 | 25 | @Override |
| 21 | 26 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -30,7 +35,9 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat |
| 30 | 35 | @Override |
| 31 | 36 | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| 32 | 37 | Object result = FlowableUtils.getExpressionValue(execution, param); |
| 33 | - return Convert.toSet(Long.class, result); | |
| 38 | + Set<Long> users = Convert.toSet(Long.class, result); | |
| 39 | + removeDisableUsers(users); | |
| 40 | + return users; | |
| 34 | 41 | } |
| 35 | 42 | |
| 36 | 43 | } |
| 37 | 44 | \ 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
| ... | ... | @@ -5,8 +5,7 @@ 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 jakarta.annotation.Resource; | |
| 9 | -import org.flowable.engine.delegate.DelegateExecution; | |
| 8 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 10 | 9 | import org.springframework.stereotype.Component; |
| 11 | 10 | |
| 12 | 11 | import java.util.Collection; |
| ... | ... | @@ -21,10 +20,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 21 | 20 | * @author kyle |
| 22 | 21 | */ |
| 23 | 22 | @Component |
| 24 | -public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { | |
| 23 | +public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 25 | 24 | |
| 26 | - @Resource | |
| 27 | - private BpmUserGroupService userGroupService; | |
| 25 | + private final BpmUserGroupService userGroupService; | |
| 26 | + | |
| 27 | + public BpmTaskCandidateGroupStrategy(AdminUserApi adminUserApi, BpmUserGroupService userGroupService) { | |
| 28 | + super(adminUserApi); | |
| 29 | + this.userGroupService = userGroupService; | |
| 30 | + } | |
| 28 | 31 | |
| 29 | 32 | @Override |
| 30 | 33 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -38,7 +41,7 @@ public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { |
| 38 | 41 | } |
| 39 | 42 | |
| 40 | 43 | @Override |
| 41 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 44 | + public Set<Long> calculateUsers(String param) { | |
| 42 | 45 | Set<Long> groupIds = StrUtils.splitToLongSet(param); |
| 43 | 46 | List<BpmUserGroupDO> groups = userGroupService.getUserGroupList(groupIds); |
| 44 | 47 | return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java
| ... | ... | @@ -6,8 +6,6 @@ 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; | |
| 10 | -import org.flowable.engine.delegate.DelegateExecution; | |
| 11 | 9 | import org.springframework.stereotype.Component; |
| 12 | 10 | |
| 13 | 11 | import java.util.List; |
| ... | ... | @@ -21,12 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 21 | 19 | * @author kyle |
| 22 | 20 | */ |
| 23 | 21 | @Component |
| 24 | -public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { | |
| 22 | +public class BpmTaskCandidatePostStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 25 | 23 | |
| 26 | - @Resource | |
| 27 | - private PostApi postApi; | |
| 28 | - @Resource | |
| 29 | - private AdminUserApi adminUserApi; | |
| 24 | + private final PostApi postApi; | |
| 25 | + | |
| 26 | + public BpmTaskCandidatePostStrategy(AdminUserApi adminUserApi, PostApi postApi) { | |
| 27 | + super(adminUserApi); | |
| 28 | + this.postApi = postApi; | |
| 29 | + } | |
| 30 | 30 | |
| 31 | 31 | @Override |
| 32 | 32 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -40,7 +40,7 @@ public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | @Override |
| 43 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 43 | + public Set<Long> calculateUsers(String param) { | |
| 44 | 44 | Set<Long> postIds = StrUtils.splitToLongSet(param); |
| 45 | 45 | List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds); |
| 46 | 46 | return convertSet(users, AdminUserRespDTO::getId); | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java
| ... | ... | @@ -5,8 +5,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand |
| 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; | |
| 8 | 9 | import jakarta.annotation.Resource; |
| 9 | -import org.flowable.engine.delegate.DelegateExecution; | |
| 10 | 10 | import org.springframework.stereotype.Component; |
| 11 | 11 | |
| 12 | 12 | import java.util.Set; |
| ... | ... | @@ -17,13 +17,17 @@ import java.util.Set; |
| 17 | 17 | * @author kyle |
| 18 | 18 | */ |
| 19 | 19 | @Component |
| 20 | -public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { | |
| 20 | +public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 21 | 21 | |
| 22 | 22 | @Resource |
| 23 | 23 | private RoleApi roleApi; |
| 24 | 24 | @Resource |
| 25 | 25 | private PermissionApi permissionApi; |
| 26 | 26 | |
| 27 | + public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) { | |
| 28 | + super(adminUserApi); | |
| 29 | + } | |
| 30 | + | |
| 27 | 31 | @Override |
| 28 | 32 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 29 | 33 | return BpmTaskCandidateStrategyEnum.ROLE; |
| ... | ... | @@ -36,7 +40,7 @@ public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { |
| 36 | 40 | } |
| 37 | 41 | |
| 38 | 42 | @Override |
| 39 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 43 | + public Set<Long> calculateUsers(String param) { | |
| 40 | 44 | Set<Long> roleIds = StrUtils.splitToLongSet(param); |
| 41 | 45 | return permissionApi.getUserRoleIdListByRoleIds(roleIds); |
| 42 | 46 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | + | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 7 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 8 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 9 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 10 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 12 | +import jakarta.annotation.Resource; | |
| 13 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 14 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 15 | +import org.springframework.context.annotation.Lazy; | |
| 16 | +import org.springframework.stereotype.Component; | |
| 17 | + | |
| 18 | +import java.util.HashSet; | |
| 19 | +import java.util.Set; | |
| 20 | + | |
| 21 | +import static cn.hutool.core.collection.ListUtil.toList; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 | |
| 25 | + * | |
| 26 | + * @author jason | |
| 27 | + */ | |
| 28 | +@Component | |
| 29 | +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { | |
| 30 | + | |
| 31 | + @Resource | |
| 32 | + @Lazy | |
| 33 | + private BpmProcessInstanceService processInstanceService; | |
| 34 | + | |
| 35 | + public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 36 | + super(adminUserApi, deptApi); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 41 | + return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI; | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public void validateParam(String param) { | |
| 46 | + // 参数是部门的层级 | |
| 47 | + Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 52 | + // 获得流程发起人 | |
| 53 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); | |
| 54 | + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); | |
| 55 | + // 获取发起人的 multi 部门负责人 | |
| 56 | + DeptRespDTO dept = getStartUserDept(startUserId); | |
| 57 | + if (dept == null) { | |
| 58 | + return new HashSet<>(); | |
| 59 | + } | |
| 60 | + Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级 | |
| 61 | + // TODO @jason:这里 removeDisableUsers 的原因是啥呀? | |
| 62 | + removeDisableUsers(users); | |
| 63 | + return users; | |
| 64 | + } | |
| 65 | + | |
| 66 | + @Override | |
| 67 | + public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 68 | + DeptRespDTO dept = getStartUserDept(startUserId); | |
| 69 | + if (dept == null) { | |
| 70 | + return new HashSet<>(); | |
| 71 | + } | |
| 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()); | |
| 88 | + } | |
| 89 | + | |
| 90 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | + | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | |
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 7 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 8 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 9 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 10 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 12 | +import jakarta.annotation.Resource; | |
| 13 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 14 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 15 | +import org.springframework.context.annotation.Lazy; | |
| 16 | +import org.springframework.stereotype.Component; | |
| 17 | + | |
| 18 | +import java.util.HashSet; | |
| 19 | +import java.util.Set; | |
| 20 | + | |
| 21 | +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类 | |
| 25 | + * | |
| 26 | + * @author jason | |
| 27 | + */ | |
| 28 | +@Component | |
| 29 | +public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy { | |
| 30 | + | |
| 31 | + @Resource | |
| 32 | + @Lazy // 避免循环依赖 | |
| 33 | + private BpmProcessInstanceService processInstanceService; | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 37 | + return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; | |
| 38 | + } | |
| 39 | + | |
| 40 | + public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) { | |
| 41 | + super(adminUserApi, deptApi); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public void validateParam(String param) { | |
| 46 | + // 参数是部门的层级 | |
| 47 | + Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 52 | + // 获得流程发起人 | |
| 53 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); | |
| 54 | + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); | |
| 55 | + // 获取发起人的部门负责人 | |
| 56 | + Set<Long> users = getStartUserDeptLeader(startUserId, param); | |
| 57 | + removeDisableUsers(users); | |
| 58 | + return users; | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 63 | + // 获取发起人的部门负责人 | |
| 64 | + Set<Long> users = getStartUserDeptLeader(startUserId, param); | |
| 65 | + removeDisableUsers(users); | |
| 66 | + return users; | |
| 67 | + } | |
| 68 | + | |
| 69 | + private Set<Long> getStartUserDeptLeader(Long startUserId, String param) { | |
| 70 | + DeptRespDTO dept = getStartUserDept(startUserId); | |
| 71 | + if (dept == null) { | |
| 72 | + return new HashSet<>(); | |
| 73 | + } | |
| 74 | + Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级 | |
| 75 | + return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); | |
| 76 | + } | |
| 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 | + | |
| 91 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java
| ... | ... | @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.lang.Assert; |
| 5 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | |
| 6 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 7 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 8 | 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 9 | 8 | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; |
| 9 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 10 | 10 | import jakarta.annotation.Resource; |
| 11 | 11 | import org.flowable.bpmn.model.BpmnModel; |
| 12 | 12 | import org.flowable.bpmn.model.UserTask; |
| ... | ... | @@ -23,12 +23,16 @@ import java.util.*; |
| 23 | 23 | * @author 芋道源码 |
| 24 | 24 | */ |
| 25 | 25 | @Component |
| 26 | -public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidateStrategy { | |
| 26 | +public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 27 | 27 | |
| 28 | 28 | @Resource |
| 29 | 29 | @Lazy // 延迟加载,避免循环依赖 |
| 30 | 30 | private BpmProcessInstanceService processInstanceService; |
| 31 | 31 | |
| 32 | + public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) { | |
| 33 | + super(adminUserApi); | |
| 34 | + } | |
| 35 | + | |
| 32 | 36 | @Override |
| 33 | 37 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| 34 | 38 | return BpmTaskCandidateStrategyEnum.START_USER_SELECT; |
| ... | ... | @@ -46,7 +50,23 @@ public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidate |
| 46 | 50 | execution.getProcessInstanceId()); |
| 47 | 51 | // 获得审批人 |
| 48 | 52 | List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); |
| 49 | - return new LinkedHashSet<>(assignees); | |
| 53 | + Set<Long> users = new LinkedHashSet<>(assignees); | |
| 54 | + removeDisableUsers(users); | |
| 55 | + return users; | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Override | |
| 59 | + public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) { | |
| 60 | + if (processInstance == null) { | |
| 61 | + return Collections.emptySet(); | |
| 62 | + } | |
| 63 | + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); | |
| 64 | + Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId()); | |
| 65 | + // 获得审批人 | |
| 66 | + List<Long> assignees = startUserSelectAssignees.get(activityId); | |
| 67 | + Set<Long> users = new LinkedHashSet<>(assignees); | |
| 68 | + removeDisableUsers(users); | |
| 69 | + return users; | |
| 50 | 70 | } |
| 51 | 71 | |
| 52 | 72 | @Override | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | |
| 4 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
| 5 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | |
| 6 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 7 | +import jakarta.annotation.Resource; | |
| 8 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 9 | +import org.flowable.engine.runtime.ProcessInstance; | |
| 10 | +import org.springframework.context.annotation.Lazy; | |
| 11 | +import org.springframework.stereotype.Component; | |
| 12 | + | |
| 13 | +import java.util.Set; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类 | |
| 17 | + * <p> | |
| 18 | + * 适合场景:用于需要发起人信息复核等场景 | |
| 19 | + * | |
| 20 | + * @author jason | |
| 21 | + */ | |
| 22 | +@Component | |
| 23 | +public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 24 | + | |
| 25 | + @Resource | |
| 26 | + @Lazy // 延迟加载,避免循环依赖 | |
| 27 | + private BpmProcessInstanceService processInstanceService; | |
| 28 | + | |
| 29 | + public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) { | |
| 30 | + super(adminUserApi); | |
| 31 | + } | |
| 32 | + | |
| 33 | + @Override | |
| 34 | + public BpmTaskCandidateStrategyEnum getStrategy() { | |
| 35 | + return BpmTaskCandidateStrategyEnum.START_USER; | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public void validateParam(String param) { | |
| 40 | + } | |
| 41 | + | |
| 42 | + @Override | |
| 43 | + public boolean isParamRequired() { | |
| 44 | + return false; | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 49 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); | |
| 50 | + Set<Long> users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); | |
| 51 | + removeDisableUsers(users); | |
| 52 | + return users; | |
| 53 | + } | |
| 54 | + | |
| 55 | + @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; | |
| 60 | + } | |
| 61 | + | |
| 62 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.text.StrPool; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 4 | 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 5 | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 6 | 7 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 7 | -import jakarta.annotation.Resource; | |
| 8 | -import org.flowable.engine.delegate.DelegateExecution; | |
| 9 | 8 | import org.springframework.stereotype.Component; |
| 10 | 9 | |
| 10 | +import java.util.LinkedHashSet; | |
| 11 | 11 | import java.util.Set; |
| 12 | 12 | |
| 13 | 13 | /** |
| ... | ... | @@ -16,10 +16,11 @@ import java.util.Set; |
| 16 | 16 | * @author kyle |
| 17 | 17 | */ |
| 18 | 18 | @Component |
| 19 | -public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { | |
| 19 | +public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrategy { | |
| 20 | 20 | |
| 21 | - @Resource | |
| 22 | - private AdminUserApi adminUserApi; | |
| 21 | + public BpmTaskCandidateUserStrategy(AdminUserApi adminUserApi) { | |
| 22 | + super(adminUserApi); | |
| 23 | + } | |
| 23 | 24 | |
| 24 | 25 | @Override |
| 25 | 26 | public BpmTaskCandidateStrategyEnum getStrategy() { |
| ... | ... | @@ -32,8 +33,8 @@ public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { |
| 32 | 33 | } |
| 33 | 34 | |
| 34 | 35 | @Override |
| 35 | - public Set<Long> calculateUsers(DelegateExecution execution, String param) { | |
| 36 | - return StrUtils.splitToLongSet(param); | |
| 36 | + public Set<Long> calculateUsers(String param) { | |
| 37 | + return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); | |
| 37 | 38 | } |
| 38 | 39 | |
| 39 | 40 | } |
| 40 | 41 | \ No newline at end of file | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.el; | |
| 2 | + | |
| 3 | +import org.flowable.common.engine.api.variable.VariableContainer; | |
| 4 | +import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction; | |
| 5 | +import org.springframework.stereotype.Component; | |
| 6 | + | |
| 7 | +// TODO @jason:这个自定义转换的原因是啥呀? | |
| 8 | +/** | |
| 9 | + * 根据流程变量 variable 的类型, 转换参数的值 | |
| 10 | + * | |
| 11 | + * @author jason | |
| 12 | + */ | |
| 13 | +@Component | |
| 14 | +public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction { | |
| 15 | + | |
| 16 | + public VariableConvertByTypeExpressionFunction() { | |
| 17 | + super("convertByType"); | |
| 18 | + } | |
| 19 | + | |
| 20 | + public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) { | |
| 21 | + Object variable = variableContainer.getVariable(variableName); | |
| 22 | + if (variable != null && parmaValue != null) { | |
| 23 | + // 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串 | |
| 24 | + if (!(parmaValue instanceof String) && variable instanceof String ) { | |
| 25 | + return parmaValue.toString(); | |
| 26 | + } | |
| 27 | + } | |
| 28 | + return parmaValue; | |
| 29 | + } | |
| 30 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.ArrayUtil; |
| 4 | +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | |
| 4 | 5 | import lombok.AllArgsConstructor; |
| 5 | 6 | import lombok.Getter; |
| 6 | 7 | |
| 8 | +import java.util.Arrays; | |
| 9 | + | |
| 7 | 10 | /** |
| 8 | 11 | * BPM 任务的候选人策略枚举 |
| 9 | 12 | * |
| ... | ... | @@ -13,18 +16,25 @@ import lombok.Getter; |
| 13 | 16 | */ |
| 14 | 17 | @Getter |
| 15 | 18 | @AllArgsConstructor |
| 16 | -public enum BpmTaskCandidateStrategyEnum { | |
| 19 | +public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { | |
| 17 | 20 | |
| 18 | 21 | ROLE(10, "角色"), |
| 19 | 22 | DEPT_MEMBER(20, "部门的成员"), // 包括负责人 |
| 20 | 23 | DEPT_LEADER(21, "部门的负责人"), |
| 24 | + MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), | |
| 21 | 25 | POST(22, "岗位"), |
| 22 | 26 | USER(30, "用户"), |
| 23 | 27 | START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 |
| 28 | + START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 | |
| 29 | + START_USER_DEPT_LEADER(37, "发起人部门负责人"), | |
| 30 | + START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), | |
| 24 | 31 | USER_GROUP(40, "用户组"), |
| 25 | 32 | EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager |
| 33 | + ASSIGN_EMPTY(1, "审批人为空"), | |
| 26 | 34 | ; |
| 27 | 35 | |
| 36 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray(); | |
| 37 | + | |
| 28 | 38 | /** |
| 29 | 39 | * 类型 |
| 30 | 40 | */ |
| ... | ... | @@ -38,4 +48,9 @@ public enum BpmTaskCandidateStrategyEnum { |
| 38 | 48 | return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values()); |
| 39 | 49 | } |
| 40 | 50 | |
| 51 | + @Override | |
| 52 | + public int[] array() { | |
| 53 | + return ARRAYS; | |
| 54 | + } | |
| 55 | + | |
| 41 | 56 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
| ... | ... | @@ -23,4 +23,95 @@ public interface BpmnModelConstants { |
| 23 | 23 | */ |
| 24 | 24 | String USER_TASK_CANDIDATE_PARAM = "candidateParam"; |
| 25 | 25 | |
| 26 | + /** | |
| 27 | + * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型 | |
| 28 | + */ | |
| 29 | + String BOUNDARY_EVENT_TYPE = "boundaryEventType"; | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 | |
| 33 | + */ | |
| 34 | + String USER_TASK_TIMEOUT_HANDLER_TYPE = "timeoutHandlerType"; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 | |
| 38 | + */ | |
| 39 | + String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型 | |
| 43 | + */ | |
| 44 | + String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType"; | |
| 45 | + /** | |
| 46 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组 | |
| 47 | + */ | |
| 48 | + String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds"; | |
| 49 | + | |
| 50 | + /** | |
| 51 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 | |
| 52 | + */ | |
| 53 | + String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; | |
| 54 | + /** | |
| 55 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id | |
| 56 | + */ | |
| 57 | + String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; | |
| 58 | + | |
| 59 | + /** | |
| 60 | + * BPMN UserTask 的扩展属性,用于标记用户任务的审批类型 | |
| 61 | + */ | |
| 62 | + String USER_TASK_APPROVE_TYPE = "approveType"; | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式 | |
| 66 | + */ | |
| 67 | + String USER_TASK_APPROVE_METHOD = "approveMethod"; | |
| 68 | + | |
| 69 | + /** | |
| 70 | + * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 | |
| 71 | + */ | |
| 72 | + String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * BPMN ExtensionElement Attribute, 用于标记表单字段 | |
| 76 | + */ | |
| 77 | + String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field"; | |
| 78 | + /** | |
| 79 | + * BPMN ExtensionElement Attribute, 用于标记表单权限 | |
| 80 | + */ | |
| 81 | + String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; | |
| 82 | + | |
| 83 | + /** | |
| 84 | + * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 | |
| 85 | + */ | |
| 86 | + String BUTTON_SETTING_ELEMENT = "buttonsSetting"; | |
| 87 | + | |
| 88 | + /** | |
| 89 | + * BPMN ExtensionElement Attribute, 用于标记按钮编号 | |
| 90 | + */ | |
| 91 | + String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id"; | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * BPMN ExtensionElement Attribute, 用于标记按钮显示名称 | |
| 95 | + */ | |
| 96 | + String BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE = "displayName"; | |
| 97 | + | |
| 98 | + /** | |
| 99 | + * BPMN ExtensionElement Attribute, 用于标记按钮是否启用 | |
| 100 | + */ | |
| 101 | + String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; | |
| 102 | + | |
| 103 | + /** | |
| 104 | + * BPMN Start Event Node Id | |
| 105 | + */ | |
| 106 | + String START_EVENT_NODE_ID = "StartEvent"; | |
| 107 | + /** | |
| 108 | + * BPMN Start Event Node Name | |
| 109 | + */ | |
| 110 | + String START_EVENT_NODE_NAME = "开始"; | |
| 111 | + | |
| 112 | + /** | |
| 113 | + * 发起人节点 ID | |
| 114 | + */ | |
| 115 | + String START_USER_NODE_ID = "StartUserNode"; | |
| 116 | + | |
| 26 | 117 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
| ... | ... | @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; |
| 3 | 3 | import org.flowable.engine.runtime.ProcessInstance; |
| 4 | 4 | |
| 5 | 5 | /** |
| 6 | - * BPM 通用常量 | |
| 6 | + * BPM Variable 通用常量 | |
| 7 | 7 | * |
| 8 | 8 | * @author 芋道源码 |
| 9 | 9 | */ |
| 10 | -public class BpmConstants { | |
| 10 | +public class BpmnVariableConstants { | |
| 11 | 11 | |
| 12 | 12 | /** |
| 13 | 13 | * 流程实例的变量 - 状态 |
| ... | ... | @@ -16,6 +16,14 @@ public class BpmConstants { |
| 16 | 16 | */ |
| 17 | 17 | public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; |
| 18 | 18 | /** |
| 19 | + * 流程实例的变量 - 理由 | |
| 20 | + * | |
| 21 | + * 例如说:审批不通过的理由(目前审核通过暂时不会记录) | |
| 22 | + * | |
| 23 | + * @see ProcessInstance#getProcessVariables() | |
| 24 | + */ | |
| 25 | + public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON"; | |
| 26 | + /** | |
| 19 | 27 | * 流程实例的变量 - 发起用户选择的审批人 Map |
| 20 | 28 | * |
| 21 | 29 | * @see ProcessInstance#getProcessVariables() |
| ... | ... | @@ -23,6 +31,15 @@ public class BpmConstants { |
| 23 | 31 | public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; |
| 24 | 32 | |
| 25 | 33 | /** |
| 34 | + * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} | |
| 35 | + * | |
| 36 | + * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 | |
| 37 | + * | |
| 38 | + * @see ProcessInstance#getProcessVariables() | |
| 39 | + */ | |
| 40 | + public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; | |
| 41 | + | |
| 42 | + /** | |
| 26 | 43 | * 任务的变量 - 状态 |
| 27 | 44 | * |
| 28 | 45 | * @see org.flowable.task.api.Task#getTaskLocalVariables() | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java
0 → 100644
| 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
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; | |
| 5 | +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService; | |
| 6 | +import jakarta.annotation.Resource; | |
| 7 | +import org.flowable.bpmn.model.FlowElement; | |
| 8 | +import org.flowable.engine.delegate.DelegateExecution; | |
| 9 | +import org.flowable.engine.delegate.JavaDelegate; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +import java.util.Set; | |
| 13 | + | |
| 14 | +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate.BEAN_NAME; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 处理抄送用户的 {@link JavaDelegate} 的实现类 | |
| 18 | + * | |
| 19 | + * 目前只有快搭模式的【抄送节点】使用 | |
| 20 | + * | |
| 21 | + * @author jason | |
| 22 | + */ | |
| 23 | +@Component(BEAN_NAME) | |
| 24 | +public class BpmCopyTaskDelegate implements JavaDelegate { | |
| 25 | + | |
| 26 | + public static final String BEAN_NAME = "bpmCopyTaskDelegate"; | |
| 27 | + | |
| 28 | + @Resource | |
| 29 | + private BpmTaskCandidateInvoker taskCandidateInvoker; | |
| 30 | + | |
| 31 | + @Resource | |
| 32 | + private BpmProcessInstanceCopyService processInstanceCopyService; | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + public void execute(DelegateExecution execution) { | |
| 36 | + // 1. 获得抄送人 | |
| 37 | + Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution); | |
| 38 | + if (CollUtil.isEmpty(userIds)) { | |
| 39 | + return; | |
| 40 | + } | |
| 41 | + // 2. 执行抄送 | |
| 42 | + FlowElement currentFlowElement = execution.getCurrentFlowElement(); | |
| 43 | + processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), | |
| 44 | + currentFlowElement.getId(), null, currentFlowElement.getName()); | |
| 45 | + } | |
| 46 | + | |
| 47 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
| ... | ... | @@ -6,7 +6,6 @@ import jakarta.annotation.Resource; |
| 6 | 6 | import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; |
| 7 | 7 | import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; |
| 8 | 8 | import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; |
| 9 | -import org.flowable.engine.delegate.event.FlowableCancelledEvent; | |
| 10 | 9 | import org.flowable.engine.runtime.ProcessInstance; |
| 11 | 10 | import org.springframework.context.annotation.Lazy; |
| 12 | 11 | import org.springframework.stereotype.Component; |
| ... | ... | @@ -21,27 +20,21 @@ import java.util.Set; |
| 21 | 20 | @Component |
| 22 | 21 | public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { |
| 23 | 22 | |
| 23 | + public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() | |
| 24 | + .add(FlowableEngineEventType.PROCESS_COMPLETED) | |
| 25 | + .build(); | |
| 26 | + | |
| 24 | 27 | @Resource |
| 25 | - @Lazy | |
| 28 | + @Lazy // 延迟加载,避免循环依赖 | |
| 26 | 29 | private BpmProcessInstanceService processInstanceService; |
| 27 | 30 | |
| 28 | - public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() | |
| 29 | - .add(FlowableEngineEventType.PROCESS_CANCELLED) | |
| 30 | - .add(FlowableEngineEventType.PROCESS_COMPLETED) | |
| 31 | - .build(); | |
| 32 | - | |
| 33 | 31 | public BpmProcessInstanceEventListener(){ |
| 34 | 32 | super(PROCESS_INSTANCE_EVENTS); |
| 35 | 33 | } |
| 36 | 34 | |
| 37 | 35 | @Override |
| 38 | - protected void processCancelled(FlowableCancelledEvent event) { | |
| 39 | - processInstanceService.updateProcessInstanceWhenCancel(event); | |
| 40 | - } | |
| 41 | - | |
| 42 | - @Override | |
| 43 | 36 | protected void processCompleted(FlowableEngineEntityEvent event) { |
| 44 | - processInstanceService.updateProcessInstanceWhenApprove((ProcessInstance)event.getEntity()); | |
| 37 | + processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); | |
| 45 | 38 | } |
| 46 | 39 | |
| 47 | 40 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.ObjectUtil; | |
| 4 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | |
| 7 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | |
| 8 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | |
| 9 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 10 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | |
| 5 | 11 | import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; |
| 6 | 12 | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |
| 7 | 13 | import com.google.common.collect.ImmutableSet; |
| 14 | +import jakarta.annotation.Resource; | |
| 8 | 15 | import lombok.extern.slf4j.Slf4j; |
| 16 | +import org.flowable.bpmn.model.BoundaryEvent; | |
| 17 | +import org.flowable.bpmn.model.BpmnModel; | |
| 18 | +import org.flowable.bpmn.model.FlowElement; | |
| 9 | 19 | import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; |
| 10 | 20 | import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; |
| 11 | 21 | import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; |
| 12 | 22 | import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; |
| 13 | 23 | import org.flowable.engine.history.HistoricActivityInstance; |
| 24 | +import org.flowable.job.api.Job; | |
| 14 | 25 | import org.flowable.task.api.Task; |
| 15 | 26 | import org.springframework.context.annotation.Lazy; |
| 16 | 27 | import org.springframework.stereotype.Component; |
| 17 | 28 | |
| 18 | -import jakarta.annotation.Resource; | |
| 19 | 29 | import java.util.List; |
| 20 | 30 | import java.util.Set; |
| 21 | 31 | |
| ... | ... | @@ -29,6 +39,9 @@ import java.util.Set; |
| 29 | 39 | public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 30 | 40 | |
| 31 | 41 | @Resource |
| 42 | + @Lazy // 延迟加载,避免循环依赖 | |
| 43 | + private BpmModelService modelService; | |
| 44 | + @Resource | |
| 32 | 45 | @Lazy // 解决循环依赖 |
| 33 | 46 | private BpmTaskService taskService; |
| 34 | 47 | @Resource |
| ... | ... | @@ -40,20 +53,21 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 40 | 53 | .add(FlowableEngineEventType.TASK_ASSIGNED) |
| 41 | 54 | // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 |
| 42 | 55 | .add(FlowableEngineEventType.ACTIVITY_CANCELLED) |
| 56 | + .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 | |
| 43 | 57 | .build(); |
| 44 | 58 | |
| 45 | - public BpmTaskEventListener(){ | |
| 59 | + public BpmTaskEventListener() { | |
| 46 | 60 | super(TASK_EVENTS); |
| 47 | 61 | } |
| 48 | 62 | |
| 49 | 63 | @Override |
| 50 | 64 | protected void taskCreated(FlowableEngineEntityEvent event) { |
| 51 | - taskService.updateTaskStatusWhenCreated((Task) event.getEntity()); | |
| 65 | + taskService.processTaskCreated((Task) event.getEntity()); | |
| 52 | 66 | } |
| 53 | 67 | |
| 54 | 68 | @Override |
| 55 | 69 | protected void taskAssigned(FlowableEngineEntityEvent event) { |
| 56 | - taskService.updateTaskExtAssign((Task)event.getEntity()); | |
| 70 | + taskService.processTaskAssigned((Task) event.getEntity()); | |
| 57 | 71 | } |
| 58 | 72 | |
| 59 | 73 | @Override |
| ... | ... | @@ -68,8 +82,34 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| 68 | 82 | if (StrUtil.isEmpty(activity.getTaskId())) { |
| 69 | 83 | return; |
| 70 | 84 | } |
| 71 | - taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); | |
| 85 | + taskService.processTaskCanceled(activity.getTaskId()); | |
| 72 | 86 | }); |
| 73 | 87 | } |
| 74 | 88 | |
| 89 | + @Override | |
| 90 | + protected void timerFired(FlowableEngineEntityEvent event) { | |
| 91 | + // 1.1 只处理 BoundaryEvent 边界计时时间 | |
| 92 | + String processDefinitionId = event.getProcessDefinitionId(); | |
| 93 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 94 | + Job entity = (Job) event.getEntity(); | |
| 95 | + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); | |
| 96 | + if (!(element instanceof BoundaryEvent)) { | |
| 97 | + return; | |
| 98 | + } | |
| 99 | + // 1.2 判断是否为超时处理 | |
| 100 | + BoundaryEvent boundaryEvent = (BoundaryEvent) element; | |
| 101 | + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, | |
| 102 | + BpmnModelConstants.BOUNDARY_EVENT_TYPE); | |
| 103 | + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); | |
| 104 | + if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) { | |
| 105 | + return; | |
| 106 | + } | |
| 107 | + | |
| 108 | + // 2. 处理超时 | |
| 109 | + String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, | |
| 110 | + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE); | |
| 111 | + String taskKey = boundaryEvent.getAttachedToRefId(); | |
| 112 | + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType)); | |
| 113 | + } | |
| 114 | + | |
| 75 | 115 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.map.MapUtil; | |
| 4 | 5 | import cn.hutool.core.util.ArrayUtil; |
| 5 | 6 | import cn.hutool.core.util.StrUtil; |
| 6 | 7 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 8 | +import cn.iocoder.yudao.framework.common.util.string.StrUtils; | |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | |
| 10 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; | |
| 7 | 11 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 8 | 12 | import org.flowable.bpmn.converter.BpmnXMLConverter; |
| 9 | 13 | import org.flowable.bpmn.model.Process; |
| ... | ... | @@ -12,19 +16,111 @@ import org.flowable.common.engine.impl.util.io.BytesStreamSource; |
| 12 | 16 | |
| 13 | 17 | import java.util.*; |
| 14 | 18 | |
| 19 | +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; | |
| 20 | +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; | |
| 21 | + | |
| 15 | 22 | /** |
| 16 | 23 | * 流程模型转操作工具类 |
| 17 | 24 | */ |
| 18 | 25 | public class BpmnModelUtils { |
| 19 | 26 | |
| 20 | 27 | public static Integer parseCandidateStrategy(FlowElement userTask) { |
| 21 | - return NumberUtils.parseInt(userTask.getAttributeValue( | |
| 28 | + Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( | |
| 22 | 29 | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); |
| 30 | + // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 | |
| 31 | + if (candidateStrategy == null) { | |
| 32 | + ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); | |
| 33 | + candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null; | |
| 34 | + } | |
| 35 | + return candidateStrategy; | |
| 23 | 36 | } |
| 24 | 37 | |
| 25 | 38 | public static String parseCandidateParam(FlowElement userTask) { |
| 26 | - return userTask.getAttributeValue( | |
| 39 | + String candidateParam = userTask.getAttributeValue( | |
| 27 | 40 | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); |
| 41 | + if (candidateParam == null) { | |
| 42 | + ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); | |
| 43 | + candidateParam = element != null ? element.getElementText() : null; | |
| 44 | + } | |
| 45 | + return candidateParam; | |
| 46 | + } | |
| 47 | + | |
| 48 | + public static Integer parseApproveType(FlowElement userTask) { | |
| 49 | + return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE)); | |
| 50 | + } | |
| 51 | + | |
| 52 | + public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { | |
| 53 | + Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); | |
| 54 | + return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); | |
| 55 | + } | |
| 56 | + | |
| 57 | + public static String parseReturnTaskId(FlowElement flowElement) { | |
| 58 | + return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); | |
| 59 | + } | |
| 60 | + | |
| 61 | + public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { | |
| 62 | + return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); | |
| 63 | + } | |
| 64 | + | |
| 65 | + public static Integer parseAssignEmptyHandlerType(FlowElement userTask) { | |
| 66 | + return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE)); | |
| 67 | + } | |
| 68 | + | |
| 69 | + public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) { | |
| 70 | + return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); | |
| 71 | + } | |
| 72 | + | |
| 73 | + public static String parseExtensionElement(FlowElement flowElement, String elementName) { | |
| 74 | + if (flowElement == null) { | |
| 75 | + return null; | |
| 76 | + } | |
| 77 | + ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); | |
| 78 | + return element != null ? element.getElementText() : null; | |
| 79 | + } | |
| 80 | + | |
| 81 | + public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { | |
| 82 | + if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { | |
| 83 | + return null; | |
| 84 | + } | |
| 85 | + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); | |
| 86 | + if (flowElement == null) { | |
| 87 | + return null; | |
| 88 | + } | |
| 89 | + List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); | |
| 90 | + if (CollUtil.isEmpty(extensionElements)) { | |
| 91 | + return null; | |
| 92 | + } | |
| 93 | + Map<String, String> fieldsPermission = MapUtil.newHashMap(); | |
| 94 | + extensionElements.forEach(element -> { | |
| 95 | + String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); | |
| 96 | + String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); | |
| 97 | + if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { | |
| 98 | + fieldsPermission.put(field, permission); | |
| 99 | + } | |
| 100 | + }); | |
| 101 | + return fieldsPermission; | |
| 102 | + } | |
| 103 | + | |
| 104 | + public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { | |
| 105 | + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); | |
| 106 | + if (flowElement == null) { | |
| 107 | + return null; | |
| 108 | + } | |
| 109 | + List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT); | |
| 110 | + if (CollUtil.isEmpty(extensionElements)) { | |
| 111 | + return null; | |
| 112 | + } | |
| 113 | + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = MapUtil.newHashMap(16); | |
| 114 | + extensionElements.forEach(element -> { | |
| 115 | + String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); | |
| 116 | + String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); | |
| 117 | + String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE); | |
| 118 | + if (StrUtil.isNotEmpty(id)) { | |
| 119 | + BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting(); | |
| 120 | + buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable))); | |
| 121 | + } | |
| 122 | + }); | |
| 123 | + return buttonSettings; | |
| 28 | 124 | } |
| 29 | 125 | |
| 30 | 126 | /** |
| ... | ... | @@ -95,6 +191,12 @@ public class BpmnModelUtils { |
| 95 | 191 | return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent); |
| 96 | 192 | } |
| 97 | 193 | |
| 194 | + public static EndEvent getEndEvent(BpmnModel model) { | |
| 195 | + Process process = model.getMainProcess(); | |
| 196 | + // 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题 | |
| 197 | + return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent); | |
| 198 | + } | |
| 199 | + | |
| 98 | 200 | public static BpmnModel getBpmnModel(byte[] bpmnBytes) { |
| 99 | 201 | if (ArrayUtil.isEmpty(bpmnBytes)) { |
| 100 | 202 | return null; |
| ... | ... | @@ -334,4 +436,11 @@ public class BpmnModelUtils { |
| 334 | 436 | return userTaskList; |
| 335 | 437 | } |
| 336 | 438 | |
| 439 | + public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { | |
| 440 | + if (boundaryEvent == null) { | |
| 441 | + return null; | |
| 442 | + } | |
| 443 | + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement)); | |
| 444 | + return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null); | |
| 445 | + } | |
| 337 | 446 | } | ... | ... |
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 | +import cn.hutool.core.util.ObjectUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 4 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; | |
| 5 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 6 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; | |
| 5 | 7 | import org.flowable.common.engine.api.delegate.Expression; |
| 6 | 8 | import org.flowable.common.engine.api.variable.VariableContainer; |
| 7 | 9 | import org.flowable.common.engine.impl.el.ExpressionManager; |
| ... | ... | @@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo; |
| 16 | 18 | import java.util.HashMap; |
| 17 | 19 | import java.util.List; |
| 18 | 20 | import java.util.Map; |
| 21 | +import java.util.Objects; | |
| 19 | 22 | |
| 20 | 23 | /** |
| 21 | 24 | * Flowable 相关的工具方法 |
| ... | ... | @@ -39,6 +42,16 @@ public class FlowableUtils { |
| 39 | 42 | return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; |
| 40 | 43 | } |
| 41 | 44 | |
| 45 | + public static void execute(String tenantIdStr, Runnable runnable) { | |
| 46 | + if (ObjectUtil.isEmpty(tenantIdStr) | |
| 47 | + || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { | |
| 48 | + runnable.run(); | |
| 49 | + } else { | |
| 50 | + Long tenantId = Long.valueOf(tenantIdStr); | |
| 51 | + TenantUtils.execute(tenantId, runnable); | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 42 | 55 | // ========== Execution 相关的工具方法 ========== |
| 43 | 56 | |
| 44 | 57 | /** |
| ... | ... | @@ -78,7 +91,7 @@ public class FlowableUtils { |
| 78 | 91 | * @return 状态 |
| 79 | 92 | */ |
| 80 | 93 | private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) { |
| 81 | - return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); | |
| 94 | + return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); | |
| 82 | 95 | } |
| 83 | 96 | |
| 84 | 97 | /** |
| ... | ... | @@ -102,7 +115,7 @@ public class FlowableUtils { |
| 102 | 115 | * @return 过滤后的表单 |
| 103 | 116 | */ |
| 104 | 117 | public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) { |
| 105 | - processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); | |
| 118 | + processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); | |
| 106 | 119 | return processVariables; |
| 107 | 120 | } |
| 108 | 121 | |
| ... | ... | @@ -115,7 +128,7 @@ public class FlowableUtils { |
| 115 | 128 | @SuppressWarnings("unchecked") |
| 116 | 129 | public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) { |
| 117 | 130 | return (Map<String, List<Long>>) processInstance.getProcessVariables().get( |
| 118 | - BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); | |
| 131 | + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); | |
| 119 | 132 | } |
| 120 | 133 | |
| 121 | 134 | // ========== Task 相关的工具方法 ========== |
| ... | ... | @@ -127,7 +140,7 @@ public class FlowableUtils { |
| 127 | 140 | * @return 状态 |
| 128 | 141 | */ |
| 129 | 142 | public static Integer getTaskStatus(TaskInfo task) { |
| 130 | - return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); | |
| 143 | + return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); | |
| 131 | 144 | } |
| 132 | 145 | |
| 133 | 146 | /** |
| ... | ... | @@ -137,7 +150,7 @@ public class FlowableUtils { |
| 137 | 150 | * @return 审批原因 |
| 138 | 151 | */ |
| 139 | 152 | public static String getTaskReason(TaskInfo task) { |
| 140 | - return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON); | |
| 153 | + return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON); | |
| 141 | 154 | } |
| 142 | 155 | |
| 143 | 156 | /** |
| ... | ... | @@ -161,8 +174,8 @@ public class FlowableUtils { |
| 161 | 174 | * @return 过滤后的表单 |
| 162 | 175 | */ |
| 163 | 176 | public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) { |
| 164 | - taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS); | |
| 165 | - taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON); | |
| 177 | + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS); | |
| 178 | + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON); | |
| 166 | 179 | return taskLocalVariables; |
| 167 | 180 | } |
| 168 | 181 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 5 | +import cn.hutool.core.map.MapUtil; | |
| 6 | +import cn.hutool.core.util.*; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 8 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; | |
| 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; | |
| 15 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | |
| 16 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; | |
| 17 | +import org.flowable.bpmn.BpmnAutoLayout; | |
| 18 | +import org.flowable.bpmn.model.Process; | |
| 19 | +import org.flowable.bpmn.model.*; | |
| 20 | + | |
| 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; | |
| 34 | +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; | |
| 35 | +import static org.flowable.bpmn.constants.BpmnXMLConstants.*; | |
| 36 | + | |
| 37 | +/** | |
| 38 | + * 仿钉钉快搭模型相关的工具方法 | |
| 39 | + * | |
| 40 | + * @author jason | |
| 41 | + */ | |
| 42 | +public class SimpleModelUtils { | |
| 43 | + | |
| 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}"; | |
| 63 | + | |
| 64 | + // TODO @yunai:注释需要完善下; | |
| 65 | + | |
| 66 | + /** | |
| 67 | + * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) | |
| 68 | + * | |
| 69 | + * @param processId 流程标识 | |
| 70 | + * @param processName 流程名称 | |
| 71 | + * @param simpleModelNode 仿钉钉流程设计模型数据结构 | |
| 72 | + * @return Bpmn Model | |
| 73 | + */ | |
| 74 | + public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { | |
| 75 | + BpmnModel bpmnModel = new BpmnModel(); | |
| 76 | + // 不加这个 解析 Message 会报 NPE 异常 . | |
| 77 | + bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace; | |
| 78 | + // TODO 芋艿:后续在 review | |
| 79 | + | |
| 80 | + Process process = new Process(); | |
| 81 | + process.setId(processId); | |
| 82 | + process.setName(processName); | |
| 83 | + process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么? | |
| 84 | + bpmnModel.addProcess(process); | |
| 85 | + | |
| 86 | + // TODO 芋艿:这里可能纠结下,到底前端传递,还是后端创建出来。 | |
| 87 | + // 目前前端的第一个节点是“发起人节点”。这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 | |
| 88 | + BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode(); | |
| 89 | + startNode.setChildNode(simpleModelNode); | |
| 90 | + // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process | |
| 91 | + traverseNodeToBuildFlowNode(startNode, process); | |
| 92 | + // 找到 end event | |
| 93 | + EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent); | |
| 94 | + | |
| 95 | + // 构建并添加节点之间的连线 Sequence Flow | |
| 96 | + traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); | |
| 97 | + // 自动布局 | |
| 98 | + new BpmnAutoLayout(bpmnModel).execute(); | |
| 99 | + return bpmnModel; | |
| 100 | + } | |
| 101 | + | |
| 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; | |
| 108 | + } | |
| 109 | + | |
| 110 | + // TODO @芋艿:在优化下这个注释 | |
| 111 | + private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { | |
| 112 | + // 1.1 无效节点返回 | |
| 113 | + if (!isValidNode(node)) { | |
| 114 | + return; | |
| 115 | + } | |
| 116 | + // 1.2 END_NODE 直接返回 | |
| 117 | + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); | |
| 118 | + Assert.notNull(nodeType, "模型节点类型不支持"); | |
| 119 | + if (nodeType == END_NODE) { | |
| 120 | + return; | |
| 121 | + } | |
| 122 | + // 2.1 情况一:普通节点 | |
| 123 | + BpmSimpleModelNodeVO childNode = node.getChildNode(); | |
| 124 | + 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 | + } | |
| 149 | + } else { | |
| 150 | + // 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); | |
| 196 | + } | |
| 197 | + } | |
| 198 | + | |
| 199 | + /** | |
| 200 | + * 构建有附加节点的连线 | |
| 201 | + * | |
| 202 | + * @param nodeId 当前节点 Id | |
| 203 | + * @param attachNodeId 附属节点 Id | |
| 204 | + * @param targetNodeId 目标节点 Id | |
| 205 | + */ | |
| 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); | |
| 210 | + } | |
| 211 | + | |
| 212 | + /** | |
| 213 | + * 构造条件表达式 | |
| 214 | + * | |
| 215 | + * @param conditionNode 条件节点 | |
| 216 | + */ | |
| 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() ? " && " : " || ")); | |
| 239 | + } | |
| 240 | + } | |
| 241 | + // TODO 待增加其它类型 | |
| 242 | + return conditionExpression; | |
| 243 | + } | |
| 244 | + | |
| 245 | + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) { | |
| 246 | + Assert.notEmpty(sourceId, "sourceId 不能为空"); | |
| 247 | + Assert.notEmpty(targetId, "targetId 不能为空"); | |
| 248 | + // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? @芋艿: 貌似不需要,Flowable 会默认生成 | |
| 249 | + // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧 | |
| 250 | + SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); | |
| 251 | + if (StrUtil.isNotEmpty(conditionExpression)) { | |
| 252 | + sequenceFlow.setConditionExpression(conditionExpression); | |
| 253 | + } | |
| 254 | + if (StrUtil.isNotEmpty(seqFlowId)) { | |
| 255 | + sequenceFlow.setId(seqFlowId); | |
| 256 | + } | |
| 257 | + if (StrUtil.isNotEmpty(seqName)) { | |
| 258 | + sequenceFlow.setName(seqName); | |
| 259 | + } | |
| 260 | + return sequenceFlow; | |
| 261 | + } | |
| 262 | + | |
| 263 | + // TODO @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow | |
| 264 | + private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { | |
| 265 | + // 判断是否有效节点 | |
| 266 | + if (!isValidNode(node)) { | |
| 267 | + return; | |
| 268 | + } | |
| 269 | + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); | |
| 270 | + Assert.notNull(nodeType, "模型节点类型不支持"); | |
| 271 | + | |
| 272 | + List<FlowElement> flowElements = buildFlowNode(node, nodeType); | |
| 273 | + flowElements.forEach(process::addFlowElement); | |
| 274 | + | |
| 275 | + // 如果不是网关类型的接口, 并且chileNode为空退出 | |
| 276 | + // 如果是“分支”节点,则递归处理条件 | |
| 277 | + if (BpmSimpleModelNodeType.isBranchNode(node.getType()) | |
| 278 | + && ArrayUtil.isNotEmpty(node.getConditionNodes())) { | |
| 279 | + node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); | |
| 280 | + } | |
| 281 | + | |
| 282 | + // 如果有“子”节点,则递归处理子节点 | |
| 283 | + traverseNodeToBuildFlowNode(node.getChildNode(), process); | |
| 284 | + } | |
| 285 | + | |
| 286 | + public static boolean isValidNode(BpmSimpleModelNodeVO node) { | |
| 287 | + return node != null && node.getId() != null; | |
| 288 | + } | |
| 289 | + | |
| 290 | + public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { | |
| 291 | + return APPROVE_NODE.getType().equals(node.getType()) && SEQUENTIAL.getMethod().equals(node.getApproveMethod()); | |
| 292 | + } | |
| 293 | + | |
| 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; | |
| 316 | + } | |
| 317 | + case COPY_NODE: { // 抄送节点 | |
| 318 | + ServiceTask serviceTask = convertCopyNode(node); | |
| 319 | + list.add(serviceTask); | |
| 320 | + break; | |
| 321 | + } | |
| 322 | + case CONDITION_BRANCH_NODE: { | |
| 323 | + ExclusiveGateway exclusiveGateway = convertConditionBranchNode(node); | |
| 324 | + list.add(exclusiveGateway); | |
| 325 | + break; | |
| 326 | + } | |
| 327 | + case PARALLEL_BRANCH_NODE: { | |
| 328 | + List<ParallelGateway> parallelGateways = convertParallelBranchNode(node); | |
| 329 | + list.addAll(parallelGateways); | |
| 330 | + break; | |
| 331 | + } | |
| 332 | + | |
| 333 | + case INCLUSIVE_BRANCH_NODE: { | |
| 334 | + // TODO jason 待实现 | |
| 335 | + break; | |
| 336 | + } | |
| 337 | + default: { | |
| 338 | + // TODO 其它节点类型的实现 | |
| 339 | + } | |
| 340 | + } | |
| 341 | + return list; | |
| 342 | + } | |
| 343 | + | |
| 344 | + private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) { | |
| 345 | + return buildBpmnStartUserTask(node); | |
| 346 | + } | |
| 347 | + | |
| 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); | |
| 357 | + } | |
| 358 | + return flowElements; | |
| 359 | + } | |
| 360 | + | |
| 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 | |
| 395 | + | |
| 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 | + } | |
| 402 | + | |
| 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 | + } | |
| 416 | + | |
| 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 | + } | |
| 425 | + | |
| 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) { | |
| 434 | + exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); | |
| 435 | + } | |
| 436 | + return exclusiveGateway; | |
| 437 | + } | |
| 438 | + | |
| 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 | + } | |
| 454 | + } | |
| 455 | + return inclusiveGateway; | |
| 456 | + } | |
| 457 | + | |
| 458 | + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { | |
| 459 | + UserTask userTask = new UserTask(); | |
| 460 | + userTask.setId(node.getId()); | |
| 461 | + userTask.setName(node.getName()); | |
| 462 | + | |
| 463 | + // 如果不是审批人节点,则直接返回 | |
| 464 | + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType())); | |
| 465 | + if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) { | |
| 466 | + return userTask; | |
| 467 | + } | |
| 468 | + | |
| 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 | + } | |
| 489 | + | |
| 490 | + private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { | |
| 491 | + if (rejectHandler == null) { | |
| 492 | + return; | |
| 493 | + } | |
| 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 | + | |
| 498 | + private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { | |
| 499 | + if (assignStartUserHandlerType == null) { | |
| 500 | + return; | |
| 501 | + } | |
| 502 | + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); | |
| 503 | + } | |
| 504 | + | |
| 505 | + private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) { | |
| 506 | + if (emptyHandler == null) { | |
| 507 | + return; | |
| 508 | + } | |
| 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 | + | |
| 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; | |
| 518 | + } | |
| 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); | |
| 539 | + } | |
| 540 | + userTask.setLoopCharacteristics(multiInstanceCharacteristics); | |
| 541 | + } | |
| 542 | + | |
| 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)); | |
| 556 | + } | |
| 557 | + } | |
| 558 | + | |
| 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 | + } | |
| 566 | + } | |
| 567 | + | |
| 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); | |
| 582 | + } | |
| 583 | + | |
| 584 | + private static void addExtensionElement(FlowElement element, String name, String value) { | |
| 585 | + if (value == null) { | |
| 586 | + return; | |
| 587 | + } | |
| 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 | + } | |
| 595 | + | |
| 596 | + // ========== 各种 build 节点的方法 ========== | |
| 597 | + | |
| 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 | + } | |
| 604 | + | |
| 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 | + } | |
| 621 | + | |
| 622 | + private static EndEvent convertEndNode(BpmSimpleModelNodeVO node) { | |
| 623 | + EndEvent endEvent = new EndEvent(); | |
| 624 | + endEvent.setId(node.getId()); | |
| 625 | + endEvent.setName(node.getName()); | |
| 626 | + | |
| 627 | + // TODO @芋艿 + jason:要不要加一个终止定义? | |
| 628 | + return endEvent; | |
| 629 | + } | |
| 630 | + | |
| 631 | +} | ... | ... |
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 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; | |
| 4 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | |
| 6 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 7 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | |
| 5 | 8 | import jakarta.validation.Valid; |
| 6 | 9 | import org.flowable.bpmn.model.BpmnModel; |
| 7 | 10 | import org.flowable.engine.repository.Model; |
| ... | ... | @@ -25,10 +28,9 @@ public interface BpmModelService { |
| 25 | 28 | * 创建流程模型 |
| 26 | 29 | * |
| 27 | 30 | * @param modelVO 创建信息 |
| 28 | - * @param bpmnXml BPMN XML | |
| 29 | 31 | * @return 创建的流程模型的编号 |
| 30 | 32 | */ |
| 31 | - String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml); | |
| 33 | + String createModel(@Valid BpmModelSaveReqVO modelVO); | |
| 32 | 34 | |
| 33 | 35 | /** |
| 34 | 36 | * 获得流程模块 |
| ... | ... | @@ -47,33 +49,45 @@ public interface BpmModelService { |
| 47 | 49 | byte[] getModelBpmnXML(String id); |
| 48 | 50 | |
| 49 | 51 | /** |
| 52 | + * 修改流程模型的 BPMN XML | |
| 53 | + * | |
| 54 | + * @param id 编号 | |
| 55 | + * @param bpmnXml BPMN XML | |
| 56 | + */ | |
| 57 | + void updateModelBpmnXml(String id, String bpmnXml); | |
| 58 | + | |
| 59 | + /** | |
| 50 | 60 | * 修改流程模型 |
| 51 | 61 | * |
| 62 | + * @param userId 用户编号 | |
| 52 | 63 | * @param updateReqVO 更新信息 |
| 53 | 64 | */ |
| 54 | - void updateModel(@Valid BpmModelUpdateReqVO updateReqVO); | |
| 65 | + void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); | |
| 55 | 66 | |
| 56 | 67 | /** |
| 57 | 68 | * 将流程模型,部署成一个流程定义 |
| 58 | 69 | * |
| 70 | + * @param userId 用户编号 | |
| 59 | 71 | * @param id 编号 |
| 60 | 72 | */ |
| 61 | - void deployModel(String id); | |
| 73 | + void deployModel(Long userId, String id); | |
| 62 | 74 | |
| 63 | 75 | /** |
| 64 | 76 | * 删除模型 |
| 65 | 77 | * |
| 78 | + * @param userId 用户编号 | |
| 66 | 79 | * @param id 编号 |
| 67 | 80 | */ |
| 68 | - void deleteModel(String id); | |
| 81 | + void deleteModel(Long userId, String id); | |
| 69 | 82 | |
| 70 | 83 | /** |
| 71 | 84 | * 修改模型的状态,实际更新的部署的流程定义的状态 |
| 72 | 85 | * |
| 86 | + * @param userId 用户编号 | |
| 73 | 87 | * @param id 编号 |
| 74 | 88 | * @param state 状态 |
| 75 | 89 | */ |
| 76 | - void updateModelState(String id, Integer state); | |
| 90 | + void updateModelState(Long userId, String id, Integer state); | |
| 77 | 91 | |
| 78 | 92 | /** |
| 79 | 93 | * 获得流程定义编号对应的 BPMN Model |
| ... | ... | @@ -83,4 +97,22 @@ public interface BpmModelService { |
| 83 | 97 | */ |
| 84 | 98 | BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); |
| 85 | 99 | |
| 100 | + // ========== 仿钉钉/飞书的精简模型 ========= | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * 获取仿钉钉流程设计模型结构 | |
| 104 | + * | |
| 105 | + * @param modelId 流程模型编号 | |
| 106 | + * @return 仿钉钉流程设计模型结构 | |
| 107 | + */ | |
| 108 | + BpmSimpleModelNodeVO getSimpleModel(String modelId); | |
| 109 | + | |
| 110 | + /** | |
| 111 | + * 更新仿钉钉流程设计模型 | |
| 112 | + * | |
| 113 | + * @param userId 用户编号 | |
| 114 | + * @param reqVO 请求信息 | |
| 115 | + */ | |
| 116 | + void updateSimpleModel(Long userId, @Valid BpmSimpleModelUpdateReqVO reqVO); | |
| 117 | + | |
| 86 | 118 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.service.definition; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.ArrayUtil; | |
| 3 | 5 | import cn.hutool.core.util.StrUtil; |
| 4 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 6 | 8 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 7 | 9 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 8 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; | |
| 10 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | |
| 9 | 11 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; |
| 10 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; | |
| 12 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | |
| 13 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | |
| 14 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | |
| 11 | 15 | import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert; |
| 12 | 16 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 13 | 17 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| 14 | 18 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| 15 | 19 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 16 | 20 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 17 | -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; | |
| 21 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; | |
| 18 | 22 | import jakarta.annotation.Resource; |
| 19 | 23 | import jakarta.validation.Valid; |
| 20 | 24 | import lombok.extern.slf4j.Slf4j; |
| ... | ... | @@ -28,7 +32,6 @@ import org.flowable.engine.repository.ModelQuery; |
| 28 | 32 | import org.flowable.engine.repository.ProcessDefinition; |
| 29 | 33 | import org.springframework.stereotype.Service; |
| 30 | 34 | import org.springframework.transaction.annotation.Transactional; |
| 31 | -import org.springframework.util.ObjectUtils; | |
| 32 | 35 | import org.springframework.validation.annotation.Validated; |
| 33 | 36 | |
| 34 | 37 | import java.util.List; |
| ... | ... | @@ -86,63 +89,79 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 86 | 89 | |
| 87 | 90 | @Override |
| 88 | 91 | @Transactional(rollbackFor = Exception.class) |
| 89 | - public String createModel(@Valid BpmModelCreateReqVO createReqVO, String bpmnXml) { | |
| 92 | + public String createModel(@Valid BpmModelSaveReqVO createReqVO) { | |
| 90 | 93 | if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { |
| 91 | 94 | throw exception(MODEL_KEY_VALID); |
| 92 | 95 | } |
| 93 | - // 校验流程标识已经存在 | |
| 96 | + // 1. 校验流程标识已经存在 | |
| 94 | 97 | Model keyModel = getModelByKey(createReqVO.getKey()); |
| 95 | 98 | if (keyModel != null) { |
| 96 | 99 | throw exception(MODEL_KEY_EXISTS, createReqVO.getKey()); |
| 97 | 100 | } |
| 98 | 101 | |
| 99 | - // 创建流程定义 | |
| 102 | + // 2.1 创建流程定义 | |
| 100 | 103 | Model model = repositoryService.newModel(); |
| 101 | - BpmModelConvert.INSTANCE.copyToCreateModel(model, createReqVO); | |
| 104 | + BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); | |
| 102 | 105 | model.setTenantId(FlowableUtils.getTenantId()); |
| 103 | - // 保存流程定义 | |
| 106 | + // 2.2 保存流程定义 | |
| 104 | 107 | repositoryService.saveModel(model); |
| 105 | - // 保存 BPMN XML | |
| 106 | - saveModelBpmnXml(model, bpmnXml); | |
| 107 | 108 | return model.getId(); |
| 108 | 109 | } |
| 109 | 110 | |
| 110 | 111 | @Override |
| 111 | 112 | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 |
| 112 | - public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) { | |
| 113 | - // 校验流程模型存在 | |
| 114 | - Model model = getModel(updateReqVO.getId()); | |
| 115 | - if (model == null) { | |
| 116 | - throw exception(MODEL_NOT_EXISTS); | |
| 117 | - } | |
| 113 | + public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) { | |
| 114 | + // 1. 校验流程模型存在 | |
| 115 | + Model model = validateModelManager(updateReqVO.getId(), userId); | |
| 118 | 116 | |
| 119 | 117 | // 修改流程定义 |
| 120 | - BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateReqVO); | |
| 118 | + BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); | |
| 121 | 119 | // 更新模型 |
| 122 | 120 | repositoryService.saveModel(model); |
| 123 | - // 更新 BPMN XML | |
| 124 | - saveModelBpmnXml(model, updateReqVO.getBpmnXml()); | |
| 121 | + } | |
| 122 | + | |
| 123 | + private Model validateModelExists(String id) { | |
| 124 | + Model model = repositoryService.getModel(id); | |
| 125 | + if (model == null) { | |
| 126 | + throw exception(MODEL_NOT_EXISTS); | |
| 127 | + } | |
| 128 | + return model; | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** | |
| 132 | + * 校验是否有流程模型的管理权限 | |
| 133 | + * | |
| 134 | + * @param id 流程模型编号 | |
| 135 | + * @param userId 用户编号 | |
| 136 | + * @return 流程模型 | |
| 137 | + */ | |
| 138 | + private Model validateModelManager(String id, Long userId) { | |
| 139 | + Model model = validateModelExists(id); | |
| 140 | + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | |
| 141 | + if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { | |
| 142 | + throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER); | |
| 143 | + } | |
| 144 | + return model; | |
| 125 | 145 | } |
| 126 | 146 | |
| 127 | 147 | @Override |
| 128 | 148 | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 |
| 129 | - public void deployModel(String id) { | |
| 149 | + public void deployModel(Long userId, String id) { | |
| 130 | 150 | // 1.1 校验流程模型存在 |
| 131 | - Model model = getModel(id); | |
| 132 | - if (ObjectUtils.isEmpty(model)) { | |
| 133 | - throw exception(MODEL_NOT_EXISTS); | |
| 134 | - } | |
| 151 | + Model model = validateModelManager(id, userId); | |
| 135 | 152 | // 1.2 校验流程图 |
| 136 | 153 | byte[] bpmnBytes = getModelBpmnXML(model.getId()); |
| 137 | 154 | validateBpmnXml(bpmnBytes); |
| 138 | 155 | // 1.3 校验表单已配 |
| 139 | - BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); | |
| 156 | + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | |
| 140 | 157 | BpmFormDO form = validateFormConfig(metaInfo); |
| 141 | 158 | // 1.4 校验任务分配规则已配置 |
| 142 | 159 | taskCandidateInvoker.validateBpmnConfig(bpmnBytes); |
| 160 | + // 1.5 获取仿钉钉流程设计器模型数据 | |
| 161 | + byte[] simpleBytes = getModelSimpleJson(model.getId()); | |
| 143 | 162 | |
| 144 | 163 | // 2.1 创建流程定义 |
| 145 | - String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form); | |
| 164 | + String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleBytes, form); | |
| 146 | 165 | |
| 147 | 166 | // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 |
| 148 | 167 | updateProcessDefinitionSuspended(model.getDeploymentId()); |
| ... | ... | @@ -174,12 +193,10 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 174 | 193 | |
| 175 | 194 | @Override |
| 176 | 195 | @Transactional(rollbackFor = Exception.class) |
| 177 | - public void deleteModel(String id) { | |
| 196 | + public void deleteModel(Long userId, String id) { | |
| 178 | 197 | // 校验流程模型存在 |
| 179 | - Model model = getModel(id); | |
| 180 | - if (model == null) { | |
| 181 | - throw exception(MODEL_NOT_EXISTS); | |
| 182 | - } | |
| 198 | + Model model = validateModelManager(id, userId); | |
| 199 | + | |
| 183 | 200 | // 执行删除 |
| 184 | 201 | repositoryService.deleteModel(id); |
| 185 | 202 | // 禁用流程定义 |
| ... | ... | @@ -187,12 +204,9 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 187 | 204 | } |
| 188 | 205 | |
| 189 | 206 | @Override |
| 190 | - public void updateModelState(String id, Integer state) { | |
| 207 | + public void updateModelState(Long userId, String id, Integer state) { | |
| 191 | 208 | // 1.1 校验流程模型存在 |
| 192 | - Model model = getModel(id); | |
| 193 | - if (model == null) { | |
| 194 | - throw exception(MODEL_NOT_EXISTS); | |
| 195 | - } | |
| 209 | + Model model = validateModelManager(id, userId); | |
| 196 | 210 | // 1.2 校验流程定义存在 |
| 197 | 211 | ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId()); |
| 198 | 212 | if (definition == null) { |
| ... | ... | @@ -208,13 +222,34 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 208 | 222 | return repositoryService.getBpmnModel(processDefinitionId); |
| 209 | 223 | } |
| 210 | 224 | |
| 225 | + @Override | |
| 226 | + public BpmSimpleModelNodeVO getSimpleModel(String modelId) { | |
| 227 | + Model model = validateModelExists(modelId); | |
| 228 | + // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 | |
| 229 | + byte[] jsonBytes = getModelSimpleJson(model.getId()); | |
| 230 | + return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); | |
| 231 | + } | |
| 232 | + | |
| 233 | + @Override | |
| 234 | + public void updateSimpleModel(Long userId, BpmSimpleModelUpdateReqVO reqVO) { | |
| 235 | + // 1. 校验流程模型存在 | |
| 236 | + Model model = validateModelManager(reqVO.getId(), userId); | |
| 237 | + | |
| 238 | + // 2.1 JSON 转换成 bpmnModel | |
| 239 | + BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); | |
| 240 | + // 2.2 保存 Bpmn XML | |
| 241 | + updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); | |
| 242 | + // 2.3 保存 JSON 数据 | |
| 243 | + saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel())); | |
| 244 | + } | |
| 245 | + | |
| 211 | 246 | /** |
| 212 | 247 | * 校验流程表单已配置 |
| 213 | 248 | * |
| 214 | 249 | * @param metaInfo 流程模型元数据 |
| 215 | 250 | * @return 表单配置 |
| 216 | 251 | */ |
| 217 | - private BpmFormDO validateFormConfig(BpmModelMetaInfoRespDTO metaInfo) { | |
| 252 | + private BpmFormDO validateFormConfig(BpmModelMetaInfoVO metaInfo) { | |
| 218 | 253 | if (metaInfo == null || metaInfo.getFormType() == null) { |
| 219 | 254 | throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); |
| 220 | 255 | } |
| ... | ... | @@ -236,16 +271,28 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 236 | 271 | } |
| 237 | 272 | } |
| 238 | 273 | |
| 239 | - private void saveModelBpmnXml(Model model, String bpmnXml) { | |
| 274 | + @Override | |
| 275 | + public void updateModelBpmnXml(String id, String bpmnXml) { | |
| 240 | 276 | if (StrUtil.isEmpty(bpmnXml)) { |
| 241 | 277 | return; |
| 242 | 278 | } |
| 243 | - repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml)); | |
| 279 | + repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); | |
| 280 | + } | |
| 281 | + | |
| 282 | + private byte[] getModelSimpleJson(String id) { | |
| 283 | + return repositoryService.getModelEditorSourceExtra(id); | |
| 284 | + } | |
| 285 | + | |
| 286 | + private void saveModelSimpleJson(String id, byte[] jsonBytes) { | |
| 287 | + if (ArrayUtil.isEmpty(jsonBytes)) { | |
| 288 | + return; | |
| 289 | + } | |
| 290 | + repositoryService.addModelEditorSourceExtra(id, jsonBytes); | |
| 244 | 291 | } |
| 245 | 292 | |
| 246 | 293 | /** |
| 247 | 294 | * 挂起 deploymentId 对应的流程定义 |
| 248 | - * | |
| 295 | + * <p> | |
| 249 | 296 | * 注意:这里一个 deploymentId 只关联一个流程定义 |
| 250 | 297 | * |
| 251 | 298 | * @param deploymentId 流程发布Id | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
| ... | ... | @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 7 | -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | |
| 8 | 8 | import org.flowable.bpmn.model.BpmnModel; |
| 9 | 9 | import org.flowable.engine.repository.Deployment; |
| 10 | 10 | import org.flowable.engine.repository.Model; |
| ... | ... | @@ -48,10 +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 | 52 | * @param form 表单 |
| 52 | 53 | * @return 流程编号 |
| 53 | 54 | */ |
| 54 | - String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form); | |
| 55 | + String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, | |
| 56 | + byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form); | |
| 55 | 57 | |
| 56 | 58 | /** |
| 57 | 59 | * 更新流程定义状态 |
| ... | ... | @@ -134,6 +136,15 @@ public interface BpmProcessDefinitionService { |
| 134 | 136 | ProcessDefinition getActiveProcessDefinition(String key); |
| 135 | 137 | |
| 136 | 138 | /** |
| 139 | + * 判断用户是否可以使用该流程定义,进行流程的发起 | |
| 140 | + * | |
| 141 | + * @param processDefinition 流程定义 | |
| 142 | + * @param userId 用户编号 | |
| 143 | + * @return 是否可以发起流程 | |
| 144 | + */ | |
| 145 | + boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId); | |
| 146 | + | |
| 147 | + /** | |
| 137 | 148 | * 获得 ids 对应的 Deployment Map |
| 138 | 149 | * |
| 139 | 150 | * @param ids 部署编号的数组 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
| ... | ... | @@ -5,13 +5,13 @@ 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.object.BeanUtils; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 8 | +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | |
| 8 | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; |
| 9 | 10 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 10 | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 11 | 12 | import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper; |
| 12 | 13 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 13 | 14 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 14 | -import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; | |
| 15 | 15 | import jakarta.annotation.Resource; |
| 16 | 16 | import lombok.extern.slf4j.Slf4j; |
| 17 | 17 | import org.flowable.bpmn.model.BpmnModel; |
| ... | ... | @@ -24,6 +24,7 @@ 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; | |
| 27 | 28 | import java.util.*; |
| 28 | 29 | |
| 29 | 30 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -85,6 +86,19 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ |
| 85 | 86 | } |
| 86 | 87 | |
| 87 | 88 | @Override |
| 89 | + public boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId) { | |
| 90 | + if (processDefinition == null) { | |
| 91 | + return false; | |
| 92 | + } | |
| 93 | + // 为空,则所有人都可以发起 | |
| 94 | + if (CollUtil.isEmpty(processDefinition.getStartUserIds())) { | |
| 95 | + return true; | |
| 96 | + } | |
| 97 | + // 不为空,则需要存在里面 | |
| 98 | + return processDefinition.getStartUserIds().contains(userId); | |
| 99 | + } | |
| 100 | + | |
| 101 | + @Override | |
| 88 | 102 | public List<Deployment> getDeploymentList(Set<String> ids) { |
| 89 | 103 | if (CollUtil.isEmpty(ids)) { |
| 90 | 104 | return emptyList(); |
| ... | ... | @@ -105,8 +119,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ |
| 105 | 119 | } |
| 106 | 120 | |
| 107 | 121 | @Override |
| 108 | - public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, | |
| 109 | - byte[] bpmnBytes, BpmFormDO form) { | |
| 122 | + public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, | |
| 123 | + byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form) { | |
| 110 | 124 | // 创建 Deployment 部署 |
| 111 | 125 | Deployment deploy = repositoryService.createDeployment() |
| 112 | 126 | .key(model.getKey()).name(model.getName()).category(model.getCategory()) |
| ... | ... | @@ -131,7 +145,9 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ |
| 131 | 145 | |
| 132 | 146 | // 插入拓展表 |
| 133 | 147 | BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) |
| 134 | - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()); | |
| 148 | + .setModelId(model.getId()).setProcessDefinitionId(definition.getId()).setModelType(modelMetaInfo.getType()) | |
| 149 | + .setSimpleModel(StrUtil.str(simpleBytes, StandardCharsets.UTF_8)); | |
| 150 | + | |
| 135 | 151 | if (form != null) { |
| 136 | 152 | definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); |
| 137 | 153 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.service.definition.dto; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; | |
| 4 | -import lombok.Data; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * BPM 流程 MetaInfo Response DTO | |
| 8 | - * 主要用于 { Model#setMetaInfo(String)} 的存储 | |
| 9 | - * | |
| 10 | - * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 | |
| 11 | - * | |
| 12 | - * @author 芋道源码 | |
| 13 | - */ | |
| 14 | -@Data | |
| 15 | -public class BpmModelMetaInfoRespDTO { | |
| 16 | - | |
| 17 | - /** | |
| 18 | - * 流程图标 | |
| 19 | - */ | |
| 20 | - private String icon; | |
| 21 | - /** | |
| 22 | - * 流程描述 | |
| 23 | - */ | |
| 24 | - private String description; | |
| 25 | - | |
| 26 | - /** | |
| 27 | - * 表单类型 | |
| 28 | - */ | |
| 29 | - private Integer formType; | |
| 30 | - /** | |
| 31 | - * 表单编号 | |
| 32 | - * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 | |
| 33 | - */ | |
| 34 | - private Long formId; | |
| 35 | - /** | |
| 36 | - * 自定义表单的提交路径,使用 Vue 的路由地址 | |
| 37 | - * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 | |
| 38 | - */ | |
| 39 | - private String formCustomCreatePath; | |
| 40 | - /** | |
| 41 | - * 自定义表单的查看路径,使用 Vue 的路由地址 | |
| 42 | - * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 | |
| 43 | - */ | |
| 44 | - private String formCustomViewPath; | |
| 45 | - | |
| 46 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.message; |
| 3 | 3 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 6 | - | |
| 6 | +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | |
| 7 | 7 | import jakarta.validation.Valid; |
| 8 | 8 | |
| 9 | 9 | /** |
| ... | ... | @@ -36,4 +36,11 @@ public interface BpmMessageService { |
| 36 | 36 | */ |
| 37 | 37 | void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); |
| 38 | 38 | |
| 39 | + /** | |
| 40 | + * 发送任务审批超时的消息 | |
| 41 | + * | |
| 42 | + * @param reqDTO 发送信息 | |
| 43 | + */ | |
| 44 | + void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); | |
| 45 | + | |
| 39 | 46 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| 7 | 7 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 9 | +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | |
| 9 | 10 | import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; |
| 10 | 11 | import lombok.extern.slf4j.Slf4j; |
| 11 | 12 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService { |
| 61 | 62 | BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); |
| 62 | 63 | } |
| 63 | 64 | |
| 65 | + @Override | |
| 66 | + public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { | |
| 67 | + Map<String, Object> templateParams = new HashMap<>(); | |
| 68 | + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); | |
| 69 | + templateParams.put("taskName", reqDTO.getTaskName()); | |
| 70 | + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); | |
| 71 | + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), | |
| 72 | + BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)); | |
| 73 | + } | |
| 74 | + | |
| 64 | 75 | private String getProcessInstanceDetailUrl(String taskId) { |
| 65 | 76 | return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; |
| 66 | 77 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.service.message.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotEmpty; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * BPM 发送任务审批超时 Request DTO | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class BpmMessageSendWhenTaskTimeoutReqDTO { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 流程实例的编号 | |
| 15 | + */ | |
| 16 | + @NotEmpty(message = "流程实例的编号不能为空") | |
| 17 | + private String processInstanceId; | |
| 18 | + /** | |
| 19 | + * 流程实例的名字 | |
| 20 | + */ | |
| 21 | + @NotEmpty(message = "流程实例的名字不能为空") | |
| 22 | + private String processInstanceName; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 流程任务的编号 | |
| 26 | + */ | |
| 27 | + @NotEmpty(message = "流程任务的编号不能为空") | |
| 28 | + private String taskId; | |
| 29 | + /** | |
| 30 | + * 流程任务的名字 | |
| 31 | + */ | |
| 32 | + @NotEmpty(message = "流程任务的名字不能为空") | |
| 33 | + private String taskName; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 审批人的用户编号 | |
| 37 | + */ | |
| 38 | + @NotNull(message = "审批人的用户编号不能为空") | |
| 39 | + private Long assigneeUserId; | |
| 40 | + | |
| 41 | +} | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; | |
| 4 | 3 | import org.flowable.engine.history.HistoricActivityInstance; |
| 5 | 4 | |
| 6 | 5 | import java.util.List; |
| ... | ... | @@ -18,7 +17,7 @@ public interface BpmActivityService { |
| 18 | 17 | * @param processInstanceId 流程实例的编号 |
| 19 | 18 | * @return 活动实例列表 |
| 20 | 19 | */ |
| 21 | - List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId); | |
| 20 | + List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId); | |
| 22 | 21 | |
| 23 | 22 | /** |
| 24 | 23 | * 获得执行编号对应的活动实例 | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; | |
| 4 | -import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert; | |
| 3 | +import jakarta.annotation.Resource; | |
| 5 | 4 | import lombok.extern.slf4j.Slf4j; |
| 6 | 5 | import org.flowable.engine.HistoryService; |
| 7 | 6 | import org.flowable.engine.history.HistoricActivityInstance; |
| 8 | 7 | import org.springframework.stereotype.Service; |
| 9 | 8 | import org.springframework.validation.annotation.Validated; |
| 10 | 9 | |
| 11 | -import jakarta.annotation.Resource; | |
| 12 | 10 | import java.util.List; |
| 13 | 11 | |
| 14 | 12 | |
| ... | ... | @@ -26,10 +24,9 @@ public class BpmActivityServiceImpl implements BpmActivityService { |
| 26 | 24 | private HistoryService historyService; |
| 27 | 25 | |
| 28 | 26 | @Override |
| 29 | - public List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId) { | |
| 30 | - List<HistoricActivityInstance> activityList = historyService.createHistoricActivityInstanceQuery() | |
| 31 | - .processInstanceId(processInstanceId).list(); | |
| 32 | - return BpmActivityConvert.INSTANCE.convertList(activityList); | |
| 27 | + public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) { | |
| 28 | + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) | |
| 29 | + .orderByHistoricActivityInstanceStartTime().asc().list(); | |
| 33 | 30 | } |
| 34 | 31 | |
| 35 | 32 | @Override | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| 6 | 6 | |
| 7 | 7 | import java.util.Collection; |
| 8 | +import java.util.Set; | |
| 8 | 9 | |
| 9 | 10 | /** |
| 10 | 11 | * 流程抄送 Service 接口 |
| ... | ... | @@ -22,6 +23,18 @@ public interface BpmProcessInstanceCopyService { |
| 22 | 23 | void createProcessInstanceCopy(Collection<Long> userIds, String taskId); |
| 23 | 24 | |
| 24 | 25 | /** |
| 26 | + * 流程实例的抄送 | |
| 27 | + * | |
| 28 | + * @param userIds 抄送的用户编号 | |
| 29 | + * @param processInstanceId 流程编号 | |
| 30 | + * @param activityId 流程活动编号 id (对应 BPMN XML 节点 Id) | |
| 31 | + * // TODO 芋艿这个 taskId 是不是可以不要了 | |
| 32 | + * @param taskId 任务编号 | |
| 33 | + * @param taskName 任务名称 | |
| 34 | + */ | |
| 35 | + void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName); | |
| 36 | + | |
| 37 | + /** | |
| 25 | 38 | * 获得抄送的流程的分页 |
| 26 | 39 | * |
| 27 | 40 | * @param userId 当前登录用户 |
| ... | ... | @@ -30,5 +43,14 @@ public interface BpmProcessInstanceCopyService { |
| 30 | 43 | */ |
| 31 | 44 | PageResult<BpmProcessInstanceCopyDO> getProcessInstanceCopyPage(Long userId, |
| 32 | 45 | 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); | |
| 33 | 55 | |
| 34 | 56 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
| ... | ... | @@ -2,6 +2,7 @@ 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; | |
| 5 | 6 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| 7 | 8 | import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper; |
| ... | ... | @@ -18,6 +19,7 @@ import org.springframework.validation.annotation.Validated; |
| 18 | 19 | |
| 19 | 20 | import java.util.Collection; |
| 20 | 21 | import java.util.List; |
| 22 | +import java.util.Set; | |
| 21 | 23 | |
| 22 | 24 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 23 | 25 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| ... | ... | @@ -48,18 +50,22 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy |
| 48 | 50 | |
| 49 | 51 | @Override |
| 50 | 52 | public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) { |
| 51 | - // 1.1 校验任务存在 | |
| 52 | 53 | Task task = taskService.getTask(taskId); |
| 53 | 54 | if (ObjectUtil.isNull(task)) { |
| 54 | 55 | throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); |
| 55 | 56 | } |
| 56 | - // 1.2 校验流程实例存在 | |
| 57 | 57 | String processInstanceId = task.getProcessInstanceId(); |
| 58 | + createProcessInstanceCopy(userIds, processInstanceId, task.getTaskDefinitionKey(), task.getId(), task.getName()); | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName) { | |
| 63 | + // 1.1 校验流程实例存在 | |
| 58 | 64 | ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); |
| 59 | 65 | if (processInstance == null) { |
| 60 | 66 | throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); |
| 61 | 67 | } |
| 62 | - // 1.3 校验流程定义存在 | |
| 68 | + // 1.2 校验流程定义存在 | |
| 63 | 69 | ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( |
| 64 | 70 | processInstance.getProcessDefinitionId()); |
| 65 | 71 | if (processDefinition == null) { |
| ... | ... | @@ -70,7 +76,8 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy |
| 70 | 76 | List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() |
| 71 | 77 | .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) |
| 72 | 78 | .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) |
| 73 | - .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName())); | |
| 79 | + .setCategory(processDefinition.getCategory()).setActivityId(activityId) | |
| 80 | + .setTaskId(taskId).setTaskName(taskName)); | |
| 74 | 81 | processInstanceCopyMapper.insertBatch(copyList); |
| 75 | 82 | } |
| 76 | 83 | |
| ... | ... | @@ -80,4 +87,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy |
| 80 | 87 | return processInstanceCopyMapper.selectPage(userId, pageReqVO); |
| 81 | 88 | } |
| 82 | 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 | + | |
| 83 | 96 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
| ... | ... | @@ -2,11 +2,8 @@ package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| 5 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; | |
| 6 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; | |
| 7 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; | |
| 8 | 6 | import jakarta.validation.Valid; |
| 9 | -import org.flowable.engine.delegate.event.FlowableCancelledEvent; | |
| 10 | 7 | import org.flowable.engine.history.HistoricProcessInstance; |
| 11 | 8 | import org.flowable.engine.runtime.ProcessInstance; |
| 12 | 9 | |
| ... | ... | @@ -23,6 +20,8 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 23 | 20 | */ |
| 24 | 21 | public interface BpmProcessInstanceService { |
| 25 | 22 | |
| 23 | + // ========== Query 查询相关方法 ========== | |
| 24 | + | |
| 26 | 25 | /** |
| 27 | 26 | * 获得流程实例 |
| 28 | 27 | * |
| ... | ... | @@ -86,6 +85,28 @@ public interface BpmProcessInstanceService { |
| 86 | 85 | @Valid BpmProcessInstancePageReqVO pageReqVO); |
| 87 | 86 | |
| 88 | 87 | /** |
| 88 | + * 获得表单字段权限 | |
| 89 | + * | |
| 90 | + * @param reqVO 请求消息 | |
| 91 | + * @return 表单字段权限 | |
| 92 | + */ | |
| 93 | + Map<String, String> getFormFieldsPermission(@Valid BpmFormFieldsPermissionReqVO reqVO); | |
| 94 | + | |
| 95 | + // TODO @芋艿:重点在 review 下 | |
| 96 | + /** | |
| 97 | + * 获取审批详情。 | |
| 98 | + * <p> | |
| 99 | + * 可以是准备发起的流程、进行中的流程、已经结束的流程 | |
| 100 | + * | |
| 101 | + * @param loginUserId 登录人的用户编号 | |
| 102 | + * @param reqVO 请求信息 | |
| 103 | + * @return 流程实例的进度 | |
| 104 | + */ | |
| 105 | + BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); | |
| 106 | + | |
| 107 | + // ========== Update 写入相关方法 ========== | |
| 108 | + | |
| 109 | + /** | |
| 89 | 110 | * 创建流程实例(提供给前端) |
| 90 | 111 | * |
| 91 | 112 | * @param userId 用户编号 |
| ... | ... | @@ -114,31 +135,27 @@ public interface BpmProcessInstanceService { |
| 114 | 135 | /** |
| 115 | 136 | * 管理员取消流程实例 |
| 116 | 137 | * |
| 117 | - * @param userId 用户编号 | |
| 138 | + * @param userId 用户编号 | |
| 118 | 139 | * @param cancelReqVO 取消信息 |
| 119 | 140 | */ |
| 120 | 141 | void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); |
| 121 | 142 | |
| 122 | 143 | /** |
| 123 | - * 更新 ProcessInstance 拓展记录为取消 | |
| 144 | + * 更新 ProcessInstance 为不通过 | |
| 124 | 145 | * |
| 125 | - * @param event 流程取消事件 | |
| 146 | + * @param processInstance 流程实例 | |
| 147 | + * @param reason 理由。例如说,审批不通过时,需要传递该值 | |
| 126 | 148 | */ |
| 127 | - void updateProcessInstanceWhenCancel(FlowableCancelledEvent event); | |
| 149 | + void updateProcessInstanceReject(ProcessInstance processInstance, String reason); | |
| 150 | + | |
| 151 | + // ========== Event 事件相关方法 ========== | |
| 128 | 152 | |
| 129 | 153 | /** |
| 130 | - * 更新 ProcessInstance 拓展记录为完成 | |
| 154 | + * 处理 ProcessInstance 完成事件,例如说:审批通过、不通过、取消 | |
| 131 | 155 | * |
| 132 | 156 | * @param instance 流程任务 |
| 133 | 157 | */ |
| 134 | - void updateProcessInstanceWhenApprove(ProcessInstance instance); | |
| 158 | + void processProcessInstanceCompleted(ProcessInstance instance); | |
| 135 | 159 | |
| 136 | - /** | |
| 137 | - * 更新 ProcessInstance 拓展记录为不通过 | |
| 138 | - * | |
| 139 | - * @param id 流程编号 | |
| 140 | - * @param reason 理由。例如说,审批不通过时,需要传递该值 | |
| 141 | - */ | |
| 142 | - void updateProcessInstanceReject(String id, String reason); | |
| 143 | 160 | |
| 144 | 161 | } | ... | ... |
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.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.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 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.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.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; 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.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.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @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(BpmConstants.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 @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); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.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. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @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); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } | |
| 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.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)); }); } } | |
| 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
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.task; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 6 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; | |
| 6 | 7 | import jakarta.validation.Valid; |
| 7 | 8 | import org.flowable.bpmn.model.UserTask; |
| 8 | 9 | import org.flowable.task.api.Task; |
| ... | ... | @@ -20,6 +21,8 @@ import java.util.Map; |
| 20 | 21 | */ |
| 21 | 22 | public interface BpmTaskService { |
| 22 | 23 | |
| 24 | + // ========== Query 查询相关方法 ========== | |
| 25 | + | |
| 23 | 26 | /** |
| 24 | 27 | * 获得待办的流程任务分页 |
| 25 | 28 | * |
| ... | ... | @@ -75,65 +78,96 @@ public interface BpmTaskService { |
| 75 | 78 | List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId); |
| 76 | 79 | |
| 77 | 80 | /** |
| 78 | - * 通过任务 | |
| 81 | + * 获取任务 | |
| 79 | 82 | * |
| 80 | - * @param userId 用户编号 | |
| 81 | - * @param reqVO 通过请求 | |
| 83 | + * @param id 任务编号 | |
| 84 | + * @return 任务 | |
| 82 | 85 | */ |
| 83 | - void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO); | |
| 86 | + Task getTask(String id); | |
| 84 | 87 | |
| 85 | 88 | /** |
| 86 | - * 不通过任务 | |
| 89 | + * 获取历史任务 | |
| 87 | 90 | * |
| 88 | - * @param userId 用户编号 | |
| 89 | - * @param reqVO 不通过请求 | |
| 91 | + * @param id 任务编号 | |
| 92 | + * @return 历史任务 | |
| 90 | 93 | */ |
| 91 | - void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); | |
| 94 | + HistoricTaskInstance getHistoricTask(String id); | |
| 92 | 95 | |
| 93 | 96 | /** |
| 94 | - * 将流程任务分配给指定用户 | |
| 97 | + * 获取历史任务列表 | |
| 95 | 98 | * |
| 96 | - * @param userId 用户编号 | |
| 97 | - * @param reqVO 分配请求 | |
| 99 | + * @param taskIds 任务编号集合 | |
| 100 | + * @return 历史任务列表 | |
| 98 | 101 | */ |
| 99 | - void transferTask(Long userId, BpmTaskTransferReqVO reqVO); | |
| 102 | + List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds); | |
| 100 | 103 | |
| 101 | 104 | /** |
| 102 | - * 更新 Task 状态,在创建时 | |
| 105 | + * 根据条件查询正在进行中的任务 | |
| 103 | 106 | * |
| 104 | - * @param task 任务实体 | |
| 107 | + * @param processInstanceId 流程实例编号,不允许为空 | |
| 108 | + * @param assigned 是否分配了审批人,允许空 | |
| 109 | + * @param taskDefineKey 任务定义 Key,允许空 | |
| 105 | 110 | */ |
| 106 | - void updateTaskStatusWhenCreated(Task task); | |
| 111 | + List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, | |
| 112 | + Boolean assigned, | |
| 113 | + String taskDefineKey); | |
| 107 | 114 | |
| 108 | 115 | /** |
| 109 | - * 更新 Task 状态,在取消时 | |
| 116 | + * 获取当前任务的可回退的 UserTask 集合 | |
| 110 | 117 | * |
| 111 | - * @param taskId 任务的编号 | |
| 118 | + * @param id 当前的任务 ID | |
| 119 | + * @return 可以回退的节点列表 | |
| 112 | 120 | */ |
| 113 | - void updateTaskStatusWhenCanceled(String taskId); | |
| 121 | + List<UserTask> getUserTaskListByReturn(String id); | |
| 114 | 122 | |
| 115 | 123 | /** |
| 116 | - * 更新 Task 拓展记录,并发送通知 | |
| 124 | + * 获取指定任务的子任务列表 | |
| 117 | 125 | * |
| 118 | - * @param task 任务实体 | |
| 126 | + * @param parentTaskId 父任务ID | |
| 127 | + * @return 子任务列表 | |
| 119 | 128 | */ |
| 120 | - void updateTaskExtAssign(Task task); | |
| 129 | + List<Task> getTaskListByParentTaskId(String parentTaskId); | |
| 121 | 130 | |
| 122 | 131 | /** |
| 123 | - * 获取任务 | |
| 132 | + * 通过任务 ID,查询任务名 Map | |
| 124 | 133 | * |
| 125 | - * @param id 任务编号 | |
| 126 | - * @return 任务 | |
| 134 | + * @param taskIds 任务 ID | |
| 135 | + * @return 任务 ID 与名字的 Map | |
| 127 | 136 | */ |
| 128 | - Task getTask(String id); | |
| 137 | + Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds); | |
| 138 | + | |
| 139 | + // ========== Update 写入相关方法 ========== | |
| 129 | 140 | |
| 130 | 141 | /** |
| 131 | - * 获取当前任务的可回退的 UserTask 集合 | |
| 142 | + * 通过任务 | |
| 132 | 143 | * |
| 133 | - * @param id 当前的任务 ID | |
| 134 | - * @return 可以回退的节点列表 | |
| 144 | + * @param userId 用户编号 | |
| 145 | + * @param reqVO 通过请求 | |
| 135 | 146 | */ |
| 136 | - List<UserTask> getUserTaskListByReturn(String id); | |
| 147 | + void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO); | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * 不通过任务 | |
| 151 | + * | |
| 152 | + * @param userId 用户编号 | |
| 153 | + * @param reqVO 不通过请求 | |
| 154 | + */ | |
| 155 | + void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); | |
| 156 | + | |
| 157 | + /** | |
| 158 | + * 将流程任务分配给指定用户 | |
| 159 | + * | |
| 160 | + * @param userId 用户编号 | |
| 161 | + * @param reqVO 分配请求 | |
| 162 | + */ | |
| 163 | + void transferTask(Long userId, BpmTaskTransferReqVO reqVO); | |
| 164 | + | |
| 165 | + /** | |
| 166 | + * 将指定流程实例的、进行中的流程任务,移动到结束节点 | |
| 167 | + * | |
| 168 | + * @param processInstanceId 流程编号 | |
| 169 | + */ | |
| 170 | + void moveTaskToEnd(String processInstanceId); | |
| 137 | 171 | |
| 138 | 172 | /** |
| 139 | 173 | * 将任务回退到指定的 targetDefinitionKey 位置 |
| ... | ... | @@ -167,20 +201,41 @@ public interface BpmTaskService { |
| 167 | 201 | */ |
| 168 | 202 | void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); |
| 169 | 203 | |
| 204 | + // ========== Event 事件相关方法 ========== | |
| 205 | + | |
| 170 | 206 | /** |
| 171 | - * 获取指定任务的子任务列表 | |
| 207 | + * 处理 Task 创建事件,目前是 | |
| 172 | 208 | * |
| 173 | - * @param parentTaskId 父任务ID | |
| 174 | - * @return 子任务列表 | |
| 209 | + * 1. 更新它的状态为审批中 | |
| 210 | + * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 | |
| 211 | + * | |
| 212 | + * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 | |
| 213 | + * | |
| 214 | + * @param task 任务实体 | |
| 175 | 215 | */ |
| 176 | - List<Task> getTaskListByParentTaskId(String parentTaskId); | |
| 216 | + void processTaskCreated(Task task); | |
| 177 | 217 | |
| 178 | 218 | /** |
| 179 | - * 通过任务 ID,查询任务名 Map | |
| 219 | + * 处理 Task 取消事件,目前是更新它的状态为已取消 | |
| 180 | 220 | * |
| 181 | - * @param taskIds 任务 ID | |
| 182 | - * @return 任务 ID 与名字的 Map | |
| 221 | + * @param taskId 任务的编号 | |
| 183 | 222 | */ |
| 184 | - Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds); | |
| 223 | + void processTaskCanceled(String taskId); | |
| 224 | + | |
| 225 | + /** | |
| 226 | + * 处理 Task 设置审批人事件,目前是发送审批消息 | |
| 227 | + * | |
| 228 | + * @param task 任务实体 | |
| 229 | + */ | |
| 230 | + void processTaskAssigned(Task task); | |
| 231 | + | |
| 232 | + /** | |
| 233 | + * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 | |
| 234 | + * | |
| 235 | + * @param processInstanceId 流程示例编号 | |
| 236 | + * @param taskDefineKey 任务 Key | |
| 237 | + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} | |
| 238 | + */ | |
| 239 | + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); | |
| 185 | 240 | |
| 186 | 241 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.bpm.service.task; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | -import cn.hutool.core.util.ArrayUtil; | |
| 5 | -import cn.hutool.core.util.IdUtil; | |
| 6 | -import cn.hutool.core.util.ObjectUtil; | |
| 7 | -import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 5 | +import cn.hutool.core.util.*; | |
| 6 | +import cn.hutool.extra.spring.SpringUtil; | |
| 8 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | 8 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 10 | 9 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 10 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | |
| 11 | 11 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 12 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 13 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | |
| 14 | 12 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| 15 | 13 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 16 | 14 | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; |
| 15 | +import cn.iocoder.yudao.module.bpm.enums.definition.*; | |
| 17 | 16 | import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; |
| 18 | -import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; | |
| 17 | +import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; | |
| 19 | 18 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; |
| 20 | 19 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| 21 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; | |
| 20 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; | |
| 21 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | |
| 22 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | |
| 22 | 23 | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |
| 23 | 24 | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; |
| 25 | +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | |
| 26 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 27 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 24 | 28 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 25 | 29 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 26 | 30 | import jakarta.annotation.Resource; |
| 27 | 31 | import jakarta.validation.Valid; |
| 28 | 32 | import lombok.extern.slf4j.Slf4j; |
| 29 | 33 | import org.flowable.bpmn.model.BpmnModel; |
| 34 | +import org.flowable.bpmn.model.EndEvent; | |
| 30 | 35 | import org.flowable.bpmn.model.FlowElement; |
| 31 | 36 | import org.flowable.bpmn.model.UserTask; |
| 32 | 37 | import org.flowable.engine.HistoryService; |
| ... | ... | @@ -45,7 +50,6 @@ import org.springframework.stereotype.Service; |
| 45 | 50 | import org.springframework.transaction.annotation.Transactional; |
| 46 | 51 | import org.springframework.transaction.support.TransactionSynchronization; |
| 47 | 52 | import org.springframework.transaction.support.TransactionSynchronizationManager; |
| 48 | -import org.springframework.util.Assert; | |
| 49 | 53 | |
| 50 | 54 | import java.util.*; |
| 51 | 55 | import java.util.stream.Stream; |
| ... | ... | @@ -53,6 +57,7 @@ import java.util.stream.Stream; |
| 53 | 57 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 54 | 58 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 55 | 59 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; |
| 60 | +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; | |
| 56 | 61 | |
| 57 | 62 | /** |
| 58 | 63 | * 流程任务实例 Service 实现类 |
| ... | ... | @@ -78,12 +83,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 78 | 83 | @Resource |
| 79 | 84 | private BpmProcessInstanceCopyService processInstanceCopyService; |
| 80 | 85 | @Resource |
| 81 | - private BpmModelService bpmModelService; | |
| 86 | + private BpmModelService modelService; | |
| 82 | 87 | @Resource |
| 83 | 88 | private BpmMessageService messageService; |
| 84 | 89 | |
| 85 | 90 | @Resource |
| 86 | 91 | private AdminUserApi adminUserApi; |
| 92 | + @Resource | |
| 93 | + private DeptApi deptApi; | |
| 94 | + | |
| 95 | + // ========== Query 查询相关方法 ========== | |
| 87 | 96 | |
| 88 | 97 | @Override |
| 89 | 98 | public PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) { |
| ... | ... | @@ -173,6 +182,165 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 173 | 182 | return tasks; |
| 174 | 183 | } |
| 175 | 184 | |
| 185 | + /** | |
| 186 | + * 校验任务是否存在,并且是否是分配给自己的任务 | |
| 187 | + * | |
| 188 | + * @param userId 用户 id | |
| 189 | + * @param taskId task id | |
| 190 | + */ | |
| 191 | + private Task validateTask(Long userId, String taskId) { | |
| 192 | + Task task = validateTaskExist(taskId); | |
| 193 | + // 为什么判断 assignee 非空的情况下? | |
| 194 | + // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 | |
| 195 | + if (StrUtil.isNotBlank(task.getAssignee()) | |
| 196 | + && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { | |
| 197 | + throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); | |
| 198 | + } | |
| 199 | + return task; | |
| 200 | + } | |
| 201 | + | |
| 202 | + private Task validateTaskExist(String id) { | |
| 203 | + Task task = getTask(id); | |
| 204 | + if (task == null) { | |
| 205 | + throw exception(TASK_NOT_EXISTS); | |
| 206 | + } | |
| 207 | + return task; | |
| 208 | + } | |
| 209 | + | |
| 210 | + @Override | |
| 211 | + public Task getTask(String id) { | |
| 212 | + return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); | |
| 213 | + } | |
| 214 | + | |
| 215 | + @Override | |
| 216 | + public HistoricTaskInstance getHistoricTask(String id) { | |
| 217 | + return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); | |
| 218 | + } | |
| 219 | + | |
| 220 | + @Override | |
| 221 | + public List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds) { | |
| 222 | + return historyService.createHistoricTaskInstanceQuery().taskIds(taskIds).includeTaskLocalVariables().list(); | |
| 223 | + } | |
| 224 | + | |
| 225 | + @Override | |
| 226 | + public List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) { | |
| 227 | + Assert.notNull(processInstanceId, "processInstanceId 不能为空"); | |
| 228 | + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active() | |
| 229 | + .includeTaskLocalVariables(); | |
| 230 | + if (BooleanUtil.isTrue(assigned)) { | |
| 231 | + taskQuery.taskAssigned(); | |
| 232 | + } else if (BooleanUtil.isFalse(assigned)) { | |
| 233 | + taskQuery.taskUnassigned(); | |
| 234 | + } | |
| 235 | + if (StrUtil.isNotEmpty(defineKey)) { | |
| 236 | + taskQuery.taskDefinitionKey(defineKey); | |
| 237 | + } | |
| 238 | + return taskQuery.list(); | |
| 239 | + } | |
| 240 | + | |
| 241 | + @Override | |
| 242 | + public List<UserTask> getUserTaskListByReturn(String id) { | |
| 243 | + // 1.1 校验当前任务 task 存在 | |
| 244 | + Task task = validateTaskExist(id); | |
| 245 | + // 1.2 根据流程定义获取流程模型信息 | |
| 246 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 247 | + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 248 | + if (source == null) { | |
| 249 | + throw exception(TASK_NOT_EXISTS); | |
| 250 | + } | |
| 251 | + | |
| 252 | + // 2.1 查询该任务的前置任务节点的 key 集合 | |
| 253 | + List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); | |
| 254 | + if (CollUtil.isEmpty(previousUserList)) { | |
| 255 | + return Collections.emptyList(); | |
| 256 | + } | |
| 257 | + // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 | |
| 258 | + previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); | |
| 259 | + return previousUserList; | |
| 260 | + } | |
| 261 | + | |
| 262 | + /** | |
| 263 | + * 获得所有子任务列表 | |
| 264 | + * | |
| 265 | + * @param parentTask 父任务 | |
| 266 | + * @return 所有子任务列表 | |
| 267 | + */ | |
| 268 | + private List<Task> getAllChildTaskList(Task parentTask) { | |
| 269 | + List<Task> result = new ArrayList<>(); | |
| 270 | + // 1. 递归获取子级 | |
| 271 | + Stack<Task> stack = new Stack<>(); | |
| 272 | + stack.push(parentTask); | |
| 273 | + // 2. 递归遍历 | |
| 274 | + for (int i = 0; i < Short.MAX_VALUE; i++) { | |
| 275 | + if (stack.isEmpty()) { | |
| 276 | + break; | |
| 277 | + } | |
| 278 | + // 2.1 获取子任务们 | |
| 279 | + Task task = stack.pop(); | |
| 280 | + List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); | |
| 281 | + // 2.2 如果非空,则添加到 stack 进一步递归 | |
| 282 | + if (CollUtil.isNotEmpty(childTaskList)) { | |
| 283 | + stack.addAll(childTaskList); | |
| 284 | + result.addAll(childTaskList); | |
| 285 | + } | |
| 286 | + } | |
| 287 | + return result; | |
| 288 | + } | |
| 289 | + | |
| 290 | + @Override | |
| 291 | + public List<Task> getTaskListByParentTaskId(String parentTaskId) { | |
| 292 | + String tableName = managementService.getTableName(TaskEntity.class); | |
| 293 | + // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 | |
| 294 | + String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; | |
| 295 | + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); | |
| 296 | + } | |
| 297 | + | |
| 298 | + /** | |
| 299 | + * 获取子任务个数 | |
| 300 | + * | |
| 301 | + * @param parentTaskId 父任务 ID | |
| 302 | + * @return 剩余子任务个数 | |
| 303 | + */ | |
| 304 | + private Long getTaskCountByParentTaskId(String parentTaskId) { | |
| 305 | + String tableName = managementService.getTableName(TaskEntity.class); | |
| 306 | + String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; | |
| 307 | + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); | |
| 308 | + } | |
| 309 | + | |
| 310 | + /** | |
| 311 | + * 获得任务根任务的父任务编号 | |
| 312 | + * | |
| 313 | + * @param task 任务 | |
| 314 | + * @return 根任务的父任务编号 | |
| 315 | + */ | |
| 316 | + private String getTaskRootParentId(Task task) { | |
| 317 | + if (task == null || task.getParentTaskId() == null) { | |
| 318 | + return null; | |
| 319 | + } | |
| 320 | + for (int i = 0; i < Short.MAX_VALUE; i++) { | |
| 321 | + Task parentTask = getTask(task.getParentTaskId()); | |
| 322 | + if (parentTask == null) { | |
| 323 | + return null; | |
| 324 | + } | |
| 325 | + if (parentTask.getParentTaskId() == null) { | |
| 326 | + return parentTask.getId(); | |
| 327 | + } | |
| 328 | + task = parentTask; | |
| 329 | + } | |
| 330 | + throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId())); | |
| 331 | + } | |
| 332 | + | |
| 333 | + @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); | |
| 340 | + } | |
| 341 | + | |
| 342 | + // ========== Update 写入相关方法 ========== | |
| 343 | + | |
| 176 | 344 | @Override |
| 177 | 345 | @Transactional(rollbackFor = Exception.class) |
| 178 | 346 | public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) { |
| ... | ... | @@ -211,6 +379,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 211 | 379 | // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 |
| 212 | 380 | if (CollUtil.isNotEmpty(reqVO.getVariables())) { |
| 213 | 381 | Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); |
| 382 | + // 修改表单的值需要存储到 ProcessInstance 变量 | |
| 383 | + runtimeService.setVariables(task.getProcessInstanceId(), variables); | |
| 214 | 384 | taskService.complete(task.getId(), variables, true); |
| 215 | 385 | } else { |
| 216 | 386 | taskService.complete(task.getId()); |
| ... | ... | @@ -244,7 +414,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 244 | 414 | |
| 245 | 415 | /** |
| 246 | 416 | * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务: |
| 247 | - * | |
| 417 | + * <p> | |
| 248 | 418 | * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批 |
| 249 | 419 | * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批 |
| 250 | 420 | * |
| ... | ... | @@ -277,11 +447,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 277 | 447 | taskService.resolveTask(parentTaskId); |
| 278 | 448 | // 3.1.2 更新流程任务 status |
| 279 | 449 | updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus()); |
| 280 | - // 3.2 情况二:处理向【向后】加签 | |
| 450 | + // 3.2 情况二:处理向【向后】加签 | |
| 281 | 451 | } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { |
| 282 | 452 | // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 |
| 283 | 453 | // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 |
| 284 | - Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); | |
| 454 | + Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); | |
| 285 | 455 | if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) { |
| 286 | 456 | return; |
| 287 | 457 | } |
| ... | ... | @@ -326,135 +496,59 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 326 | 496 | throw exception(PROCESS_INSTANCE_NOT_EXISTS); |
| 327 | 497 | } |
| 328 | 498 | |
| 329 | - // 2.1 更新流程实例为不通过 | |
| 499 | + // 2.1 更新流程任务为不通过 | |
| 330 | 500 | updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); |
| 331 | - // 2.2 添加评论 | |
| 501 | + // 2.2 添加流程评论 | |
| 332 | 502 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), |
| 333 | 503 | BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); |
| 504 | + // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 | |
| 505 | + // 疑问:为什么要标记未通过呢? | |
| 506 | + // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 | |
| 507 | + if (task.getParentTaskId() != null) { | |
| 508 | + String rootParentId = getTaskRootParentId(task); | |
| 509 | + updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(), | |
| 510 | + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); | |
| 511 | + taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), | |
| 512 | + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); | |
| 513 | + } | |
| 334 | 514 | |
| 335 | - // 3. 更新流程实例,审批不通过! | |
| 336 | - processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); | |
| 515 | + // 3. 根据不同的 RejectHandler 处理策略 | |
| 516 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 517 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 518 | + // 3.1 情况一:驳回到指定的任务节点 | |
| 519 | + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); | |
| 520 | + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { | |
| 521 | + String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); | |
| 522 | + Assert.notNull(returnTaskId, "回退的节点不能为空"); | |
| 523 | + returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) | |
| 524 | + .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); | |
| 525 | + return; | |
| 526 | + } | |
| 527 | + // 3.2 情况二:直接结束,审批不通过 | |
| 528 | + processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason()); // 标记不通过 | |
| 529 | + moveTaskToEnd(task.getProcessInstanceId()); // 结束流程 | |
| 337 | 530 | } |
| 338 | 531 | |
| 339 | 532 | /** |
| 340 | 533 | * 更新流程任务的 status 状态 |
| 341 | 534 | * |
| 342 | - * @param id 任务编号 | |
| 535 | + * @param id 任务编号 | |
| 343 | 536 | * @param status 状态 |
| 344 | 537 | */ |
| 345 | 538 | private void updateTaskStatus(String id, Integer status) { |
| 346 | - taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status); | |
| 539 | + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_STATUS, status); | |
| 347 | 540 | } |
| 348 | 541 | |
| 349 | 542 | /** |
| 350 | 543 | * 更新流程任务的 status 状态、reason 理由 |
| 351 | 544 | * |
| 352 | - * @param id 任务编号 | |
| 545 | + * @param id 任务编号 | |
| 353 | 546 | * @param status 状态 |
| 354 | 547 | * @param reason 理由(审批通过、审批不通过的理由) |
| 355 | 548 | */ |
| 356 | 549 | private void updateTaskStatusAndReason(String id, Integer status, String reason) { |
| 357 | 550 | updateTaskStatus(id, status); |
| 358 | - taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason); | |
| 359 | - } | |
| 360 | - | |
| 361 | - /** | |
| 362 | - * 校验任务是否存在,并且是否是分配给自己的任务 | |
| 363 | - * | |
| 364 | - * @param userId 用户 id | |
| 365 | - * @param taskId task id | |
| 366 | - */ | |
| 367 | - private Task validateTask(Long userId, String taskId) { | |
| 368 | - Task task = validateTaskExist(taskId); | |
| 369 | - if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { | |
| 370 | - throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); | |
| 371 | - } | |
| 372 | - return task; | |
| 373 | - } | |
| 374 | - | |
| 375 | - @Override | |
| 376 | - public void updateTaskStatusWhenCreated(Task task) { | |
| 377 | - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); | |
| 378 | - if (status != null) { | |
| 379 | - log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); | |
| 380 | - return; | |
| 381 | - } | |
| 382 | - updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); | |
| 383 | - } | |
| 384 | - | |
| 385 | - @Override | |
| 386 | - public void updateTaskStatusWhenCanceled(String taskId) { | |
| 387 | - Task task = getTask(taskId); | |
| 388 | - // 1. 可能只是活动,不是任务,所以查询不到 | |
| 389 | - if (task == null) { | |
| 390 | - log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); | |
| 391 | - return; | |
| 392 | - } | |
| 393 | - | |
| 394 | - // 2. 更新 task 状态 + 原因 | |
| 395 | - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); | |
| 396 | - if (BpmTaskStatusEnum.isEndStatus(status)) { | |
| 397 | - log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); | |
| 398 | - return; | |
| 399 | - } | |
| 400 | - updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); | |
| 401 | - // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 | |
| 402 | - } | |
| 403 | - | |
| 404 | - @Override | |
| 405 | - public void updateTaskExtAssign(Task task) { | |
| 406 | - // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 | |
| 407 | - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| 408 | - | |
| 409 | - @Override | |
| 410 | - public void afterCommit() { | |
| 411 | - if (StrUtil.isEmpty(task.getAssignee())) { | |
| 412 | - return; | |
| 413 | - } | |
| 414 | - ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); | |
| 415 | - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | |
| 416 | - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | |
| 417 | - } | |
| 418 | - | |
| 419 | - }); | |
| 420 | - } | |
| 421 | - | |
| 422 | - private Task validateTaskExist(String id) { | |
| 423 | - Task task = getTask(id); | |
| 424 | - if (task == null) { | |
| 425 | - throw exception(TASK_NOT_EXISTS); | |
| 426 | - } | |
| 427 | - return task; | |
| 428 | - } | |
| 429 | - | |
| 430 | - @Override | |
| 431 | - public Task getTask(String id) { | |
| 432 | - return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); | |
| 433 | - } | |
| 434 | - | |
| 435 | - private HistoricTaskInstance getHistoricTask(String id) { | |
| 436 | - return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); | |
| 437 | - } | |
| 438 | - | |
| 439 | - @Override | |
| 440 | - public List<UserTask> getUserTaskListByReturn(String id) { | |
| 441 | - // 1.1 校验当前任务 task 存在 | |
| 442 | - Task task = validateTaskExist(id); | |
| 443 | - // 1.2 根据流程定义获取流程模型信息 | |
| 444 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | |
| 445 | - FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 446 | - if (source == null) { | |
| 447 | - throw exception(TASK_NOT_EXISTS); | |
| 448 | - } | |
| 449 | - | |
| 450 | - // 2.1 查询该任务的前置任务节点的 key 集合 | |
| 451 | - List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); | |
| 452 | - if (CollUtil.isEmpty(previousUserList)) { | |
| 453 | - return Collections.emptyList(); | |
| 454 | - } | |
| 455 | - // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 | |
| 456 | - previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); | |
| 457 | - return previousUserList; | |
| 551 | + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_REASON, reason); | |
| 458 | 552 | } |
| 459 | 553 | |
| 460 | 554 | @Override |
| ... | ... | @@ -483,7 +577,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 483 | 577 | */ |
| 484 | 578 | private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { |
| 485 | 579 | // 1.1 获取流程模型信息 |
| 486 | - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 580 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); | |
| 487 | 581 | // 1.3 获取当前任务节点元素 |
| 488 | 582 | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); |
| 489 | 583 | // 1.3 获取跳转的节点元素 |
| ... | ... | @@ -529,7 +623,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 529 | 623 | updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); |
| 530 | 624 | }); |
| 531 | 625 | |
| 532 | - // 3. 执行驳回 | |
| 626 | + // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 | |
| 627 | + runtimeService.setVariable(currentTask.getProcessInstanceId(), | |
| 628 | + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); | |
| 629 | + | |
| 630 | + // 4. 执行驳回 | |
| 533 | 631 | runtimeService.createChangeActivityStateBuilder() |
| 534 | 632 | .processInstanceId(currentTask.getProcessInstanceId()) |
| 535 | 633 | .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多) |
| ... | ... | @@ -593,6 +691,35 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 593 | 691 | } |
| 594 | 692 | |
| 595 | 693 | @Override |
| 694 | + public void moveTaskToEnd(String processInstanceId) { | |
| 695 | + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null); | |
| 696 | + if (CollUtil.isEmpty(taskList)) { | |
| 697 | + return; | |
| 698 | + } | |
| 699 | + | |
| 700 | + // 1. 其它未结束的任务,直接取消 | |
| 701 | + // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? | |
| 702 | + // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 | |
| 703 | + taskList.forEach(task -> { | |
| 704 | + Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); | |
| 705 | + if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { | |
| 706 | + return; | |
| 707 | + } | |
| 708 | + processTaskCanceled(task.getId()); | |
| 709 | + }); | |
| 710 | + | |
| 711 | + // 2. 终止流程 | |
| 712 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); | |
| 713 | + List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); | |
| 714 | + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); | |
| 715 | + Assert.notNull(endEvent, "结束节点不能未空"); | |
| 716 | + runtimeService.createChangeActivityStateBuilder() | |
| 717 | + .processInstanceId(processInstanceId) | |
| 718 | + .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId()) | |
| 719 | + .changeState(); | |
| 720 | + } | |
| 721 | + | |
| 722 | + @Override | |
| 596 | 723 | @Transactional(rollbackFor = Exception.class) |
| 597 | 724 | public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) { |
| 598 | 725 | // 1. 获取和校验任务 |
| ... | ... | @@ -656,7 +783,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 656 | 783 | List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner |
| 657 | 784 | Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); |
| 658 | 785 | if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { |
| 659 | - List<AdminUserRespDTO> userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); | |
| 786 | + List<AdminUserRespDTO> userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); | |
| 660 | 787 | throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); |
| 661 | 788 | } |
| 662 | 789 | return taskEntity; |
| ... | ... | @@ -665,8 +792,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 665 | 792 | /** |
| 666 | 793 | * 创建加签子任务 |
| 667 | 794 | * |
| 668 | - * @param userIds 被加签的用户 ID | |
| 669 | - * @param taskEntity 被加签的任务 | |
| 795 | + * @param userIds 被加签的用户 ID | |
| 796 | + * @param taskEntity 被加签的任务 | |
| 670 | 797 | */ |
| 671 | 798 | private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) { |
| 672 | 799 | if (CollUtil.isEmpty(userIds)) { |
| ... | ... | @@ -695,7 +822,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 695 | 822 | // 2.1 向前加签,设置审批人 |
| 696 | 823 | if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { |
| 697 | 824 | task.setAssignee(assignee); |
| 698 | - // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 | |
| 825 | + // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 | |
| 699 | 826 | } else { |
| 700 | 827 | task.setOwner(assignee); |
| 701 | 828 | } |
| ... | ... | @@ -762,61 +889,203 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 762 | 889 | return task; |
| 763 | 890 | } |
| 764 | 891 | |
| 892 | + // ========== Event 事件相关方法 ========== | |
| 893 | + | |
| 894 | + @Override | |
| 895 | + public void processTaskCreated(Task task) { | |
| 896 | + // 1. 设置为待办中 | |
| 897 | + Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); | |
| 898 | + if (status != null) { | |
| 899 | + log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); | |
| 900 | + return; | |
| 901 | + } | |
| 902 | + updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); | |
| 903 | + | |
| 904 | + // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 | |
| 905 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); | |
| 906 | + if (processInstance == null) { | |
| 907 | + log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); | |
| 908 | + return; | |
| 909 | + } | |
| 910 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); | |
| 911 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 912 | + Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement); | |
| 913 | + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement); | |
| 914 | + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| 915 | + | |
| 916 | + @Override | |
| 917 | + public void afterCompletion(int transactionStatus) { | |
| 918 | + // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以 | |
| 919 | + // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时 | |
| 920 | + if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) { | |
| 921 | + return; | |
| 922 | + } | |
| 923 | + // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 | |
| 924 | + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { | |
| 925 | + if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { | |
| 926 | + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() | |
| 927 | + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); | |
| 928 | + } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { | |
| 929 | + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() | |
| 930 | + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); | |
| 931 | + } | |
| 932 | + // 特殊情况二:【自动审核】审批类型为自动通过、不通过 | |
| 933 | + } else { | |
| 934 | + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { | |
| 935 | + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() | |
| 936 | + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); | |
| 937 | + } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { | |
| 938 | + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() | |
| 939 | + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); | |
| 940 | + } | |
| 941 | + } | |
| 942 | + } | |
| 943 | + | |
| 944 | + }); | |
| 945 | + } | |
| 946 | + | |
| 765 | 947 | /** |
| 766 | - * 获得所有子任务列表 | |
| 767 | - * | |
| 768 | - * @param parentTask 父任务 | |
| 769 | - * @return 所有子任务列表 | |
| 948 | + * 重要补充说明:该方法目前主要有两个情况会调用到: | |
| 949 | + * <p> | |
| 950 | + * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 | |
| 951 | + * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 | |
| 770 | 952 | */ |
| 771 | - private List<Task> getAllChildTaskList(Task parentTask) { | |
| 772 | - List<Task> result = new ArrayList<>(); | |
| 773 | - // 1. 递归获取子级 | |
| 774 | - Stack<Task> stack = new Stack<>(); | |
| 775 | - stack.push(parentTask); | |
| 776 | - // 2. 递归遍历 | |
| 777 | - for (int i = 0; i < Short.MAX_VALUE; i++) { | |
| 778 | - if (stack.isEmpty()) { | |
| 779 | - break; | |
| 780 | - } | |
| 781 | - // 2.1 获取子任务们 | |
| 782 | - Task task = stack.pop(); | |
| 783 | - List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); | |
| 784 | - // 2.2 如果非空,则添加到 stack 进一步递归 | |
| 785 | - if (CollUtil.isNotEmpty(childTaskList)) { | |
| 786 | - stack.addAll(childTaskList); | |
| 787 | - result.addAll(childTaskList); | |
| 788 | - } | |
| 953 | + @Override | |
| 954 | + public void processTaskCanceled(String taskId) { | |
| 955 | + Task task = getTask(taskId); | |
| 956 | + // 1. 可能只是活动,不是任务,所以查询不到 | |
| 957 | + if (task == null) { | |
| 958 | + log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); | |
| 959 | + return; | |
| 789 | 960 | } |
| 790 | - return result; | |
| 961 | + | |
| 962 | + // 2. 更新 task 状态 + 原因 | |
| 963 | + Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); | |
| 964 | + if (BpmTaskStatusEnum.isEndStatus(status)) { | |
| 965 | + log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); | |
| 966 | + return; | |
| 967 | + } | |
| 968 | + updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); | |
| 969 | + // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 | |
| 791 | 970 | } |
| 792 | 971 | |
| 793 | 972 | @Override |
| 794 | - public List<Task> getTaskListByParentTaskId(String parentTaskId) { | |
| 795 | - String tableName = managementService.getTableName(TaskEntity.class); | |
| 796 | - // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 | |
| 797 | - String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; | |
| 798 | - return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); | |
| 799 | - } | |
| 973 | + public void processTaskAssigned(Task task) { | |
| 974 | + // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 | |
| 975 | + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| 800 | 976 | |
| 801 | - /** | |
| 802 | - * 获取子任务个数 | |
| 803 | - * | |
| 804 | - * @param parentTaskId 父任务 ID | |
| 805 | - * @return 剩余子任务个数 | |
| 806 | - */ | |
| 807 | - private Long getTaskCountByParentTaskId(String parentTaskId) { | |
| 808 | - String tableName = managementService.getTableName(TaskEntity.class); | |
| 809 | - String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; | |
| 810 | - return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); | |
| 977 | + @Override | |
| 978 | + public void afterCommit() { | |
| 979 | + if (StrUtil.isEmpty(task.getAssignee())) { | |
| 980 | + log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId()); | |
| 981 | + return; | |
| 982 | + } | |
| 983 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); | |
| 984 | + if (processInstance == null) { | |
| 985 | + log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); | |
| 986 | + return; | |
| 987 | + } | |
| 988 | + // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 | |
| 989 | + if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { | |
| 990 | + // 判断是否为回退或者驳回:如果是回退或者驳回不走这个策略 | |
| 991 | + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 | |
| 992 | + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), | |
| 993 | + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); | |
| 994 | + if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { | |
| 995 | + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); | |
| 996 | + if (bpmnModel == null) { | |
| 997 | + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); | |
| 998 | + return; | |
| 999 | + } | |
| 1000 | + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | |
| 1001 | + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); | |
| 1002 | + | |
| 1003 | + // 情况一:自动跳过 | |
| 1004 | + if (ObjectUtils.equalsAny(assignStartUserHandlerType, | |
| 1005 | + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | |
| 1006 | + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) | |
| 1007 | + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); | |
| 1008 | + return; | |
| 1009 | + } | |
| 1010 | + // 情况二:转交给部门负责人审批 | |
| 1011 | + if (ObjectUtils.equalsAny(assignStartUserHandlerType, | |
| 1012 | + BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { | |
| 1013 | + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | |
| 1014 | + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); | |
| 1015 | + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; | |
| 1016 | + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); | |
| 1017 | + // 找不到部门负责人的情况下,自动审批通过 | |
| 1018 | + // noinspection DataFlowIssue | |
| 1019 | + if (dept.getLeaderUserId() == null) { | |
| 1020 | + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) | |
| 1021 | + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); | |
| 1022 | + return; | |
| 1023 | + } | |
| 1024 | + // 找得到部门负责人的情况下,修改负责人 | |
| 1025 | + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { | |
| 1026 | + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() | |
| 1027 | + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) | |
| 1028 | + .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); | |
| 1029 | + return; | |
| 1030 | + } | |
| 1031 | + // 如果部门负责人是自己,还是自己审批吧~ | |
| 1032 | + } | |
| 1033 | + } | |
| 1034 | + } | |
| 1035 | + | |
| 1036 | + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | |
| 1037 | + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | |
| 1038 | + } | |
| 1039 | + | |
| 1040 | + }); | |
| 811 | 1041 | } |
| 812 | 1042 | |
| 813 | 1043 | @Override |
| 814 | - public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) { | |
| 815 | - if (CollUtil.isEmpty(taskIds)) { | |
| 816 | - return Collections.emptyMap(); | |
| 1044 | + @Transactional(rollbackFor = Exception.class) | |
| 1045 | + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) { | |
| 1046 | + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); | |
| 1047 | + if (processInstance == null) { | |
| 1048 | + log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); | |
| 1049 | + return; | |
| 817 | 1050 | } |
| 818 | - List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list(); | |
| 819 | - return convertMap(tasks, Task::getId, Task::getName); | |
| 1051 | + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey); | |
| 1052 | + // TODO 优化:未来需要考虑加签的情况 | |
| 1053 | + if (CollUtil.isEmpty(taskList)) { | |
| 1054 | + log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey); | |
| 1055 | + return; | |
| 1056 | + } | |
| 1057 | + | |
| 1058 | + taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { | |
| 1059 | + // 情况一:自动提醒 | |
| 1060 | + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType())) { | |
| 1061 | + messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() | |
| 1062 | + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) | |
| 1063 | + .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); | |
| 1064 | + return; | |
| 1065 | + } | |
| 1066 | + | |
| 1067 | + // 情况二:自动同意 | |
| 1068 | + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.APPROVE.getType())) { | |
| 1069 | + approveTask(Long.parseLong(task.getAssignee()), | |
| 1070 | + new BpmTaskApproveReqVO().setId(task.getId()).setReason(BpmReasonEnum.TIMEOUT_APPROVE.getReason())); | |
| 1071 | + return; | |
| 1072 | + } | |
| 1073 | + | |
| 1074 | + // 情况三:自动拒绝 | |
| 1075 | + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REJECT.getType())) { | |
| 1076 | + rejectTask(Long.parseLong(task.getAssignee()), | |
| 1077 | + new BpmTaskRejectReqVO().setId(task.getId()).setReason(BpmReasonEnum.REJECT_TASK.getReason())); | |
| 1078 | + } | |
| 1079 | + })); | |
| 1080 | + } | |
| 1081 | + | |
| 1082 | + /** | |
| 1083 | + * 获得自身的代理对象,解决 AOP 生效问题 | |
| 1084 | + * | |
| 1085 | + * @return 自己 | |
| 1086 | + */ | |
| 1087 | + private BpmTaskServiceImpl getSelf() { | |
| 1088 | + return SpringUtil.getBean(getClass()); | |
| 820 | 1089 | } |
| 821 | 1090 | |
| 822 | 1091 | } | ... | ... |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/bo/AlreadyRunApproveNodeRespBO.java
0 → 100644
| 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
| ... | ... | @@ -10,8 +10,8 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 10 | 10 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 11 | 11 | import org.flowable.bpmn.model.UserTask; |
| 12 | 12 | import org.flowable.engine.delegate.DelegateExecution; |
| 13 | +import org.junit.jupiter.api.BeforeEach; | |
| 13 | 14 | import org.junit.jupiter.api.Test; |
| 14 | -import org.mockito.InjectMocks; | |
| 15 | 15 | import org.mockito.Mock; |
| 16 | 16 | import org.mockito.Spy; |
| 17 | 17 | |
| ... | ... | @@ -34,15 +34,23 @@ import static org.mockito.Mockito.when; |
| 34 | 34 | */ |
| 35 | 35 | public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest { |
| 36 | 36 | |
| 37 | - @InjectMocks | |
| 38 | 37 | private BpmTaskCandidateInvoker taskCandidateInvoker; |
| 39 | 38 | |
| 40 | 39 | @Mock |
| 41 | 40 | private AdminUserApi adminUserApi; |
| 41 | + | |
| 42 | 42 | @Spy |
| 43 | - private BpmTaskCandidateStrategy strategy = new BpmTaskCandidateUserStrategy(); | |
| 43 | + private BpmTaskCandidateStrategy strategy ; | |
| 44 | + | |
| 44 | 45 | @Spy |
| 45 | - private List<BpmTaskCandidateStrategy> strategyList = Collections.singletonList(strategy); | |
| 46 | + private List<BpmTaskCandidateStrategy> strategyList ; | |
| 47 | + | |
| 48 | + @BeforeEach | |
| 49 | + public void setUp() { | |
| 50 | + strategy = new BpmTaskCandidateUserStrategy(adminUserApi); // 创建strategy实例 | |
| 51 | + strategyList = Collections.singletonList(strategy); // 创建strategyList | |
| 52 | + taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi); | |
| 53 | + } | |
| 46 | 54 | |
| 47 | 55 | @Test |
| 48 | 56 | public void testCalculateUsers() { | ... | ... |
yudao-server/pom.xml
| ... | ... | @@ -46,11 +46,11 @@ |
| 46 | 46 | <!-- <version>${revision}</version>--> |
| 47 | 47 | <!-- </dependency>--> |
| 48 | 48 | <!-- 工作流。默认注释,保证编译速度 --> |
| 49 | -<!-- <dependency>--> | |
| 50 | -<!-- <groupId>cn.iocoder.boot</groupId>--> | |
| 51 | -<!-- <artifactId>yudao-module-bpm-biz</artifactId>--> | |
| 52 | -<!-- <version>${revision}</version>--> | |
| 53 | -<!-- </dependency>--> | |
| 49 | + <dependency> | |
| 50 | + <groupId>cn.iocoder.boot</groupId> | |
| 51 | + <artifactId>yudao-module-bpm-biz</artifactId> | |
| 52 | + <version>${revision}</version> | |
| 53 | + </dependency> | |
| 54 | 54 | <!-- 支付服务。默认注释,保证编译速度 --> |
| 55 | 55 | <!-- <dependency>--> |
| 56 | 56 | <!-- <groupId>cn.iocoder.boot</groupId>--> | ... | ... |