Commit 76e1d0e6f789f23877915477ca4f073e28105b33

Authored by 芋道源码
Committed by Gitee
2 parents 78496665 f299bf8a

!978 BPM 仿钉钉/飞书模式的草稿 PR

Merge pull request !978 from 芋道源码/feature/bpm
Showing 97 changed files with 3810 additions and 657 deletions
... ... @@ -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
... ... @@ -13,6 +13,7 @@ import lombok.Getter;
13 13 @AllArgsConstructor
14 14 public enum BpmTaskStatusEnum {
15 15  
  16 + NOT_START(-1, "未开始"),
16 17 RUNNING(1, "审批中"),
17 18 APPROVE(2, "审批通过"),
18 19 REJECT(3, "审批不通过"),
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/package-info.java 0 → 100644
  1 +/**
  2 + * 基础包,放一些通用的 VO 类
  3 + */
  4 +package cn.iocoder.yudao.module.bpm.controller.admin.base;
... ...
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&lt;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>-->
... ...