Merge remote-tracking branch 'origin/master'
已修改75个文件
已删除11个文件
已重命名7个文件
已添加38个文件
| | |
| | | - Path=/admin-api/shasteel/** |
| | | filters: |
| | | - RewritePath=/admin-api/shasteel/v3/api-docs, /v3/api-docs |
| | | ## xmcpms 服务 |
| | | - id: xmcpms-admin-api # 路由的编号 |
| | | uri: grayLb://xmcpms-server |
| | | predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 |
| | | - Path=/admin-api/xmcpms/** |
| | | filters: |
| | | - RewritePath=/admin-api/xmcpms/v3/api-docs, /v3/api-docs |
| | | ## xmcsms 服务 |
| | | - id: xmcsms-admin-api # 路由的编号 |
| | | uri: grayLb://xmcsms-server |
| | | predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 |
| | | - Path=/admin-api/xmcsms/** |
| | | filters: |
| | | - RewritePath=/admin-api/xmcsms/v3/api-docs, /v3/api-docs |
| | | x-forwarded: |
| | | prefix-enabled: true # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 |
| | | |
| | |
| | | import net.sf.jsqlparser.expression.operators.relational.EqualsTo; |
| | | import net.sf.jsqlparser.expression.operators.relational.ExpressionList; |
| | | import net.sf.jsqlparser.expression.operators.relational.InExpression; |
| | | import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | |
| | | return deptExpression; |
| | | } |
| | | // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?) |
| | | return new Parenthesis(new OrExpression(deptExpression, userExpression)); |
| | | return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression)); |
| | | } |
| | | |
| | | private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) { |
| | |
| | | } |
| | | // 拼接条件 |
| | | return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), |
| | | // new Parenthesis(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new)))); |
| | | new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new))); |
| | | // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号 |
| | | new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new)))); |
| | | } |
| | | |
| | | private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { |
| | |
| | | import cn.hutool.core.collection.CollectionUtil; |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import com.google.common.collect.ImmutableMap; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | |
| | | import java.util.*; |
| | | import java.util.function.*; |
| | |
| | | return new ArrayList<>(); |
| | | } |
| | | return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); |
| | | } |
| | | |
| | | public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) { |
| | | if (ArrayUtil.isEmpty(from)) { |
| | | return new PageResult<>(from.getTotal()); |
| | | } |
| | | return new PageResult<>(convertList(from.getList(), func), from.getTotal()); |
| | | } |
| | | |
| | | public static <T, U> List<U> convertListByFlatMap(Collection<T> from, |
| | |
| | | return valueFunc.apply(t); |
| | | } |
| | | |
| | | public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, |
| | | public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) { |
| | | if (CollUtil.isEmpty(from)) { |
| | | return null; |
| | | } |
| | | assert from.size() > 0; // 断言,避免告警 |
| | | return from.stream().min(Comparator.comparing(valueFunc)).get(); |
| | | } |
| | | |
| | | public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc, |
| | | BinaryOperator<V> accumulator) { |
| | | return getSumValue(from, valueFunc, accumulator, null); |
| | | } |
| | |
| | | } |
| | | |
| | | public static <T> List<T> newArrayList(List<List<T>> list) { |
| | | return list.stream().flatMap(Collection::stream).collect(Collectors.toList()); |
| | | return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); |
| | | } |
| | | |
| | | } |
| | |
| | | return new PageResult<>(list, source.getTotal()); |
| | | } |
| | | |
| | | public static void copyProperties(Object source, Object target) { |
| | | if (source == null || target == null) { |
| | | return; |
| | | } |
| | | BeanUtil.copyProperties(source, target, false); |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | public interface DictTypeConstants { |
| | | |
| | | // String TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型 |
| | | // String TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本 |
| | | |
| | | } |
| | |
| | | "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); |
| | | ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); |
| | | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); |
| | | ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); |
| | | |
| | | // ========== 流程定义 1-009-003-000 ========== |
| | | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); |
| | |
| | | ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在"); |
| | | ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中"); |
| | | ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); |
| | | ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置"); |
| | | ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在"); |
| | | ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置"); |
| | | ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在"); |
| | | ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); |
| | | |
| | | // ========== 流程任务 1-009-005-000 ========== |
| | | ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); |
| | | ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); |
| | | ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作"); |
| | | ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在"); |
| | | ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); |
| | | ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退回任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); |
| | | ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人"); |
| | | ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在"); |
| | | ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在"); |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * BPM 边界事件 (boundary event) 自定义类型枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmBoundaryEventType { |
| | | |
| | | USER_TASK_TIMEOUT(1,"用户任务超时"); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | | |
| | | public static BpmBoundaryEventType typeOf(Integer type) { |
| | | return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * BPM 表单权限的枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmFieldPermissionEnum { |
| | | |
| | | READ(1, "只读"), |
| | | WRITE(2, "可编辑"), |
| | | NONE(3, "隐藏"); |
| | | |
| | | /** |
| | | * 权限 |
| | | */ |
| | | private final Integer permission; |
| | | /** |
| | | * 名字 |
| | | */ |
| | | private final String name; |
| | | |
| | | public static BpmFieldPermissionEnum valueOf(Integer permission) { |
| | | return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values()); |
| | | } |
| | | |
| | | } |
| | |
| | | /** |
| | | * BPM 模型的表单类型的枚举 |
| | | * |
| | | * @author iailab |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * BPM 模型的类型的枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmModelTypeEnum implements IntArrayValuable { |
| | | |
| | | BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/ |
| | | SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器 |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray(); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| | |
| | | /** |
| | | * BPM 流程监听器的类型 |
| | | * |
| | | * @author iailab |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | |
| | | /** |
| | | * BPM 流程监听器的值类型 |
| | | * |
| | | * @author iailab |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * 仿钉钉的流程器设计器条件节点的条件类型 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmSimpleModeConditionType implements IntArrayValuable { |
| | | |
| | | EXPRESSION(1, "条件表达式"), |
| | | RULE(2, "条件规则"); |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray(); |
| | | |
| | | private final Integer type; |
| | | |
| | | private final String name; |
| | | |
| | | public static BpmSimpleModeConditionType valueOf(Integer type) { |
| | | return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); |
| | | } |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * 仿钉钉的流程器设计器的模型节点类型 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmSimpleModelNodeType implements IntArrayValuable { |
| | | |
| | | // 0 ~ 1 开始和结束 |
| | | START_NODE(0, "开始", "startEvent"), |
| | | END_NODE(1, "结束", "endEvent"), |
| | | |
| | | // 10 ~ 49 各种节点 |
| | | START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定 |
| | | APPROVE_NODE(11, "审批人", "userTask"), |
| | | COPY_NODE(12, "抄送人", "serviceTask"), |
| | | |
| | | // 50 ~ 条件分支 |
| | | CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 |
| | | CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), |
| | | PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"), |
| | | INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"), |
| | | ; |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | | private final String bpmnType; |
| | | |
| | | /** |
| | | * 判断是否为分支节点 |
| | | * |
| | | * @param type 节点类型 |
| | | */ |
| | | public static boolean isBranchNode(Integer type) { |
| | | return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) |
| | | || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) |
| | | || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type); |
| | | } |
| | | |
| | | public static BpmSimpleModelNodeType valueOf(Integer type) { |
| | | return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); |
| | | } |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * BPM 多人审批方式的枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable { |
| | | |
| | | RANDOM(1, "随机挑选一人审批", null), |
| | | RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例) |
| | | ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人) |
| | | SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批 |
| | | |
| | | /** |
| | | * 审批方式 |
| | | */ |
| | | private final Integer method; |
| | | /** |
| | | * 名字 |
| | | */ |
| | | private final String name; |
| | | /** |
| | | * 完成表达式 |
| | | */ |
| | | private final String completionCondition; |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray(); |
| | | |
| | | public static BpmUserTaskApproveMethodEnum valueOf(Integer method) { |
| | | return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); |
| | | } |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * 用户任务的审批类型枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable { |
| | | |
| | | USER(1), // 人工审批 |
| | | AUTO_APPROVE(2), // 自动通过 |
| | | AUTO_REJECT(3); // 自动拒绝 |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray(); |
| | | |
| | | private final Integer type; |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.Getter; |
| | | import lombok.RequiredArgsConstructor; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * BPM 用户任务的审批人为空时,处理类型枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @RequiredArgsConstructor |
| | | @Getter |
| | | public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable { |
| | | |
| | | APPROVE(1), // 自动通过 |
| | | REJECT(2), // 自动拒绝 |
| | | ASSIGN_USER(3), // 指定人员审批 |
| | | ASSIGN_ADMIN(4), // 转交给流程管理员 |
| | | ; |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray(); |
| | | |
| | | private final Integer type; |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.Getter; |
| | | import lombok.RequiredArgsConstructor; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * BPM 用户任务的审批人与发起人相同时,处理类型枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @RequiredArgsConstructor |
| | | @Getter |
| | | public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable { |
| | | |
| | | START_USER_AUDIT(1), // 由发起人对自己审批 |
| | | SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 |
| | | TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray(); |
| | | |
| | | private final Integer type; |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * BPM 用户任务拒绝处理类型枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmUserTaskRejectHandlerType implements IntArrayValuable { |
| | | |
| | | FINISH_PROCESS_INSTANCE(1, "终止流程"), |
| | | RETURN_USER_TASK(2, "驳回到指定任务节点"); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray(); |
| | | |
| | | public static BpmUserTaskRejectHandlerType typeOf(Integer type) { |
| | | return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); |
| | | } |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.definition; |
| | | |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * 用户任务超时处理类型枚举 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable { |
| | | |
| | | REMINDER(1,"自动提醒"), |
| | | APPROVE(2, "自动同意"), |
| | | REJECT(3, "自动拒绝"); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray(); |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 |
| | | PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 |
| | | TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 |
| | | TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 |
| | | TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 |
| | | |
| | | /** |
| | | * 短信模板的标识 |
| | |
| | | package com.iailab.module.bpm.enums.task; |
| | | |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import com.iailab.framework.common.util.object.ObjectUtils; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | |
| | | @AllArgsConstructor |
| | | public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { |
| | | |
| | | NOT_START(-1, "未开始"), |
| | | RUNNING(1, "审批中"), |
| | | APPROVE(2, "审批通过"), |
| | | REJECT(3, "审批不通过"), |
| | |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static boolean isRejectStatus(Integer status) { |
| | | return REJECT.getStatus().equals(status); |
| | | } |
| | | |
| | | public static boolean isProcessEndStatus(Integer status) { |
| | | return ObjectUtils.equalsAny(status, |
| | | APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); |
| | | } |
| | | |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.enums.task; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * 流程实例/任务的的处理原因枚举 |
| | | * |
| | | * @author iailab |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmReasonEnum { |
| | | |
| | | // ========== 流程实例的独有原因 ========== |
| | | |
| | | REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 |
| | | CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 |
| | | CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 |
| | | |
| | | // ========== 流程任务的独有原因 ========== |
| | | |
| | | CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 |
| | | TIMEOUT_APPROVE("审批超时,系统自动通过"), |
| | | TIMEOUT_REJECT("审批超时,系统自动不通过"), |
| | | ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), |
| | | ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), |
| | | ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), |
| | | ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), |
| | | ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), |
| | | ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"), |
| | | APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), |
| | | APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), |
| | | ; |
| | | |
| | | private final String reason; |
| | | |
| | | /** |
| | | * 格式化理由 |
| | | * |
| | | * @param args 参数 |
| | | * @return 理由 |
| | | */ |
| | | public String format(Object... args) { |
| | | return StrUtil.format(reason, args); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.enums.task; |
| | | |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import com.iailab.framework.common.util.object.ObjectUtils; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | |
| | | @AllArgsConstructor |
| | | public enum BpmTaskStatusEnum { |
| | | |
| | | NOT_START(-1, "未开始"), |
| | | RUNNING(1, "审批中"), |
| | | APPROVE(2, "审批通过"), |
| | | REJECT(3, "审批不通过"), |
| | | CANCEL(4, "已取消"), |
| | | |
| | | RETURN(5, "已退回"), |
| | | DELEGATE(6, "委派中"), |
| | | |
| | | /** |
| | | * 使用场景: |
| | |
| | | */ |
| | | private final String name; |
| | | |
| | | public static boolean isRejectStatus(Integer status) { |
| | | return REJECT.getStatus().equals(status); |
| | | } |
| | | |
| | | /** |
| | | * 判断该状态是否已经处于 End 最终状态 |
| | | * <p> |
| | |
| | | RETURN.getStatus(), APPROVING.getStatus()); |
| | | } |
| | | |
| | | public static boolean isCancelStatus(Integer status) { |
| | | return ObjUtil.equal(status, CANCEL.getStatus()); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | /** |
| | | * 基础包,放一些通用的 VO 类 |
| | | */ |
| | | package com.iailab.module.bpm.controller.admin.base; |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.base.user; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "用户精简信息 VO") |
| | | @Data |
| | | public class UserSimpleBaseVO { |
| | | |
| | | @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Long id; |
| | | @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") |
| | | private String nickname; |
| | | @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") |
| | | private String avatar; |
| | | |
| | | @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Long deptId; |
| | | @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") |
| | | private String deptName; |
| | | |
| | | } |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/update-sort-batch") |
| | | @Operation(summary = "批量更新流程分类的排序") |
| | | @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3") |
| | | @PreAuthorize("@ss.hasPermission('bpm:category:update')") |
| | | public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) { |
| | | categoryService.updateCategorySortBatch(ids); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "删除流程分类") |
| | | @Parameter(name = "id", description = "编号", required = true) |
| | |
| | | @Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项") |
| | | public CommonResult<List<BpmCategoryRespVO>> getCategorySimpleList() { |
| | | List<BpmCategoryDO> list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus()); |
| | | BpmCategoryRespVO bpmCategoryRespVO = new BpmCategoryRespVO(); |
| | | BpmCategoryRespVO bpmCategoryRespVO1 = bpmCategoryRespVO.setName("123"); |
| | | list.sort(Comparator.comparingInt(BpmCategoryDO::getSort)); |
| | | return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId()) |
| | | .setName(category.getName()).setCode(category.getCode()))); |
| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.iailab.framework.common.pojo.CommonResult; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.collection.CollectionUtils; |
| | | import com.iailab.framework.common.util.io.IoUtils; |
| | | import com.iailab.framework.common.util.json.JsonUtils; |
| | | import com.iailab.framework.common.util.object.BeanUtils; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.*; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| | | import com.iailab.module.bpm.convert.definition.BpmModelConvert; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | |
| | | import com.iailab.module.bpm.service.definition.BpmModelService; |
| | | import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.validation.Valid; |
| | | import java.io.IOException; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.*; |
| | | import java.util.stream.Stream; |
| | | |
| | | import static com.iailab.framework.common.pojo.CommonResult.success; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.*; |
| | | import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管理后台 - 流程模型") |
| | | @RestController |
| | |
| | | @Resource |
| | | private BpmProcessDefinitionService processDefinitionService; |
| | | |
| | | @GetMapping("/page") |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @GetMapping("/list") |
| | | @Operation(summary = "获得模型分页") |
| | | public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) { |
| | | PageResult<Model> pageResult = modelService.getModelPage(pageVO); |
| | | if (CollUtil.isEmpty(pageResult.getList())) { |
| | | return success(PageResult.empty(pageResult.getTotal())); |
| | | @Parameter(name = "name", description = "模型名称", example = "芋艿") |
| | | public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) { |
| | | List<Model> list = modelService.getModelList(name); |
| | | if (CollUtil.isEmpty(list)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 拼接数据 |
| | | // 获得 Form 表单 |
| | | Set<Long> formIds = convertSet(pageResult.getList(), model -> { |
| | | BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); |
| | | Set<Long> formIds = convertSet(list, model -> { |
| | | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| | | return metaInfo != null ? metaInfo.getFormId() : null; |
| | | }); |
| | | Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); |
| | | // 获得 Category Map |
| | | Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap( |
| | | convertSet(pageResult.getList(), Model::getCategory)); |
| | | convertSet(list, Model::getCategory)); |
| | | // 获得 Deployment Map |
| | | Set<String> deploymentIds = new HashSet<>(); |
| | | pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId())); |
| | | Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds); |
| | | Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap( |
| | | convertSet(list, Model::getDeploymentId)); |
| | | // 获得 ProcessDefinition Map |
| | | List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); |
| | | List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds( |
| | | deploymentMap.keySet()); |
| | | Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); |
| | | return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap)); |
| | | // 获得 User Map |
| | | Set<Long> userIds = convertSetByFlatMap(list, model -> { |
| | | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| | | return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); |
| | | }); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| | | return success(BpmModelConvert.INSTANCE.buildModelList(list, |
| | | formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "新建模型") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:create')") |
| | | public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) { |
| | | return success(modelService.createModel(createRetVO, null)); |
| | | public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) { |
| | | return success(modelService.createModel(createRetVO)); |
| | | } |
| | | |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "修改模型") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| | | public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) { |
| | | modelService.updateModel(modelVO); |
| | | public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) { |
| | | modelService.updateModel(getLoginUserId(), modelVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/import") |
| | | @Operation(summary = "导入模型") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:import')") |
| | | public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException { |
| | | BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class); |
| | | // 读取文件 |
| | | String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false); |
| | | return success(modelService.createModel(createReqVO, bpmnXml)); |
| | | @PutMapping("/update-sort-batch") |
| | | @Operation(summary = "批量修改模型排序") |
| | | @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") |
| | | public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) { |
| | | modelService.updateModelSortBatch(getLoginUserId(), ids); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/deploy") |
| | |
| | | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:deploy')") |
| | | public CommonResult<Boolean> deployModel(@RequestParam("id") String id) { |
| | | modelService.deployModel(id); |
| | | modelService.deployModel(getLoginUserId(), id); |
| | | return success(true); |
| | | } |
| | | |
| | |
| | | @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| | | public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) { |
| | | modelService.updateModelState(reqVO.getId(), reqVO.getState()); |
| | | modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState()); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/update-bpmn") |
| | | @Operation(summary = "修改模型的 BPMN") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| | | public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) { |
| | | modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml()); |
| | | return success(true); |
| | | } |
| | | |
| | |
| | | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:delete')") |
| | | public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) { |
| | | modelService.deleteModel(id); |
| | | modelService.deleteModel(getLoginUserId(), id); |
| | | return success(true); |
| | | } |
| | | |
| | | // ========== 仿钉钉/飞书的精简模型 ========= |
| | | |
| | | @GetMapping("/simple/get") |
| | | @Operation(summary = "获得仿钉钉流程设计模型") |
| | | @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") |
| | | public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){ |
| | | return success(modelService.getSimpleModel(modelId)); |
| | | } |
| | | |
| | | @PostMapping("/simple/update") |
| | | @Operation(summary = "保存仿钉钉流程设计模型") |
| | | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| | | public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) { |
| | | modelService.updateSimpleModel(getLoginUserId(), reqVO); |
| | | return success(Boolean.TRUE); |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; |
| | | import com.iailab.module.bpm.service.definition.BpmCategoryService; |
| | | import com.iailab.module.bpm.service.definition.BpmFormService; |
| | | import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; |
| | |
| | | |
| | | import static com.iailab.framework.common.pojo.CommonResult.success; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管理后台 - 流程定义") |
| | | @RestController |
| | |
| | | @Operation(summary = "获得流程定义列表") |
| | | @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举 |
| | | public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList( |
| | | @RequestParam("suspensionState") Integer suspensionState) { |
| | | List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState); |
| | | @RequestParam("suspensionState") Integer suspensionState, @RequestParam(value = "categoryId", required = false) String categoryId) { |
| | | // 1.1 获得开启的流程定义 |
| | | List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState, categoryId); |
| | | if (CollUtil.isEmpty(list)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 获得 BpmProcessDefinitionInfoDO Map |
| | | // 1.2 移除不可见的流程定义 |
| | | Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap( |
| | | convertSet(list, ProcessDefinition::getId)); |
| | | Long userId = getLoginUserId(); |
| | | list.removeIf(processDefinition -> { |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId()); |
| | | return processDefinitionInfo == null // 不存在 |
| | | || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见 |
| | | || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起 |
| | | }); |
| | | |
| | | // 2. 拼接 VO 返回 |
| | | return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList( |
| | | list, null, processDefinitionMap, null, null)); |
| | | } |
| | |
| | | } |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); |
| | | BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); |
| | | List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); |
| | | return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( |
| | | processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList)); |
| | | processDefinition, null, processDefinitionInfo, null, null, bpmnModel)); |
| | | } |
| | | |
| | | } |
| | |
| | | return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class)); |
| | | } |
| | | |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.definition.vo.model; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotEmpty; |
| | | |
| | | @Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO") |
| | | @Data |
| | | public class BpmModeUpdateBpmnReqVO { |
| | | |
| | | @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @NotEmpty(message = "流程编号不能为空") |
| | | private String id; |
| | | |
| | | @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotEmpty(message = "BPMN XML 不能为空") |
| | | private String bpmnXml; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.definition.vo.model; |
| | | |
| | | import com.iailab.framework.common.validation.InEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import org.hibernate.validator.constraints.URL; |
| | | |
| | | import javax.validation.constraints.NotEmpty; |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * BPM 流程 MetaInfo Response DTO |
| | | * 主要用于 { Model#setMetaInfo(String)} 的存储 |
| | | * |
| | | * 最终,它的字段和 |
| | | * {@link com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} |
| | | * 是一致的 |
| | | * |
| | | * @author iailab |
| | | */ |
| | | @Data |
| | | public class BpmModelMetaInfoVO { |
| | | |
| | | @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") |
| | | @NotEmpty(message = "流程图标不能为空") |
| | | @URL(message = "流程图标格式不正确") |
| | | private String icon; |
| | | |
| | | @Schema(description = "流程描述", example = "我是描述") |
| | | private String description; |
| | | |
| | | @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @InEnum(BpmModelTypeEnum.class) |
| | | @NotNull(message = "流程类型不能为空") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @InEnum(BpmModelFormTypeEnum.class) |
| | | @NotNull(message = "表单类型不能为空") |
| | | private Integer formType; |
| | | @Schema(description = "表单编号", example = "1024") |
| | | private Long formId; // formType 为 NORMAL 使用,必须非空 |
| | | @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create") |
| | | private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 |
| | | @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view") |
| | | private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 |
| | | |
| | | @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| | | @NotNull(message = "是否可见不能为空") |
| | | private Boolean visible; |
| | | |
| | | @Schema(description = "可发起用户编号数组", example = "[1,2,3]") |
| | | private List<Long> startUserIds; |
| | | |
| | | @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") |
| | | @NotEmpty(message = "可管理用户编号数组不能为空") |
| | | private List<Long> managerUserIds; |
| | | |
| | | @Schema(description = "排序", example = "1") |
| | | private Long sort; // 创建时,后端自动生成 |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.controller.admin.definition.vo.model; |
| | | |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管理后台 - 流程模型 Response VO") |
| | | @Data |
| | | public class BpmModelRespVO { |
| | | public class BpmModelRespVO extends BpmModelMetaInfoVO { |
| | | |
| | | @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private String id; |
| | | |
| | | @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_iailab") |
| | | @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") |
| | | private String key; |
| | | |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | private String name; |
| | | |
| | | @Schema(description = "流程图标", example = "https://www.baidu.com/iailab.jpg") |
| | | @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") |
| | | private String icon; |
| | | |
| | | @Schema(description = "流程描述", example = "我是描述") |
| | | private String description; |
| | | |
| | | @Schema(description = "流程分类编码", example = "1") |
| | | private String category; |
| | | @Schema(description = "流程分类名字", example = "请假") |
| | | private String categoryName; |
| | | |
| | | @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") |
| | | private Integer formType; |
| | | |
| | | @Schema(description = "表单编号", example = "1024") |
| | | private Long formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 |
| | | @Schema(description = "表单名字", example = "请假表单") |
| | | private String formName; |
| | | |
| | | @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create") |
| | | private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 |
| | | @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view") |
| | | private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 |
| | | |
| | | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private LocalDateTime createTime; |
| | |
| | | @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private String bpmnXml; |
| | | |
| | | @Schema(description = "可发起的用户数组") |
| | | private List<UserSimpleBaseVO> startUsers; |
| | | |
| | | /** |
| | | * 最新部署的流程定义 |
| | | */ |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.definition.vo.model; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotEmpty; |
| | | |
| | | @Schema(description = "管理后台 - 流程模型的保存 Request VO") |
| | | @Data |
| | | public class BpmModelSaveReqVO extends BpmModelMetaInfoVO { |
| | | |
| | | @Schema(description = "编号", example = "1024") |
| | | private String id; |
| | | |
| | | @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") |
| | | @NotEmpty(message = "流程标识不能为空") |
| | | private String key; |
| | | |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | @NotEmpty(message = "流程名称不能为空") |
| | | private String name; |
| | | |
| | | @Schema(description = "流程分类", example = "1") |
| | | private String category; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.definition.vo.model.simple; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonInclude; |
| | | import com.iailab.framework.common.validation.InEnum; |
| | | import com.iailab.module.bpm.enums.definition.*; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.Valid; |
| | | import javax.validation.constraints.NotEmpty; |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO") |
| | | @Data |
| | | @JsonInclude(JsonInclude.Include.NON_NULL) |
| | | public class BpmSimpleModelNodeVO { |
| | | |
| | | @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1") |
| | | @NotEmpty(message = "模型节点编号不能为空") |
| | | private String id; |
| | | |
| | | @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "模型节点类型不能为空") |
| | | @InEnum(BpmSimpleModelNodeType.class) |
| | | private Integer type; |
| | | |
| | | @Schema(description = "模型节点名称", example = "领导审批") |
| | | private String name; |
| | | |
| | | @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") |
| | | private String showText; |
| | | |
| | | @Schema(description = "子节点") |
| | | private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个 |
| | | |
| | | @Schema(description = "条件节点") |
| | | private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用 |
| | | |
| | | @Schema(description = "条件类型", example = "1") |
| | | @InEnum(BpmSimpleModeConditionType.class) |
| | | private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE |
| | | |
| | | @Schema(description = "条件表达式", example = "${day>3}") |
| | | private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE |
| | | |
| | | @Schema(description = "是否默认条件", example = "true") |
| | | private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE |
| | | /** |
| | | * 条件组 |
| | | */ |
| | | private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE |
| | | |
| | | @Schema(description = "候选人策略", example = "30") |
| | | @InEnum(BpmTaskCandidateStrategyEnum.class) |
| | | private Integer candidateStrategy; // 用于审批,抄送节点 |
| | | |
| | | @Schema(description = "候选人参数") |
| | | private String candidateParam; // 用于审批,抄送节点 |
| | | |
| | | @Schema(description = "审批节点类型", example = "1") |
| | | @InEnum(BpmUserTaskApproveTypeEnum.class) |
| | | private Integer approveType; // 用于审批节点 |
| | | |
| | | @Schema(description = "多人审批方式", example = "1") |
| | | @InEnum(BpmUserTaskApproveMethodEnum.class) |
| | | private Integer approveMethod; // 用于审批节点 |
| | | |
| | | @Schema(description = "通过比例", example = "100") |
| | | private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置 |
| | | |
| | | @Schema(description = "表单权限", example = "[]") |
| | | private List<Map<String, String>> fieldsPermission; |
| | | |
| | | @Schema(description = "操作按钮设置", example = "[]") |
| | | private List<OperationButtonSetting> buttonsSetting; // 用于审批节点 |
| | | |
| | | /** |
| | | * 审批节点拒绝处理 |
| | | */ |
| | | private RejectHandler rejectHandler; |
| | | |
| | | /** |
| | | * 审批节点超时处理 |
| | | */ |
| | | private TimeoutHandler timeoutHandler; |
| | | |
| | | @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") |
| | | @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) |
| | | private Integer assignStartUserHandlerType; |
| | | |
| | | /** |
| | | * 空处理策略 |
| | | */ |
| | | private AssignEmptyHandler assignEmptyHandler; |
| | | |
| | | @Schema(description = "审批节点拒绝处理策略") |
| | | @Data |
| | | public static class RejectHandler { |
| | | |
| | | @Schema(description = "拒绝处理类型", example = "1") |
| | | @InEnum(BpmUserTaskRejectHandlerType.class) |
| | | private Integer type; |
| | | |
| | | @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1") |
| | | private String returnNodeId; |
| | | } |
| | | |
| | | @Schema(description = "审批节点超时处理策略") |
| | | @Valid |
| | | @Data |
| | | public static class TimeoutHandler { |
| | | |
| | | @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") |
| | | @NotNull(message = "是否开启超时处理不能为空") |
| | | private Boolean enable; |
| | | |
| | | @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "任务超时未处理的行为不能为空") |
| | | @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class) |
| | | private Integer type; |
| | | |
| | | @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H") |
| | | @NotEmpty(message = "超时时间不能为空") |
| | | private String timeDuration; |
| | | |
| | | @Schema(description = "最大提醒次数", example = "1") |
| | | private Integer maxRemindCount; |
| | | |
| | | } |
| | | |
| | | @Schema(description = "空处理策略") |
| | | @Data |
| | | @Valid |
| | | public static class AssignEmptyHandler { |
| | | |
| | | @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "空处理类型不能为空") |
| | | @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class) |
| | | private Integer type; |
| | | |
| | | @Schema(description = "指定人员审批的用户编号数组", example = "1") |
| | | private List<Long> userIds; |
| | | |
| | | } |
| | | |
| | | @Schema(description = "操作按钮设置") |
| | | @Data |
| | | @Valid |
| | | public static class OperationButtonSetting { |
| | | |
| | | // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。 |
| | | @Schema(description = "按钮 Id", example = "1") |
| | | private Integer id; |
| | | |
| | | @Schema(description = "显示名称", example = "审批") |
| | | private String displayName; |
| | | |
| | | @Schema(description = "是否启用", example = "true") |
| | | private Boolean enable; |
| | | } |
| | | |
| | | @Schema(description = "条件组") |
| | | @Data |
| | | @Valid |
| | | public static class ConditionGroups { |
| | | |
| | | @Schema(description = "条件组下的条件关系是否为与关系", example = "true") |
| | | @NotNull(message = "条件关系不能为空") |
| | | private Boolean and; |
| | | |
| | | @Schema(description = "条件组下的条件", example = "[]") |
| | | @NotEmpty(message = "条件不能为空") |
| | | private List<Condition> conditions; |
| | | } |
| | | |
| | | @Schema(description = "条件") |
| | | @Data |
| | | @Valid |
| | | public static class Condition { |
| | | |
| | | @Schema(description = "条件下的规则关系是否为与关系", example = "true") |
| | | @NotNull(message = "规则关系不能为空") |
| | | private Boolean and; |
| | | |
| | | @Schema(description = "条件下的规则", example = "[]") |
| | | @NotEmpty(message = "规则不能为空") |
| | | private List<ConditionRule> rules; |
| | | } |
| | | |
| | | @Schema(description = "条件规则") |
| | | @Data |
| | | @Valid |
| | | public static class ConditionRule { |
| | | |
| | | @Schema(description = "运行符号", example = "==") |
| | | @NotEmpty(message = "运行符号不能为空") |
| | | private String opCode; |
| | | |
| | | @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId") |
| | | @NotEmpty(message = "运算符左边的值不能为空") |
| | | private String leftSide; |
| | | |
| | | @Schema(description = "运算符右边的值", example = "1") |
| | | @NotEmpty(message = "运算符右边的值不能为空") |
| | | private String rightSide; |
| | | } |
| | | |
| | | // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.definition.vo.model.simple; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.Valid; |
| | | import javax.validation.constraints.NotEmpty; |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | // TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下; |
| | | @Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") |
| | | @Data |
| | | public class BpmSimpleModelUpdateReqVO { |
| | | |
| | | @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotEmpty(message = "流程模型编号不能为空") |
| | | private String id; // 对应 Flowable act_re_model 表 ID_ 字段 |
| | | |
| | | @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotNull(message = "仿钉钉流程设计模型对象不能为空") |
| | | @Valid |
| | | private BpmSimpleModelNodeVO simpleModel; |
| | | |
| | | } |
| | |
| | | @Schema(description = "版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer version; |
| | | |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | private String name; |
| | | |
| | | @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iailab") |
| | | @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") |
| | | private String key; |
| | | |
| | | @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.baidu.com/iailab.jpg") |
| | | @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") |
| | | private String icon; |
| | | |
| | | @Schema(description = "流程描述", example = "我是描述") |
| | |
| | | private String category; |
| | | @Schema(description = "流程分类名字", example = "请假") |
| | | private String categoryName; |
| | | |
| | | @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | private Integer modelType; // 参见 BpmModelTypeEnum 枚举类 |
| | | |
| | | @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") |
| | | private Integer formType; |
| | |
| | | @Schema(description = "BPMN XML") |
| | | private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回 |
| | | |
| | | @Schema(description = "发起用户需要选择审批人的任务数组") |
| | | private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回 |
| | | @Schema(description = "SIMPLE 设计器模型数据 json 格式") |
| | | private String simpleModel; // 非必须返回 |
| | | |
| | | @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long sort; |
| | | |
| | | @Schema(description = "BPMN UserTask 用户任务") |
| | | @Data |
对比新文件 |
| | |
| | | ### 请求 /bpm/process-instance/get-bpmn 接口 => 成功 |
| | | GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 |
| | | Content-Type: application/json |
| | | tenant-id: 1 |
| | | Authorization: Bearer {{token}} |
| | | |
| | | ### 请求 /bpm/process-instance/get-bpmn 接口 => 失败 |
| | | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 |
| | | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911 |
| | | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4 |
| | | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932 |
| | | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b |
| | | GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b |
| | | Content-Type: application/json |
| | | tenant-id: 1 |
| | | Authorization: Bearer {{token}} |
| | |
| | | import com.iailab.framework.common.pojo.CommonResult; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.*; |
| | | import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.service.definition.BpmCategoryService; |
| | | import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | |
| | | @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请” |
| | | @RestController |
| | |
| | | processInstance.getProcessDefinitionId()); |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( |
| | | processInstance.getProcessDefinitionId()); |
| | | String bpmnXml = BpmnModelUtils.getBpmnXml( |
| | | processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId())); |
| | | AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); |
| | | DeptRespDTO dept = null; |
| | | if (startUser != null) { |
| | | if (startUser != null && startUser.getDeptId() != null) { |
| | | dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); |
| | | } |
| | | return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, |
| | | processDefinition, processDefinitionInfo, bpmnXml, startUser, dept)); |
| | | processDefinition, processDefinitionInfo, startUser, dept)); |
| | | } |
| | | |
| | | @DeleteMapping("/cancel-by-start-user") |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get-approval-detail") |
| | | @Operation(summary = "获得审批详情") |
| | | @Parameter(name = "id", description = "流程实例的编号", required = true) |
| | | @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") |
| | | public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { |
| | | return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); |
| | | } |
| | | |
| | | @GetMapping("/get-bpmn-model-view") |
| | | @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") |
| | | @Parameter(name = "id", description = "流程实例的编号", required = true) |
| | | public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) { |
| | | return success(processInstanceService.getProcessInstanceBpmnModelView(id)); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.iailab.framework.common.util.collection.MapUtils; |
| | | import com.iailab.framework.common.util.date.DateUtils; |
| | | import com.iailab.framework.common.util.object.BeanUtils; |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; |
| | | import com.iailab.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceCopyService; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | | import com.iailab.module.bpm.service.task.BpmTaskService; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | import java.util.stream.Stream; |
| | | |
| | | import static com.iailab.framework.common.pojo.CommonResult.success; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertListByFlatMap; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.*; |
| | | import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | |
| | | @Tag(name = "管理后台 - 流程实例抄送") |
| | | @RestController |
| | |
| | | private BpmProcessInstanceCopyService processInstanceCopyService; |
| | | @Resource |
| | | private BpmProcessInstanceService processInstanceService; |
| | | @Resource |
| | | private BpmTaskService taskService; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | |
| | | } |
| | | |
| | | // 拼接返回 |
| | | Map<String, String> taskNameMap = taskService.getTaskNameByTaskIds( |
| | | convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId)); |
| | | Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( |
| | | convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), |
| | | copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator())))); |
| | | return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> { |
| | | MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname())); |
| | | MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname())); |
| | | MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName); |
| | | return success(convertPage(pageResult, copy -> { |
| | | BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class); |
| | | MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()), |
| | | user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class))); |
| | | MapUtils.findAndThen(userMap, copy.getStartUserId(), |
| | | user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class))); |
| | | MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), |
| | | processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); |
| | | return copyVO; |
| | | })); |
| | | } |
| | | |
| | |
| | | |
| | | import static com.iailab.framework.common.pojo.CommonResult.success; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.*; |
| | | import static com.iailab.framework.web.core.util.WebFrameworkUtils.getLoginUserId; |
| | | import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | |
| | | @Tag(name = "管理后台 - 流程任务实例") |
| | | @RestController |
| | |
| | | @PreAuthorize("@ss.hasPermission('bpm:task:query')") |
| | | public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId( |
| | | @RequestParam("processInstanceId") String processInstanceId) { |
| | | List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); |
| | | List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true); |
| | | if (CollUtil.isEmpty(taskList)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 拼接数据 |
| | | HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId); |
| | | // 获得 User 和 Dept Map |
| | | Set<Long> userIds = convertSetByFlatMap(taskList, task -> |
| | | Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); |
| | | userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| | | Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( |
| | | convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); |
| | | // 获得 Form Map |
| | | Map<Long, BpmFormDO> formMap = formService.getFormMap( |
| | | convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); |
| | | return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, |
| | | return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, |
| | | formMap, userMap, deptMap)); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @GetMapping("/list-by-return") |
| | | @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") |
| | | @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮") |
| | | @Parameter(name = "taskId", description = "当前任务ID", required = true) |
| | | @PreAuthorize("@ss.hasPermission('bpm:task:update')") |
| | | public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) { |
| | |
| | | } |
| | | |
| | | @PutMapping("/return") |
| | | @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") |
| | | @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮") |
| | | @PreAuthorize("@ss.hasPermission('bpm:task:update')") |
| | | public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) { |
| | | taskService.returnTask(getLoginUserId(), reqVO); |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/copy") |
| | | @Operation(summary = "抄送任务") |
| | | @PreAuthorize("@ss.hasPermission('bpm:task:update')") |
| | | public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) { |
| | | taskService.copyTask(getLoginUserId(), reqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/list-by-parent-task-id") |
| | | @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 |
| | | @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.cc; |
| | | |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | |
| | | @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") |
| | | private Long startUserId; |
| | | @Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | private String startUserName; |
| | | @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private UserSimpleBaseVO startUser; |
| | | |
| | | @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233") |
| | | private String processInstanceId; |
| | | @Schema(description = "流程实例的名称") |
| | | @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试") |
| | | private String processInstanceName; |
| | | @Schema(description = "流程实例的发起时间") |
| | | @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private LocalDateTime processInstanceStartTime; |
| | | |
| | | @Schema(description = "发起抄送的任务编号") |
| | | @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private String activityId; |
| | | @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private String activityName; |
| | | |
| | | @Schema(description = "流程活动的编号") |
| | | private String taskId; |
| | | @Schema(description = "发起抄送的任务名称") |
| | | private String taskName; |
| | | |
| | | @Schema(description = "抄送人") |
| | | private String creator; |
| | | @Schema(description = "抄送人昵称") |
| | | private String creatorName; |
| | | @Schema(description = "抄送人意见") |
| | | private String reason; |
| | | |
| | | @Schema(description = "抄送时间") |
| | | @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private UserSimpleBaseVO createUser; |
| | | |
| | | @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.instance; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.fasterxml.jackson.annotation.JsonIgnore; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.AssertTrue; |
| | | import java.util.Map; |
| | | |
| | | @Schema(description = "管理后台 - 审批详情 Request VO") |
| | | @Data |
| | | public class BpmApprovalDetailReqVO { |
| | | |
| | | @Schema(description = "流程定义的编号", example = "1024") |
| | | private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID |
| | | |
| | | @Schema(description = "流程变量") |
| | | private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测 |
| | | |
| | | @Schema(description = "流程实例的编号", example = "1024") |
| | | private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID |
| | | |
| | | // TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。 |
| | | @Schema(description = "流程活动编号", example = "StartUserNode") |
| | | private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限; |
| | | |
| | | @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") |
| | | private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限 |
| | | |
| | | @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") |
| | | @JsonIgnore |
| | | public boolean isValidProcessParam() { |
| | | return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.instance; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnore; |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | |
| | | @Schema(description = "管理后台 - 审批详情 Response VO") |
| | | @Data |
| | | public class BpmApprovalDetailRespVO { |
| | | |
| | | @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 |
| | | |
| | | @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private List<ActivityNode> activityNodes; |
| | | |
| | | @Schema(description = "表单字段权限") |
| | | private Map<String, String> formFieldsPermission; |
| | | |
| | | @Schema(description = "待办任务") |
| | | private BpmTaskRespVO todoTask; |
| | | |
| | | /** |
| | | * 所属流程定义信息 |
| | | */ |
| | | private BpmProcessDefinitionRespVO processDefinition; |
| | | |
| | | /** |
| | | * 所属流程实例信息 |
| | | */ |
| | | private BpmProcessInstanceRespVO processInstance; |
| | | |
| | | @Schema(description = "活动节点信息") |
| | | @Data |
| | | public static class ActivityNode { |
| | | |
| | | @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") |
| | | private String id; |
| | | |
| | | @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") |
| | | private String name; |
| | | |
| | | @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 |
| | | |
| | | @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | private Integer status; // 参见 BpmTaskStatusEnum 枚举 |
| | | |
| | | @Schema(description = "节点的开始时间") |
| | | private LocalDateTime startTime; |
| | | @Schema(description = "节点的结束时间") |
| | | private LocalDateTime endTime; |
| | | |
| | | @Schema(description = "审批节点的任务信息") |
| | | private List<ActivityNodeTask> tasks; |
| | | |
| | | @Schema(description = "候选人策略", example = "35") |
| | | private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选 |
| | | |
| | | @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") |
| | | @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers |
| | | private List<Long> candidateUserIds; |
| | | |
| | | @Schema(description = "候选人用户列表") |
| | | private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 |
| | | |
| | | } |
| | | |
| | | @Schema(description = "活动节点的任务信息") |
| | | @Data |
| | | public static class ActivityNodeTask { |
| | | |
| | | @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private String id; |
| | | |
| | | @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") |
| | | @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser |
| | | private Long owner; |
| | | |
| | | @Schema(description = "任务所属人", example = "1024") |
| | | private UserSimpleBaseVO ownerUser; |
| | | |
| | | @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") |
| | | @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser |
| | | private Long assignee; |
| | | |
| | | @Schema(description = "任务分配人", example = "2048") |
| | | private UserSimpleBaseVO assigneeUser; |
| | | |
| | | @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer status; // 参见 BpmTaskStatusEnum 枚举 |
| | | |
| | | @Schema(description = "审批意见", example = "同意") |
| | | private String reason; |
| | | |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.instance; |
| | | |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | @Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO") |
| | | @Data |
| | | public class BpmProcessInstanceBpmnModelViewRespVO { |
| | | |
| | | // ========== 基本信息 ========== |
| | | |
| | | @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private BpmProcessInstanceRespVO processInstance; |
| | | |
| | | @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private List<BpmTaskRespVO> tasks; |
| | | |
| | | @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private String bpmnXml; |
| | | |
| | | @Schema(description = "SIMPLE 模型") |
| | | private BpmSimpleModelNodeVO simpleModel; |
| | | |
| | | // ========== 进度信息 ========== |
| | | |
| | | @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask |
| | | |
| | | @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow |
| | | |
| | | @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow |
| | | |
| | | @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Set<String> rejectedTaskActivityIds; // 只包括 UserTask |
| | | |
| | | } |
| | |
| | | |
| | | import static com.iailab.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | |
| | | @Schema(description = "管理后台 - 流程实例抄送的分页 Request VO") |
| | | @Data |
| | | public class BpmProcessInstanceCopyPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "流程名称", example = "平台") |
| | | @Schema(description = "流程名称", example = "芋道") |
| | | private String processInstanceName; |
| | | |
| | | @Schema(description = "创建时间") |
| | |
| | | |
| | | import static com.iailab.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | |
| | | @Schema(description = "管理后台 - 流程实例分页 Request VO") |
| | | @Data |
| | | public class BpmProcessInstancePageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "流程名称", example = "平台") |
| | | @Schema(description = "流程名称", example = "芋道") |
| | | private String name; |
| | | |
| | | @Schema(description = "流程定义的编号", example = "2048") |
| | | private String processDefinitionId; |
| | | @Schema(description = "流程定义的标识", example = "2048") |
| | | private String processDefinitionKey; // 精准匹配 |
| | | |
| | | @Schema(description = "流程实例的状态", example = "1") |
| | | @InEnum(BpmProcessInstanceStatusEnum.class) |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.instance; |
| | | |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | |
| | | @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private String id; |
| | | |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | private String name; |
| | | |
| | | @Schema(description = "流程分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | |
| | | /** |
| | | * 发起流程的用户 |
| | | */ |
| | | private User startUser; |
| | | private UserSimpleBaseVO startUser; |
| | | |
| | | @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") |
| | | private String processDefinitionId; |
| | |
| | | */ |
| | | private List<Task> tasks; // 仅在流程实例分页才返回 |
| | | |
| | | @Schema(description = "用户信息") |
| | | @Data |
| | | public static class User { |
| | | |
| | | @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Long id; |
| | | @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "iailab") |
| | | private String nickname; |
| | | |
| | | @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Long deptId; |
| | | @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") |
| | | private String deptName; |
| | | |
| | | } |
| | | |
| | | @Schema(description = "流程任务") |
| | | @Data |
| | | public static class Task { |
| | |
| | | @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private String id; |
| | | |
| | | @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | private String name; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.task; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotEmpty; |
| | | import java.util.Collection; |
| | | |
| | | @Schema(description = "管理后台 - 抄送流程任务的 Request VO") |
| | | @Data |
| | | public class BpmTaskCopyReqVO { |
| | | |
| | | @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @NotEmpty(message = "任务编号不能为空") |
| | | private String id; |
| | | |
| | | @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]") |
| | | @NotEmpty(message = "抄送用户不能为空") |
| | | private Collection<Long> copyUserIds; |
| | | |
| | | @Schema(description = "抄送意见", example = "帮忙看看!") |
| | | private String reason; |
| | | } |
| | |
| | | package com.iailab.module.bpm.controller.admin.task.vo.task; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnore; |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | |
| | | @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private String id; |
| | | |
| | | @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | private String name; |
| | | |
| | | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| | |
| | | @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| | | private String reason; |
| | | |
| | | @Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") |
| | | @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser |
| | | private Long owner; |
| | | /** |
| | | * 负责人的用户信息 |
| | | */ |
| | | private BpmProcessInstanceRespVO.User ownerUser; |
| | | private UserSimpleBaseVO ownerUser; |
| | | |
| | | @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") |
| | | @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser |
| | | private Long assignee; |
| | | /** |
| | | * 审核的用户信息 |
| | | */ |
| | | private BpmProcessInstanceRespVO.User assigneeUser; |
| | | private UserSimpleBaseVO assigneeUser; |
| | | |
| | | @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") |
| | | private String taskDefinitionKey; |
| | |
| | | @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private String parentTaskId; |
| | | @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask") |
| | | private List<BpmTaskRespVO> children; |
| | | private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务 |
| | | |
| | | @Schema(description = "表单编号", example = "1024") |
| | | private Long formId; |
| | | @Schema(description = "表单名字", example = "请假表单") |
| | | private String formName; |
| | | @Schema(description = "表单的配置-JSON 字符串") |
| | | @Schema(description = "表单的配置,JSON 字符串") |
| | | private String formConf; |
| | | @Schema(description = "表单项的数组") |
| | | private List<String> formFields; |
| | | @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Map<String, Object> formVariables; |
| | | @Schema(description = "操作按钮设置值") |
| | | private Map<Integer, OperationButtonSetting> buttonsSetting; |
| | | |
| | | @Data |
| | | @Schema(description = "流程实例") |
| | |
| | | @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private String id; |
| | | |
| | | @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") |
| | | @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") |
| | | private String name; |
| | | |
| | | @Schema(description = "提交时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| | |
| | | /** |
| | | * 发起人的用户信息 |
| | | */ |
| | | private BpmProcessInstanceRespVO.User startUser; |
| | | private UserSimpleBaseVO startUser; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | @Schema(description = "操作按钮设置") |
| | | public static class OperationButtonSetting { |
| | | |
| | | @Schema(description = "显示名称", example = "审批") |
| | | private String displayName; |
| | | |
| | | @Schema(description = "是否启用", example = "true") |
| | | private Boolean enable; |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.convert.definition; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.collection.CollectionUtils; |
| | | import com.iailab.framework.common.util.date.DateUtils; |
| | | import com.iailab.framework.common.util.json.JsonUtils; |
| | | import com.iailab.framework.common.util.object.BeanUtils; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | | import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import org.flowable.common.engine.impl.db.SuspensionState; |
| | | import org.flowable.engine.repository.Deployment; |
| | | import org.flowable.engine.repository.Model; |
| | |
| | | import org.mapstruct.Mapper; |
| | | import org.mapstruct.factory.Mappers; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; |
| | | |
| | | /** |
| | | * 流程模型 Convert |
| | | * |
| | | * @author yunlongn |
| | | * @author hou |
| | | */ |
| | | @Mapper |
| | | public interface BpmModelConvert { |
| | | |
| | | BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); |
| | | |
| | | default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult, |
| | | default List<BpmModelRespVO> buildModelList(List<Model> list, |
| | | Map<Long, BpmFormDO> formMap, |
| | | Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap, |
| | | Map<String, ProcessDefinition> processDefinitionMap) { |
| | | List<BpmModelRespVO> list = CollectionUtils.convertList(pageResult.getList(), model -> { |
| | | BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); |
| | | Map<String, BpmCategoryDO> categoryMap, |
| | | Map<String, Deployment> deploymentMap, |
| | | Map<String, ProcessDefinition> processDefinitionMap, |
| | | Map<Long, AdminUserRespDTO> userMap) { |
| | | List<BpmModelRespVO> result = convertList(list, model -> { |
| | | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); |
| | | BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; |
| | | BpmCategoryDO category = categoryMap.get(model.getCategory()); |
| | | Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; |
| | | ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; |
| | | return buildModel0(model, metaInfo, form, category, deployment, processDefinition); |
| | | ProcessDefinition processDefinition = model.getDeploymentId() != null ? |
| | | processDefinitionMap.get(model.getDeploymentId()) : null; |
| | | List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; |
| | | return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); |
| | | }); |
| | | return new PageResult<>(list, pageResult.getTotal()); |
| | | // 排序 |
| | | result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort)); |
| | | return result; |
| | | } |
| | | |
| | | default BpmModelRespVO buildModel(Model model, |
| | | byte[] bpmnBytes) { |
| | | BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); |
| | | BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null); |
| | | default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) { |
| | | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); |
| | | BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); |
| | | if (ArrayUtil.isNotEmpty(bpmnBytes)) { |
| | | modelVO.setBpmnXml(new String(bpmnBytes)); |
| | | modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); |
| | | } |
| | | return modelVO; |
| | | } |
| | | |
| | | default BpmModelRespVO buildModel0(Model model, |
| | | BpmModelMetaInfoRespDTO metaInfo, BpmFormDO form, BpmCategoryDO category, |
| | | Deployment deployment, ProcessDefinition processDefinition) { |
| | | BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, |
| | | Deployment deployment, ProcessDefinition processDefinition, |
| | | List<AdminUserRespDTO> startUsers) { |
| | | BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) |
| | | .setKey(model.getKey()).setCategory(model.getCategory()) |
| | | .setCreateTime(DateUtils.of(model.getCreateTime())); |
| | | // Form |
| | | if (metaInfo != null) { |
| | | modelRespVO.setFormType(metaInfo.getFormType()).setFormId(metaInfo.getFormId()) |
| | | .setFormCustomCreatePath(metaInfo.getFormCustomCreatePath()) |
| | | .setFormCustomViewPath(metaInfo.getFormCustomViewPath()); |
| | | modelRespVO.setIcon(metaInfo.getIcon()).setDescription(metaInfo.getDescription()); |
| | | } |
| | | BeanUtils.copyProperties(metaInfo, modelRespVO); |
| | | if (form != null) { |
| | | modelRespVO.setFormId(form.getId()).setFormName(form.getName()); |
| | | modelRespVO.setFormName(form.getName()); |
| | | } |
| | | // Category |
| | | if (category != null) { |
| | |
| | | modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); |
| | | } |
| | | } |
| | | // User |
| | | modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)); |
| | | return modelRespVO; |
| | | } |
| | | |
| | | default void copyToCreateModel(Model model, BpmModelCreateReqVO bean) { |
| | | model.setName(bean.getName()); |
| | | model.setKey(bean.getKey()); |
| | | model.setMetaInfo(buildMetaInfoStr(null, |
| | | null, bean.getDescription(), |
| | | null, null, null, null)); |
| | | default void copyToModel(Model model, BpmModelSaveReqVO reqVO) { |
| | | model.setName(reqVO.getName()); |
| | | model.setKey(reqVO.getKey()); |
| | | model.setCategory(reqVO.getCategory()); |
| | | model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); |
| | | } |
| | | |
| | | default void copyToUpdateModel(Model model, BpmModelUpdateReqVO bean) { |
| | | model.setName(bean.getName()); |
| | | model.setCategory(bean.getCategory()); |
| | | model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model), |
| | | bean.getIcon(), bean.getDescription(), |
| | | bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath())); |
| | | default BpmModelMetaInfoVO parseMetaInfo(Model model) { |
| | | BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); |
| | | if (vo == null) { |
| | | return null; |
| | | } |
| | | |
| | | default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, |
| | | String icon, String description, |
| | | Integer formType, Long formId, String formCustomCreatePath, String formCustomViewPath) { |
| | | if (metaInfo == null) { |
| | | metaInfo = new BpmModelMetaInfoRespDTO(); |
| | | if (vo.getManagerUserIds() == null) { |
| | | vo.setManagerUserIds(Collections.emptyList()); |
| | | } |
| | | // 只有非空,才进行设置,避免更新时的覆盖 |
| | | if (StrUtil.isNotEmpty(icon)) { |
| | | metaInfo.setIcon(icon); |
| | | if (vo.getStartUserIds() == null) { |
| | | vo.setStartUserIds(Collections.emptyList()); |
| | | } |
| | | if (StrUtil.isNotEmpty(description)) { |
| | | metaInfo.setDescription(description); |
| | | // 如果为空,兜底处理,使用 createTime 创建时间 |
| | | if (vo.getSort() == null) { |
| | | vo.setSort(model.getCreateTime().getTime()); |
| | | } |
| | | if (Objects.nonNull(formType)) { |
| | | metaInfo.setFormType(formType); |
| | | metaInfo.setFormId(formId); |
| | | metaInfo.setFormCustomCreatePath(formCustomCreatePath); |
| | | metaInfo.setFormCustomViewPath(formCustomViewPath); |
| | | } |
| | | return JsonUtils.toJsonString(metaInfo); |
| | | } |
| | | |
| | | default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) { |
| | | return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); |
| | | return vo; |
| | | } |
| | | |
| | | } |
| | |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.bpmn.model.UserTask; |
| | | import org.flowable.common.engine.impl.db.SuspensionState; |
| | | import org.flowable.engine.repository.Deployment; |
| | | import org.flowable.engine.repository.ProcessDefinition; |
| | |
| | | import org.mapstruct.MappingTarget; |
| | | import org.mapstruct.factory.Mappers; |
| | | |
| | | import java.util.Comparator; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | |
| | | Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap, |
| | | Map<Long, BpmFormDO> formMap, |
| | | Map<String, BpmCategoryDO> categoryMap) { |
| | | return CollectionUtils.convertList(list, definition -> { |
| | | List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> { |
| | | Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class); |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class); |
| | | BpmFormDO form = null; |
| | |
| | | form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class); |
| | | } |
| | | BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); |
| | | return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null); |
| | | return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null); |
| | | }); |
| | | // 排序 |
| | | result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort)); |
| | | return result; |
| | | } |
| | | |
| | | default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, |
| | |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo, |
| | | BpmFormDO form, |
| | | BpmCategoryDO category, |
| | | BpmnModel bpmnModel, |
| | | List<UserTask> startUserSelectUserTaskList) { |
| | | BpmnModel bpmnModel) { |
| | | BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class); |
| | | respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); |
| | | // Deployment |
| | |
| | | // BpmnModel |
| | | if (bpmnModel != null) { |
| | | respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); |
| | | respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class)); |
| | | } |
| | | return respVO; |
| | | } |
| | |
| | | package com.iailab.module.bpm.convert.task; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.collection.MapUtils; |
| | | import com.iailab.framework.common.util.collection.SetUtils; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.framework.common.util.object.BeanUtils; |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| | | import com.iailab.module.bpm.convert.definition.BpmProcessDefinitionConvert; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import com.iailab.module.bpm.event.BpmProcessInstanceStatusEvent; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.history.HistoricProcessInstance; |
| | | import org.flowable.engine.repository.ProcessDefinition; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.flowable.task.api.Task; |
| | | import org.flowable.task.api.history.HistoricTaskInstance; |
| | | import org.mapstruct.Mapper; |
| | | import org.mapstruct.Mapping; |
| | | import org.mapstruct.MappingTarget; |
| | | import org.mapstruct.factory.Mappers; |
| | | |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | |
| | | |
| | | /** |
| | | * 流程实例 Convert |
| | | * |
| | | * @author iailab |
| | | * @author 芋道源码 |
| | | */ |
| | | @Mapper |
| | | public interface BpmProcessInstanceConvert { |
| | |
| | | if (userMap != null) { |
| | | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); |
| | | if (startUser != null) { |
| | | respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); |
| | | respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| | | MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); |
| | | } |
| | | } |
| | |
| | | |
| | | default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, |
| | | ProcessDefinition processDefinition, |
| | | BpmProcessDefinitionInfoDO processDefinitionExt, |
| | | String bpmnXml, |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo, |
| | | AdminUserRespDTO startUser, |
| | | DeptRespDTO dept) { |
| | | BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); |
| | | respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)); |
| | | respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); |
| | | respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)) |
| | | .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); |
| | | // definition |
| | | respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); |
| | | copyTo(processDefinitionExt, respVO.getProcessDefinition()); |
| | | respVO.getProcessDefinition().setBpmnXml(bpmnXml); |
| | | copyTo(processDefinitionInfo, respVO.getProcessDefinition()); |
| | | // user |
| | | if (startUser != null) { |
| | | respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); |
| | | respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| | | if (dept != null) { |
| | | respVO.getStartUser().setDeptName(dept.getName()); |
| | | } |
| | |
| | | @Mapping(source = "from.id", target = "to.id", ignore = true) |
| | | void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); |
| | | |
| | | default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, HistoricProcessInstance instance, Integer status) { |
| | | return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) |
| | | .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); |
| | | } |
| | | |
| | | default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {; |
| | | default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) { |
| | | return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) |
| | | .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); |
| | | } |
| | |
| | | .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); |
| | | } |
| | | |
| | | default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, |
| | | List<HistoricTaskInstance> taskInstances, |
| | | BpmnModel bpmnModel, |
| | | BpmSimpleModelNodeVO simpleModel, |
| | | Set<String> unfinishedTaskActivityIds, |
| | | Set<String> finishedTaskActivityIds, |
| | | Set<String> finishedSequenceFlowActivityIds, |
| | | Set<String> rejectTaskActivityIds, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO(); |
| | | // 基本信息 |
| | | respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o |
| | | .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) |
| | | .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap))); |
| | | respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class) |
| | | .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) |
| | | .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)) |
| | | .setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)))); |
| | | respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); |
| | | respVO.setSimpleModel(simpleModel); |
| | | // 进度信息 |
| | | respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds) |
| | | .setFinishedTaskActivityIds(finishedTaskActivityIds) |
| | | .setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds) |
| | | .setRejectedTaskActivityIds(rejectTaskActivityIds); |
| | | return respVO; |
| | | } |
| | | |
| | | default UserSimpleBaseVO buildUser(String userIdStr, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | if (StrUtil.isEmpty(userIdStr)) { |
| | | return null; |
| | | } |
| | | Long userId = NumberUtils.parseLong(userIdStr); |
| | | return buildUser(userId, userMap, deptMap); |
| | | } |
| | | |
| | | default UserSimpleBaseVO buildUser(Long userId, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | if (userId == null) { |
| | | return null; |
| | | } |
| | | AdminUserRespDTO user = userMap.get(userId); |
| | | if (user == null) { |
| | | return null; |
| | | } |
| | | UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class); |
| | | DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null; |
| | | if (dept != null) { |
| | | userVO.setDeptName(dept.getName()); |
| | | } |
| | | return userVO; |
| | | } |
| | | |
| | | default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) { |
| | | if (task == null) { |
| | | return null; |
| | | } |
| | | return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) |
| | | .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); |
| | | } |
| | | |
| | | default Set<Long> parseUserIds(HistoricProcessInstance processInstance, |
| | | List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, |
| | | BpmTaskRespVO todoTask) { |
| | | Set<Long> userIds = new HashSet<>(); |
| | | if (processInstance != null) { |
| | | userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); |
| | | } |
| | | for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) { |
| | | CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee)); |
| | | CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner)); |
| | | CollUtil.addAll(userIds, activityNode.getCandidateUserIds()); |
| | | } |
| | | if (todoTask != null) { |
| | | CollUtil.addIfAbsent(userIds, todoTask.getAssignee()); |
| | | CollUtil.addIfAbsent(userIds, todoTask.getOwner()); |
| | | if (CollUtil.isNotEmpty(todoTask.getChildren())) { |
| | | CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee)); |
| | | CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner)); |
| | | } |
| | | } |
| | | return userIds; |
| | | } |
| | | |
| | | default Set<Long> parseUserIds02(HistoricProcessInstance processInstance, |
| | | List<HistoricTaskInstance> tasks) { |
| | | Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); |
| | | tasks.forEach(task -> { |
| | | CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee()))); |
| | | CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner()))); |
| | | }); |
| | | return userIds; |
| | | } |
| | | |
| | | default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel, |
| | | ProcessDefinition processDefinition, |
| | | BpmProcessDefinitionInfoDO processDefinitionInfo, |
| | | HistoricProcessInstance processInstance, |
| | | Integer processInstanceStatus, |
| | | List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, |
| | | BpmTaskRespVO todoTask, |
| | | Map<String, String> formFieldsPermission, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | // 1.1 流程实例 |
| | | BpmProcessInstanceRespVO processInstanceResp = null; |
| | | if (processInstance != null) { |
| | | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| | | DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null; |
| | | processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept); |
| | | } |
| | | |
| | | // 1.2 流程定义 |
| | | BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( |
| | | processDefinition, null, processDefinitionInfo, null, null, bpmnModel); |
| | | |
| | | // 1.3 流程节点 |
| | | activityNodes.forEach(approveNode -> { |
| | | if (approveNode.getTasks() != null) { |
| | | approveNode.getTasks().forEach(task -> { |
| | | task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)); |
| | | task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)); |
| | | }); |
| | | } |
| | | approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap))); |
| | | }); |
| | | |
| | | // 1.4 待办任务 |
| | | if (todoTask != null) { |
| | | todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap)); |
| | | todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap)); |
| | | if (CollUtil.isNotEmpty(todoTask.getChildren())) { |
| | | todoTask.getChildren().forEach(childTask -> { |
| | | childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap)); |
| | | childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap)); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 2. 拼接起来 |
| | | return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus) |
| | | .setProcessDefinition(definitionResp) |
| | | .setProcessInstance(processInstanceResp) |
| | | .setFormFieldsPermission(formFieldsPermission) |
| | | .setTodoTask(todoTask) |
| | | .setActivityNodes(activityNodes); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.convert.task; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.map.MapUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.collection.CollectionUtils; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.framework.common.util.object.BeanUtils; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| | | import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | | import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.*; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; |
| | | import static com.iailab.framework.common.util.collection.MapUtils.findAndThen; |
| | | |
| | | |
| | | /** |
| | | * Bpm 任务 Convert |
| | | * |
| | | * @author iailab |
| | | * @author 芋道源码 |
| | | */ |
| | | @Mapper |
| | | public interface BpmTaskConvert { |
| | |
| | | } |
| | | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| | | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| | | taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); |
| | | taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| | | }); |
| | | } |
| | | |
| | |
| | | Map<String, HistoricProcessInstance> processInstanceMap, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> { |
| | | List<BpmTaskRespVO> taskVOList = convertList(pageResult.getList(), task -> { |
| | | BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); |
| | | taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); |
| | | // 用户信息 |
| | | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| | | if (assignUser != null) { |
| | | taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); |
| | | taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); |
| | | findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); |
| | | } |
| | | // 流程实例 |
| | |
| | | if (processInstance != null) { |
| | | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| | | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| | | taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); |
| | | taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| | | } |
| | | return taskVO; |
| | | }); |
| | |
| | | } |
| | | |
| | | default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList, |
| | | HistoricProcessInstance processInstance, |
| | | Map<Long, BpmFormDO> formMap, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> { |
| | | return convertList(taskList, task -> { |
| | | // 特殊:已取消的任务,不返回 |
| | | BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); |
| | | taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); |
| | | // 流程实例 |
| | | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| | | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| | | taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); |
| | | Integer taskStatus = FlowableUtils.getTaskStatus(task); |
| | | if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) { |
| | | return null; |
| | | } |
| | | taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); |
| | | // 表单信息 |
| | | BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); |
| | | if (form != null) { |
| | |
| | | .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); |
| | | } |
| | | // 用户信息 |
| | | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| | | if (assignUser != null) { |
| | | taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); |
| | | findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); |
| | | } |
| | | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); |
| | | if (ownerUser != null) { |
| | | taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); |
| | | findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); |
| | | } |
| | | buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap); |
| | | buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap); |
| | | return taskVO; |
| | | }); |
| | | |
| | | // 拼接父子关系 |
| | | Map<String, List<BpmTaskRespVO>> childrenTaskMap = convertMultiMap( |
| | | filterList(taskVOList, r -> StrUtil.isNotEmpty(r.getParentTaskId())), |
| | | BpmTaskRespVO::getParentTaskId); |
| | | for (BpmTaskRespVO taskVO : taskVOList) { |
| | | taskVO.setChildren(childrenTaskMap.get(taskVO.getId())); |
| | | } |
| | | return filterList(taskVOList, r -> StrUtil.isEmpty(r.getParentTaskId())); |
| | | } |
| | | |
| | | default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList, |
| | |
| | | return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> { |
| | | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| | | if (assignUser != null) { |
| | | taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); |
| | | taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); |
| | | DeptRespDTO dept = deptMap.get(assignUser.getDeptId()); |
| | | if (dept != null) { |
| | | taskVO.getAssigneeUser().setDeptName(dept.getName()); |
| | |
| | | } |
| | | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); |
| | | if (ownerUser != null) { |
| | | taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); |
| | | taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); |
| | | findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks, |
| | | Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) { |
| | | return BeanUtils.toBean(todoTask, BpmTaskRespVO.class) |
| | | .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask)) |
| | | .setButtonsSetting(buttonsSetting) |
| | | .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class) |
| | | .setStatus(FlowableUtils.getTaskStatus(childTask)))); |
| | | } |
| | | |
| | | default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, |
| | |
| | | return reqDTO; |
| | | } |
| | | |
| | | default void buildTaskOwner(BpmTaskRespVO task, String taskOwner, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner)); |
| | | if (ownerUser != null) { |
| | | task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); |
| | | findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName())); |
| | | } |
| | | } |
| | | |
| | | default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap, |
| | | Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { |
| | | List<Task> childTasks = childrenTaskMap.get(task.getId()); |
| | | if (CollUtil.isNotEmpty(childTasks)) { |
| | | task.setChildren( |
| | | convertList(childTasks, childTask -> { |
| | | BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class); |
| | | childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask)); |
| | | buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap); |
| | | buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap); |
| | | return childTaskVO; |
| | | }) |
| | | ); |
| | | } |
| | | } |
| | | |
| | | default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee, |
| | | Map<Long, AdminUserRespDTO> userMap, |
| | | Map<Long, DeptRespDTO> deptMap) { |
| | | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee)); |
| | | if (assignUser != null) { |
| | | task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); |
| | | findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将父任务的属性,拷贝到子任务(加签任务) |
| | | * |
| | | * <p> |
| | | * 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。 |
| | | * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。 |
| | | * |
| | |
| | | childTask.setParentTaskId(parentTask.getId()); |
| | | childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); |
| | | childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); |
| | | // childTask.setExecutionId(parentTask.getExecutionId()); // TODO iailab:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错) |
| | | childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); |
| | | childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); |
| | | childTask.setPriority(parentTask.getPriority()); |
| | |
| | | package com.iailab.module.bpm.dal.dataobject.definition; |
| | | |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | |
| | | package com.iailab.module.bpm.dal.dataobject.definition; |
| | | |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | |
| | | * @author iailab |
| | | */ |
| | | @TableName(value = "bpm_form", autoResultMap = true) |
| | | @KeySequence("bpm_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | |
| | | /** |
| | | * 表单项的数组 |
| | | * |
| | | * 目前直接将 https://github.com/JakHuang/form-generator 生成的 JSON 串,直接保存 |
| | | * 定义:https://github.com/JakHuang/form-generator/issues/46 |
| | | */ |
| | | @TableField(typeHandler = JacksonTypeHandler.class) |
| | | private List<String> fields; |
| | |
| | | package com.iailab.module.bpm.dal.dataobject.definition; |
| | | |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.iailab.framework.mybatis.core.type.LongListTypeHandler; |
| | | import com.iailab.framework.mybatis.core.type.StringListTypeHandler; |
| | | import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | import org.flowable.engine.repository.Model; |
| | | import org.flowable.engine.repository.ProcessDefinition; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * BPM 流程定义的拓信息 |
| | | * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表 |
| | | * 主要解决 Flowable {@link ProcessDefinition} 不支持拓展字段,所以新建该表 |
| | | * |
| | | * @author iailab |
| | | */ |
| | | @TableName(value = "bpm_process_definition_info", autoResultMap = true) |
| | | @KeySequence("bpm_process_definition_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | |
| | | /** |
| | | * 流程定义的编号 |
| | | * |
| | | * 关联 ProcessDefinition 的 id 属性 |
| | | * 关联 {@link ProcessDefinition#getId()} 属性 |
| | | */ |
| | | private String processDefinitionId; |
| | | /** |
| | | * 流程模型的编号 |
| | | * |
| | | * 关联 Model 的 id 属性 |
| | | * 关联 {@link Model#getId()} 属性 |
| | | */ |
| | | private String modelId; |
| | | /** |
| | | * 流程模型的类型 |
| | | * |
| | | * 枚举 {@link BpmModelTypeEnum} |
| | | */ |
| | | private Integer modelType; |
| | | |
| | | /** |
| | | * 图标 |
| | |
| | | /** |
| | | * 表单类型 |
| | | * |
| | | * 关联 {@link BpmModelFormTypeEnum} |
| | | * 枚举 {@link BpmModelFormTypeEnum} |
| | | */ |
| | | private Integer formType; |
| | | /** |
| | | * 动态表单编号 |
| | | * |
| | | * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 |
| | | * |
| | | * 关联 {@link BpmFormDO#getId()} |
| | |
| | | private Long formId; |
| | | /** |
| | | * 表单的配置 |
| | | * |
| | | * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 |
| | | * |
| | | * 冗余 {@link BpmFormDO#getConf()} |
| | |
| | | private String formConf; |
| | | /** |
| | | * 表单项的数组 |
| | | * |
| | | * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 |
| | | * |
| | | * 冗余 {@link BpmFormDO#getFields()} ()} |
| | | * 冗余 {@link BpmFormDO#getFields()} |
| | | */ |
| | | @TableField(typeHandler = JacksonTypeHandler.class) |
| | | private List<String> formFields; |
| | | /** |
| | | * 自定义表单的提交路径,使用 Vue 的路由地址 |
| | | * |
| | | * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 |
| | | */ |
| | | private String formCustomCreatePath; |
| | | /** |
| | | * 自定义表单的查看路径,使用 Vue 的路由地址 |
| | | * |
| | | * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 |
| | | */ |
| | | private String formCustomViewPath; |
| | | |
| | | /** |
| | | * SIMPLE 设计器模型数据 json 格式 |
| | | * |
| | | * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。 |
| | | */ |
| | | private String simpleModel; |
| | | /** |
| | | * 是否可见 |
| | | * |
| | | * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 |
| | | */ |
| | | private Boolean visible; |
| | | /** |
| | | * 排序值 |
| | | */ |
| | | private Long sort; |
| | | |
| | | /** |
| | | * 可发起用户编号数组 |
| | | * |
| | | * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 |
| | | * |
| | | * 如果为空,则表示“全部可以发起”! |
| | | * |
| | | * 它和 {@link #visible} 的区别在于: |
| | | * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起 |
| | | * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的 |
| | | */ |
| | | @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 |
| | | private List<Long> startUserIds; |
| | | |
| | | /** |
| | | * 可管理用户编号数组 |
| | | * |
| | | * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 |
| | | */ |
| | | @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 |
| | | private List<Long> managerUserIds; |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.dal.dataobject.definition; |
| | | |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.sun.xml.bind.v2.TODO; |
| | | import lombok.*; |
| | | |
| | | /** |
| | |
| | | package com.iailab.module.bpm.dal.dataobject.definition; |
| | | |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | |
| | | * @author iailab |
| | | */ |
| | | @TableName(value = "bpm_process_listener") |
| | | @KeySequence("bpm_process_listener_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | |
| | | * |
| | | * 枚举 {@link com.iailab.module.bpm.enums.definition.BpmProcessListenerType} |
| | | * |
| | | * 1. execution:ExecutionListener <a href="https://tkjohn.github.io/flowable-userguide/#executionListeners">执行监听器</a> |
| | | * 2. task:TaskListener <a href="https://tkjohn.github.io/flowable-userguide/#taskListeners">任务监听器</a> |
| | | */ |
| | | private String type; |
| | | /** |
| | |
| | | package com.iailab.module.bpm.dal.dataobject.definition; |
| | | |
| | | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| | | import com.iailab.framework.common.enums.CommonStatusEnum; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| | | import com.iailab.framework.common.enums.CommonStatusEnum; |
| | | import com.iailab.framework.mybatis.core.dataobject.BaseDO; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | |
| | | * @author iailab |
| | | */ |
| | | @TableName(value = "bpm_user_group", autoResultMap = true) |
| | | @KeySequence("bpm_user_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | import org.flowable.bpmn.model.FlowNode; |
| | | import org.flowable.task.api.history.HistoricTaskInstance; |
| | | |
| | | /** |
| | | * 流程抄送 DO |
| | |
| | | * 冗余 ProcessInstance 的 category 字段 |
| | | */ |
| | | private String category; |
| | | |
| | | /** |
| | | * 任务主键 |
| | | * 流程活动的编号 |
| | | * <p/> |
| | | * |
| | | * 关联 Task 的 id 属性 |
| | | * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号 |
| | | * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId |
| | | */ |
| | | private String activityId; |
| | | /** |
| | | * 流程活动的名字 |
| | | * |
| | | * 冗余 {@link FlowNode#getName()} |
| | | */ |
| | | private String activityName; |
| | | /** |
| | | * 流程活动的编号 |
| | | * |
| | | * 关联 {@link HistoricTaskInstance#getId()} |
| | | */ |
| | | private String taskId; |
| | | /** |
| | | * 任务名称 |
| | | * |
| | | * 冗余 Task 的 name 属性 |
| | | */ |
| | | private String taskName; |
| | | |
| | | /** |
| | | * 用户编号(被抄送的用户编号) |
| | |
| | | */ |
| | | private Long userId; |
| | | |
| | | /** |
| | | * 抄送意见 |
| | | */ |
| | | private String reason; |
| | | |
| | | } |
| | |
| | | /** |
| | | * 动态表单 Mapper |
| | | * |
| | | * @author 风里雾里 |
| | | * @author hou |
| | | */ |
| | | @Mapper |
| | | public interface BpmFormMapper extends BaseMapperX<BpmFormDO> { |
| | |
| | | package com.iailab.module.bpm.dal.mysql.definition; |
| | | |
| | | import com.iailab.framework.mybatis.core.mapper.BaseMapperX; |
| | | import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | |
| | | return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); |
| | | } |
| | | |
| | | default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) { |
| | | update(updateObj, |
| | | new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId)); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; |
| | | import org.flowable.common.engine.api.delegate.event.FlowableEventListener; |
| | | import org.flowable.spring.SpringProcessEngineConfiguration; |
| | | import org.flowable.spring.boot.EngineConfigurationConfigurer; |
| | |
| | | @Bean |
| | | public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer( |
| | | ObjectProvider<FlowableEventListener> listeners, |
| | | ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates, |
| | | BpmActivityBehaviorFactory bpmActivityBehaviorFactory) { |
| | | return configuration -> { |
| | | // 注册监听器,例如说 BpmActivityEventListener |
| | | configuration.setEventListeners(ListUtil.toList(listeners.iterator())); |
| | | // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义 |
| | | configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); |
| | | // 设置自定义的函数 |
| | | configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); |
| | | }; |
| | | } |
| | | |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.behavior; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.iailab.framework.common.util.collection.SetUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import lombok.Setter; |
| | |
| | | super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); |
| | | |
| | | // 第二步,获取任务的所有处理人 |
| | | Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); |
| | | @SuppressWarnings("unchecked") |
| | | Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); |
| | | if (assigneeUserIds == null) { |
| | | assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); |
| | | execution.setVariable(super.collectionVariable, assigneeUserIds); |
| | | if (CollUtil.isEmpty(assigneeUserIds)) { |
| | | // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! |
| | | // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 |
| | | // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 |
| | | assigneeUserIds = SetUtils.asSet((Long) null); |
| | | } |
| | | } |
| | | return assigneeUserIds.size(); |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.behavior; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.iailab.framework.common.util.collection.SetUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import lombok.Setter; |
| | |
| | | super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); |
| | | |
| | | // 第二步,获取任务的所有处理人 |
| | | Set<Long> assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!! |
| | | @SuppressWarnings("unchecked") |
| | | Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); |
| | | if (assigneeUserIds == null) { |
| | | assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); |
| | | execution.setVariable(super.collectionVariable, assigneeUserIds); |
| | | if (CollUtil.isEmpty(assigneeUserIds)) { |
| | | // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! |
| | | // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 |
| | | // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 |
| | | assigneeUserIds = SetUtils.asSet((Long) null); |
| | | } |
| | | } |
| | | return assigneeUserIds.size(); |
| | | } |
| | | |
| | | } |
| | |
| | | import org.flowable.engine.impl.util.TaskHelper; |
| | | import org.flowable.task.service.TaskService; |
| | | import org.flowable.task.service.impl.persistence.entity.TaskEntity; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.List; |
| | | import java.util.Set; |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | protected void handleAssignments(TaskService taskService, String assignee, String owner, |
| | | List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, |
| | | DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { |
| | | // 第一步,获得任务的候选用户 |
| | | Long assigneeUserId = calculateTaskCandidateUsers(execution); |
| | | Assert.notNull(assigneeUserId, "任务处理人不能为空"); |
| | | // 第二步,设置作为负责人 |
| | | if (assigneeUserId != null) { |
| | | TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); |
| | | } |
| | | } |
| | | |
| | | private Long calculateTaskCandidateUsers(DelegateExecution execution) { |
| | |
| | | |
| | | // 情况二,如果非多实例的任务,则计算任务处理人 |
| | | // 第一步,先计算可处理该任务的处理人们 |
| | | Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution); |
| | | Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution); |
| | | if (CollUtil.isEmpty(candidateUserIds)) { |
| | | return null; |
| | | } |
| | | // 第二步,后随机选择一个任务的处理人 |
| | | // 疑问:为什么一定要选择一个任务处理人? |
| | | // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。 |
| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.lang.Assert; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.extra.spring.SpringUtil; |
| | | import com.iailab.framework.common.enums.CommonStatusEnum; |
| | | import com.iailab.framework.common.util.object.ObjectUtils; |
| | | import com.iailab.framework.datapermission.core.annotation.DataPermission; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import com.google.common.annotations.VisibleForTesting; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.bpmn.model.FlowElement; |
| | | import org.flowable.bpmn.model.UserTask; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.*; |
| | | |
| | | import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static com.iailab.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; |
| | |
| | | List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); |
| | | // 遍历所有的 UserTask,校验审批人配置 |
| | | userTaskList.forEach(userTask -> { |
| | | // 1. 非空校验 |
| | | // 1.1 非人工审批,无需校验审批人配置 |
| | | Integer approveType = BpmnModelUtils.parseApproveType(userTask); |
| | | if (ObjectUtils.equalsAny(approveType, |
| | | BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), |
| | | BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { |
| | | return; |
| | | } |
| | | // 1.2 非空校验 |
| | | Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask); |
| | | String param = BpmnModelUtils.parseCandidateParam(userTask); |
| | | if (strategy == null) { |
| | |
| | | * @return 用户编号集合 |
| | | */ |
| | | @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 |
| | | public Set<Long> calculateUsers(DelegateExecution execution) { |
| | | Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement()); |
| | | String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution) { |
| | | // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 |
| | | FlowElement flowElement = execution.getCurrentFlowElement(); |
| | | Integer approveType = BpmnModelUtils.parseApproveType(flowElement); |
| | | if (ObjectUtils.equalsAny(approveType, |
| | | BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), |
| | | BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { |
| | | return new HashSet<>(); |
| | | } |
| | | |
| | | // 1.1 计算任务的候选人 |
| | | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); |
| | | Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); |
| | | String param = BpmnModelUtils.parseCandidateParam(flowElement); |
| | | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param); |
| | | // 1.2 移除被禁用的用户 |
| | | removeDisableUsers(userIds); |
| | | |
| | | // 2. 校验是否有候选人 |
| | | // 2. 候选人为空时,根据“审批人为空”的配置补充 |
| | | if (CollUtil.isEmpty(userIds)) { |
| | | log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(), |
| | | execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param); |
| | | throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER); |
| | | userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) |
| | | .calculateUsersByTask(execution, param); |
| | | // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! |
| | | } |
| | | |
| | | // 3. 移除发起人的用户 |
| | | ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) |
| | | .getProcessInstance(execution.getProcessInstanceId()); |
| | | Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); |
| | | removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId())); |
| | | return userIds; |
| | | } |
| | | |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 |
| | | FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); |
| | | Integer approveType = BpmnModelUtils.parseApproveType(flowElement); |
| | | if (ObjectUtils.equalsAny(approveType, |
| | | BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), |
| | | BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { |
| | | return new HashSet<>(); |
| | | } |
| | | |
| | | // 1.1 计算任务的候选人 |
| | | Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); |
| | | String param = BpmnModelUtils.parseCandidateParam(flowElement); |
| | | Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param, |
| | | startUserId, processDefinitionId, processVariables); |
| | | // 1.2 移除被禁用的用户 |
| | | removeDisableUsers(userIds); |
| | | |
| | | // 2. 候选人为空时,根据“审批人为空”的配置补充 |
| | | if (CollUtil.isEmpty(userIds)) { |
| | | userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) |
| | | .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables); |
| | | // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! |
| | | } |
| | | |
| | | // 3. 移除发起人的用户 |
| | | removeStartUserIfSkip(userIds, flowElement, startUserId); |
| | | return userIds; |
| | | } |
| | | |
| | |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds); |
| | | assigneeUserIds.removeIf(id -> { |
| | | AdminUserRespDTO user = userMap.get(id); |
| | | return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); |
| | | return user == null || CommonStatusEnum.isDisable(user.getStatus()); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 |
| | | * |
| | | * 注意:如果只有一个候选人,则不处理,避免无法审批 |
| | | * |
| | | * @param assigneeUserIds 当前分配的候选人 |
| | | * @param flowElement 当前节点 |
| | | * @param startUserId 发起人 |
| | | */ |
| | | @VisibleForTesting |
| | | void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) { |
| | | if (CollUtil.size(assigneeUserIds) <= 1) { |
| | | return; |
| | | } |
| | | Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement); |
| | | if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { |
| | | return; |
| | | } |
| | | assigneeUserIds.remove(startUserId); |
| | | } |
| | | |
| | | private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { |
| | | BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); |
| | | Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate; |
| | | |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * BPM 任务的候选人的策略接口 |
| | | * |
| | | * <p> |
| | | * 例如说:分配审批人 |
| | | * |
| | | * @author iailab |
| | | * @author hou |
| | | */ |
| | | public interface BpmTaskCandidateStrategy { |
| | | |
| | |
| | | void validateParam(String param); |
| | | |
| | | /** |
| | | * 基于执行任务,获得任务的候选用户们 |
| | | * |
| | | * @param execution 执行任务 |
| | | * @return 用户编号集合 |
| | | */ |
| | | Set<Long> calculateUsers(DelegateExecution execution, String param); |
| | | |
| | | /** |
| | | * 是否一定要输入参数 |
| | | * |
| | | * @return 是否 |
| | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 基于候选人参数,获得任务的候选用户们 |
| | | * |
| | | * 注意:实现 calculateUsers 系列方法时,有两种选择: |
| | | * 1. 只重写 calculateUsers 默认方法 |
| | | * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法 |
| | | * |
| | | * @param param 执行任务 |
| | | * @return 用户编号集合 |
| | | */ |
| | | default Set<Long> calculateUsers(String param) { |
| | | throw new UnsupportedOperationException("该分配方法未实现,请检查!"); |
| | | } |
| | | |
| | | /** |
| | | * 基于【执行任务】,获得任务的候选用户们 |
| | | * |
| | | * @param execution 执行任务 |
| | | * @return 用户编号集合 |
| | | */ |
| | | default Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | return calculateUsers(param); |
| | | } |
| | | |
| | | /** |
| | | * 基于【流程活动】,获得任务的候选用户们 |
| | | * <p> |
| | | * 目的:用于获取未执行节点的候选用户们 |
| | | * |
| | | * @param bpmnModel 流程图 |
| | | * @param activityId 活动 ID (对应 Bpmn XML id) |
| | | * @param param 节点的参数 |
| | | * @param startUserId 流程发起人编号 |
| | | * @param processDefinitionId 流程定义编号 |
| | | * @param processVariables 流程变量 |
| | | * @return 用户编号集合 |
| | | */ |
| | | @SuppressWarnings("unused") |
| | | default Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | return calculateUsers(param); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.system.api.dept.DeptApi; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { |
| | | |
| | | @Resource |
| | | protected DeptApi deptApi; |
| | | @Resource |
| | | protected AdminUserApi adminUserApi; |
| | | |
| | | /** |
| | | * 获得指定层级的部门负责人,只有第 level 的负责人 |
| | | * |
| | | * @param dept 指定部门 |
| | | * @param level 第几级 |
| | | * @return 部门负责人的编号 |
| | | */ |
| | | protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) { |
| | | Assert.isTrue(level > 0, "level 必须大于 0"); |
| | | if (dept == null) { |
| | | return null; |
| | | } |
| | | DeptRespDTO currentDept = dept; |
| | | for (int i = 1; i < level; i++) { |
| | | DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()).getCheckedData(); |
| | | if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人 |
| | | break; |
| | | } |
| | | currentDept = parentDept; |
| | | } |
| | | return currentDept.getLeaderUserId(); |
| | | } |
| | | |
| | | /** |
| | | * 获得连续层级的部门负责人,包含 [1, level] 的负责人 |
| | | * |
| | | * @param deptIds 指定部门编号数组 |
| | | * @param level 最大层级 |
| | | * @return 连续部门负责人 Id |
| | | */ |
| | | protected Set<Long> getMultiLevelDeptLeaderIds(List<Long> deptIds, Integer level) { |
| | | Assert.isTrue(level > 0, "level 必须大于 0"); |
| | | if (CollUtil.isEmpty(deptIds)) { |
| | | return new HashSet<>(); |
| | | } |
| | | Set<Long> deptLeaderIds = new LinkedHashSet<>(); // 保证有序 |
| | | for (Long deptId : deptIds) { |
| | | DeptRespDTO dept = deptApi.getDept(deptId).getCheckedData(); |
| | | for (int i = 0; i < level; i++) { |
| | | if (dept.getLeaderUserId() != null) { |
| | | deptLeaderIds.add(dept.getLeaderUserId()); |
| | | } |
| | | DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()).getCheckedData(); |
| | | if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 |
| | | break; |
| | | } |
| | | dept = parentDept; |
| | | } |
| | | } |
| | | return deptLeaderIds; |
| | | } |
| | | |
| | | /** |
| | | * 获取发起人的部门 |
| | | * |
| | | * @param startUserId 发起人 Id |
| | | */ |
| | | protected DeptRespDTO getStartUserDept(Long startUserId) { |
| | | AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData(); |
| | | if (startUser.getDeptId() == null) { // 找不到部门 |
| | | return null; |
| | | } |
| | | return deptApi.getDept(startUser.getDeptId()).getCheckedData(); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级 |
| | | String[] params = param.split("\\|"); |
| | | Assert.isTrue(params.length == 2, "参数格式不匹配"); |
| | | List<Long> deptIds = StrUtils.splitToLong(params[0], ","); |
| | | int level = Integer.parseInt(params[1]); |
| | | // 校验部门存在 |
| | | deptApi.validateDeptList(deptIds).checkError(); |
| | | Assert.isTrue(level > 0, "部门层级必须大于 0"); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(String param) { |
| | | String[] params = param.split("\\|"); |
| | | List<Long> deptIds = StrUtils.splitToLong(params[0], ","); |
| | | int level = Integer.parseInt(params[1]); |
| | | return super.getMultiLevelDeptLeaderIds(deptIds, level); |
| | | } |
| | | |
| | | } |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.system.api.dept.DeptApi; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | /** |
| | | * 部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author kyle |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { |
| | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | Set<Long> deptIds = StrUtils.splitToLongSet(param); |
| | | deptApi.validateDeptList(deptIds); |
| | | deptApi.validateDeptList(deptIds).checkError(); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | public Set<Long> calculateUsers(String param) { |
| | | Set<Long> deptIds = StrUtils.splitToLongSet(param); |
| | | List<DeptRespDTO> depts = deptApi.getDeptList(deptIds).getCheckedData(); |
| | | return convertSet(depts, DeptRespDTO::getLeaderUserId); |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | |
| | | import com.iailab.module.system.api.dept.DeptApi; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | |
| | | |
| | | /** |
| | | * 部门的成员 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author kyle |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { |
| | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | Set<Long> deptIds = StrUtils.splitToLongSet(param); |
| | | deptApi.validateDeptList(deptIds); |
| | | deptApi.validateDeptList(deptIds).checkError(); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | public Set<Long> calculateUsers(String param) { |
| | | Set<Long> deptIds = StrUtils.splitToLongSet(param); |
| | | List<AdminUserRespDTO> users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(); |
| | | return convertSet(users, AdminUserRespDTO::getId); |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.HashSet; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import static cn.hutool.core.collection.ListUtil.toList; |
| | | |
| | | /** |
| | | * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { |
| | | |
| | | @Resource |
| | | @Lazy |
| | | private BpmProcessInstanceService processInstanceService; |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | int level = Integer.parseInt(param); // 参数是部门的层级 |
| | | Assert.isTrue(level > 0, "部门的层级必须大于 0"); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | int level = Integer.parseInt(param); // 参数是部门的层级 |
| | | // 获得流程发起人 |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| | | Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); |
| | | // 获取发起人的 multi 部门负责人 |
| | | DeptRespDTO dept = super.getStartUserDept(startUserId); |
| | | if (dept == null) { |
| | | return new HashSet<>(); |
| | | } |
| | | return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | int level = Integer.parseInt(param); // 参数是部门的层级 |
| | | DeptRespDTO dept = super.getStartUserDept(startUserId); |
| | | if (dept == null) { |
| | | return new HashSet<>(); |
| | | } |
| | | return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.HashSet; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import static com.iailab.framework.common.util.collection.SetUtils.asSet; |
| | | |
| | | /** |
| | | * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { |
| | | |
| | | @Resource |
| | | @Lazy // 避免循环依赖 |
| | | private BpmProcessInstanceService processInstanceService; |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | // 参数是部门的层级 |
| | | Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | // 获得流程发起人 |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| | | Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); |
| | | // 获取发起人的部门负责人 |
| | | return getStartUserDeptLeader(startUserId, param); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | // 获取发起人的部门负责人 |
| | | return getStartUserDeptLeader(startUserId, param); |
| | | } |
| | | |
| | | private Set<Long> getStartUserDeptLeader(Long startUserId, String param) { |
| | | int level = Integer.parseInt(param); // 参数是部门的层级 |
| | | DeptRespDTO dept = super.getStartUserDept(startUserId); |
| | | if (dept == null) { |
| | | return new HashSet<>(); |
| | | } |
| | | Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level); |
| | | return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.lang.Assert; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import com.google.common.collect.Sets; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.bpmn.model.ServiceTask; |
| | | import org.flowable.bpmn.model.Task; |
| | | import org.flowable.bpmn.model.UserTask; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { |
| | | |
| | | @Resource |
| | | @Lazy // 延迟加载,避免循环依赖 |
| | | private BpmProcessInstanceService processInstanceService; |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.START_USER_SELECT; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) {} |
| | | |
| | | @Override |
| | | public boolean isParamRequired() { |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| | | Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); |
| | | Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); |
| | | Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", |
| | | execution.getProcessInstanceId()); |
| | | // 获得审批人 |
| | | List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); |
| | | return new LinkedHashSet<>(assignees); |
| | | } |
| | | |
| | | @Override |
| | | public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | if (processVariables == null) { |
| | | return Sets.newLinkedHashSet(); |
| | | } |
| | | Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables); |
| | | if (startUserSelectAssignees == null) { |
| | | return Sets.newLinkedHashSet(); |
| | | } |
| | | // 获得审批人 |
| | | List<Long> assignees = startUserSelectAssignees.get(activityId); |
| | | return new LinkedHashSet<>(assignees); |
| | | } |
| | | |
| | | /** |
| | | * 获得发起人自选审批人或抄送人的 Task 列表 |
| | | * |
| | | * @param bpmnModel BPMN 模型 |
| | | * @return Task 列表 |
| | | */ |
| | | public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) { |
| | | if (bpmnModel == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<Task> tasks = new ArrayList<>(); |
| | | tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class)); |
| | | tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class)); |
| | | if (CollUtil.isEmpty(tasks)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task), |
| | | BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())); |
| | | return tasks; |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.form; |
| | | |
| | | import cn.hutool.core.convert.Convert; |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 表单内部门负责人 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | // 参数格式: | 分隔:1)左边为表单内部门字段。2)右边为部门层级 |
| | | String[] params = param.split("\\|"); |
| | | Assert.isTrue(params.length == 2, "参数格式不匹配"); |
| | | Assert.notEmpty(param, "表单内部门字段不能为空"); |
| | | int level = Integer.parseInt(params[1]); |
| | | Assert.isTrue(level > 0, "部门层级必须大于 0"); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | String[] params = param.split("\\|"); |
| | | Object result = execution.getVariable(params[0]); |
| | | int level = Integer.parseInt(params[1]); |
| | | return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, |
| | | String param, Long startUserId, String processDefinitionId, |
| | | Map<String, Object> processVariables) { |
| | | String[] params = param.split("\\|"); |
| | | Object result = processVariables == null ? null : processVariables.get(params[0]); |
| | | int level = Integer.parseInt(params[1]); |
| | | return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.form; |
| | | |
| | | import cn.hutool.core.convert.Convert; |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 表单内用户字段 {@link BpmTaskCandidateUserStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy { |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.FORM_USER; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | Assert.notEmpty(param, "表单内用户字段不能为空"); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | Object result = execution.getVariable(param); |
| | | return Convert.toSet(Long.class, result); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, |
| | | String param, Long startUserId, String processDefinitionId, |
| | | Map<String, Object> processVariables) { |
| | | Object result = processVariables == null ? null : processVariables.get(param); |
| | | return Convert.toSet(Long.class, result); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other; |
| | | |
| | | import cn.hutool.core.lang.Assert; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.bpmn.model.FlowElement; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.HashSet; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy { |
| | | |
| | | @Resource |
| | | @Lazy // 延迟加载,避免循环依赖 |
| | | private BpmProcessDefinitionService processDefinitionService; |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement()); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); |
| | | return getCandidateUsers(processDefinitionId, flowElement); |
| | | } |
| | | |
| | | private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) { |
| | | // 情况一:指定人员审批 |
| | | Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement); |
| | | if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { |
| | | return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement)); |
| | | } |
| | | |
| | | // 情况二:流程管理员 |
| | | if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { |
| | | BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); |
| | | Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId); |
| | | return new HashSet<>(processDefinition.getManagerUserIds()); |
| | | } |
| | | |
| | | // 都不满足,还是返回空 |
| | | return new HashSet<>(); |
| | | } |
| | | |
| | | } |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other; |
| | | |
| | | import cn.hutool.core.convert.Convert; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | Object result = FlowableUtils.getExpressionValue(execution, param); |
| | | return Convert.toSet(Long.class, result); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | Object result = FlowableUtils.getExpressionValue(processVariables, param); |
| | | return Convert.toSet(Long.class, result); |
| | | } |
| | | |
| | | } |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; |
| | | |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmUserGroupDO; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.service.definition.BpmUserGroupService; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; |
| | | |
| | | |
| | | /** |
| | | * 用户组 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author kyle |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { |
| | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | Set<Long> groupIds = StrUtils.splitToLongSet(param); |
| | | userGroupService.getUserGroupList(groupIds); |
| | | userGroupService.validUserGroups(groupIds); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | public Set<Long> calculateUsers(String param) { |
| | | Set<Long> groupIds = StrUtils.splitToLongSet(param); |
| | | List<BpmUserGroupDO> groups = userGroupService.getUserGroupList(groupIds); |
| | | return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream); |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; |
| | | |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | |
| | | import com.iailab.module.system.api.dept.PostApi; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
| | | |
| | | |
| | | /** |
| | | * 岗位 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author kyle |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | public Set<Long> calculateUsers(String param) { |
| | | Set<Long> postIds = StrUtils.splitToLongSet(param); |
| | | List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds).getCheckedData(); |
| | | return convertSet(users, AdminUserRespDTO::getId); |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; |
| | | |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.system.api.permission.PermissionApi; |
| | | import com.iailab.module.system.api.permission.RoleApi; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | /** |
| | | * 角色 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author kyle |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | public Set<Long> calculateUsers(String param) { |
| | | Set<Long> roleIds = StrUtils.splitToLongSet(param); |
| | | return permissionApi.getUserRoleIdListByRoleIds(roleIds).getCheckedData(); |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; |
| | | |
| | | import com.iailab.framework.common.util.collection.SetUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceService; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类 |
| | | * <p> |
| | | * 适合场景:用于需要发起人信息复核等场景 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy { |
| | | |
| | | @Resource |
| | | @Lazy // 延迟加载,避免循环依赖 |
| | | private BpmProcessInstanceService processInstanceService; |
| | | |
| | | @Override |
| | | public BpmTaskCandidateStrategyEnum getStrategy() { |
| | | return BpmTaskCandidateStrategyEnum.START_USER; |
| | | } |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | } |
| | | |
| | | @Override |
| | | public boolean isParamRequired() { |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); |
| | | return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, |
| | | Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { |
| | | return SetUtils.asSet(startUserId); |
| | | } |
| | | |
| | | } |
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java 修改 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; |
| | | package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; |
| | | |
| | | import cn.hutool.core.text.StrPool; |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Set; |
| | | import java.util.LinkedHashSet; |
| | | |
| | | /** |
| | | * 用户 {@link BpmTaskCandidateStrategy} 实现类 |
| | | * |
| | | * @author kyle |
| | | * @author hou |
| | | */ |
| | | @Component |
| | | public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { |
| | |
| | | |
| | | @Override |
| | | public void validateParam(String param) { |
| | | adminUserApi.validateUserList(StrUtils.splitToLongSet(param)); |
| | | adminUserApi.validateUserList(StrUtils.splitToLongSet(param)).checkError(); |
| | | } |
| | | |
| | | @Override |
| | | public Set<Long> calculateUsers(DelegateExecution execution, String param) { |
| | | return StrUtils.splitToLongSet(param); |
| | | public LinkedHashSet<Long> calculateUsers(String param) { |
| | | return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.enums; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import com.iailab.framework.common.core.IntArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * BPM 任务的候选人策略枚举 |
| | |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum BpmTaskCandidateStrategyEnum { |
| | | public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { |
| | | |
| | | ROLE(10, "角色"), |
| | | DEPT_MEMBER(20, "部门的成员"), // 包括负责人 |
| | | DEPT_LEADER(21, "部门的负责人"), |
| | | MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), |
| | | POST(22, "岗位"), |
| | | USER(30, "用户"), |
| | | START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 |
| | | START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 |
| | | START_USER_DEPT_LEADER(37, "发起人部门负责人"), |
| | | START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), |
| | | USER_GROUP(40, "用户组"), |
| | | FORM_USER(50, "表单内用户字段"), |
| | | FORM_DEPT_LEADER(51, "表单内部门负责人"), |
| | | EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager |
| | | ASSIGN_EMPTY(1, "审批人为空"), |
| | | ; |
| | | |
| | | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray(); |
| | | |
| | | /** |
| | | * 类型 |
| | |
| | | return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values()); |
| | | } |
| | | |
| | | @Override |
| | | public int[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | String USER_TASK_CANDIDATE_PARAM = "candidateParam"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型 |
| | | */ |
| | | String BOUNDARY_EVENT_TYPE = "boundaryEventType"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 |
| | | */ |
| | | String USER_TASK_TIMEOUT_HANDLER_TYPE = "timeoutHandlerType"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 |
| | | */ |
| | | String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型 |
| | | */ |
| | | String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType"; |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组 |
| | | */ |
| | | String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 |
| | | */ |
| | | String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; |
| | | /** |
| | | * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的退回的任务 Id |
| | | */ |
| | | String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; |
| | | |
| | | /** |
| | | * BPMN UserTask 的扩展属性,用于标记用户任务的审批类型 |
| | | */ |
| | | String USER_TASK_APPROVE_TYPE = "approveType"; |
| | | |
| | | /** |
| | | * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式 |
| | | */ |
| | | String USER_TASK_APPROVE_METHOD = "approveMethod"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 |
| | | */ |
| | | String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement Attribute, 用于标记表单字段 |
| | | */ |
| | | String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field"; |
| | | /** |
| | | * BPMN ExtensionElement Attribute, 用于标记表单权限 |
| | | */ |
| | | String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 |
| | | */ |
| | | String BUTTON_SETTING_ELEMENT = "buttonsSetting"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement Attribute, 用于标记按钮编号 |
| | | */ |
| | | String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement Attribute, 用于标记按钮显示名称 |
| | | */ |
| | | String BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE = "displayName"; |
| | | |
| | | /** |
| | | * BPMN ExtensionElement Attribute, 用于标记按钮是否启用 |
| | | */ |
| | | String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; |
| | | |
| | | /** |
| | | * BPMN Start Event Node Id |
| | | */ |
| | | String START_EVENT_NODE_ID = "StartEvent"; |
| | | |
| | | /** |
| | | * 发起人节点 ID |
| | | */ |
| | | String START_USER_NODE_ID = "StartUserNode"; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.enums; |
| | | |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | |
| | | /** |
| | | * BPM Variable 通用常量 |
| | | * |
| | | * @author iailab |
| | | */ |
| | | public class BpmnVariableConstants { |
| | | |
| | | /** |
| | | * 流程实例的变量 - 状态 |
| | | * |
| | | * @see ProcessInstance#getProcessVariables() |
| | | */ |
| | | public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; |
| | | /** |
| | | * 流程实例的变量 - 理由 |
| | | * |
| | | * 例如说:审批不通过的理由(目前审核通过暂时不会记录) |
| | | * |
| | | * @see ProcessInstance#getProcessVariables() |
| | | */ |
| | | public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON"; |
| | | /** |
| | | * 流程实例的变量 - 发起用户选择的审批人 Map |
| | | * |
| | | * @see ProcessInstance#getProcessVariables() |
| | | */ |
| | | public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; |
| | | /** |
| | | * 流程实例的变量 - 发起用户 ID |
| | | * |
| | | * @see ProcessInstance#getProcessVariables() |
| | | */ |
| | | public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID"; |
| | | /** |
| | | * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} |
| | | * |
| | | * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 |
| | | * |
| | | * @see ProcessInstance#getProcessVariables() |
| | | */ |
| | | public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; |
| | | |
| | | /** |
| | | * 任务的变量 - 状态 |
| | | * |
| | | * @see org.flowable.task.api.Task#getTaskLocalVariables() |
| | | */ |
| | | public static final String TASK_VARIABLE_STATUS = "TASK_STATUS"; |
| | | /** |
| | | * 任务的变量 - 理由 |
| | | * |
| | | * 例如说:审批通过、不通过的理由 |
| | | * |
| | | * @see org.flowable.task.api.Task#getTaskLocalVariables() |
| | | */ |
| | | public static final String TASK_VARIABLE_REASON = "TASK_REASON"; |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.listener; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| | | import com.iailab.module.bpm.service.task.BpmProcessInstanceCopyService; |
| | | import org.flowable.bpmn.model.FlowElement; |
| | | import org.flowable.engine.delegate.DelegateExecution; |
| | | import org.flowable.engine.delegate.JavaDelegate; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * 处理抄送用户的 {@link JavaDelegate} 的实现类 |
| | | * <p> |
| | | * 目前只有仿钉钉/飞书模式的【抄送节点】使用 |
| | | * |
| | | * @author hou |
| | | */ |
| | | @Component(BpmCopyTaskDelegate.BEAN_NAME) |
| | | public class BpmCopyTaskDelegate implements JavaDelegate { |
| | | |
| | | public static final String BEAN_NAME = "bpmCopyTaskDelegate"; |
| | | |
| | | @Resource |
| | | private BpmTaskCandidateInvoker taskCandidateInvoker; |
| | | |
| | | @Resource |
| | | private BpmProcessInstanceCopyService processInstanceCopyService; |
| | | |
| | | @Override |
| | | public void execute(DelegateExecution execution) { |
| | | // 1. 获得抄送人 |
| | | Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution); |
| | | if (CollUtil.isEmpty(userIds)) { |
| | | return; |
| | | } |
| | | // 2. 执行抄送 |
| | | FlowElement currentFlowElement = execution.getCurrentFlowElement(); |
| | | processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(), |
| | | currentFlowElement.getId(), currentFlowElement.getName(), null); |
| | | } |
| | | |
| | | } |
| | |
| | | @Component |
| | | public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { |
| | | |
| | | @Resource |
| | | @Lazy |
| | | private BpmProcessInstanceService processInstanceService; |
| | | |
| | | public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() |
| | | .add(FlowableEngineEventType.PROCESS_CANCELLED) |
| | | .add(FlowableEngineEventType.PROCESS_COMPLETED) |
| | | .build(); |
| | | |
| | | @Resource |
| | | @Lazy // 延迟加载,避免循环依赖 |
| | | private BpmProcessInstanceService processInstanceService; |
| | | |
| | | public BpmProcessInstanceEventListener(){ |
| | | super(PROCESS_INSTANCE_EVENTS); |
| | | } |
| | | |
| | | @Override |
| | | protected void processCancelled(FlowableCancelledEvent event) { |
| | | processInstanceService.updateProcessInstanceWhenCancel(event); |
| | | } |
| | | |
| | | @Override |
| | | protected void processCompleted(FlowableEngineEntityEvent event) { |
| | | processInstanceService.updateProcessInstanceWhenApprove((ProcessInstance)event.getEntity()); |
| | | processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.listener; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.iailab.module.bpm.service.task.BpmActivityService; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.module.bpm.enums.definition.BpmBoundaryEventType; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.service.definition.BpmModelService; |
| | | import com.iailab.module.bpm.service.task.BpmTaskService; |
| | | import com.google.common.collect.ImmutableSet; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.bpmn.model.BoundaryEvent; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.bpmn.model.FlowElement; |
| | | import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; |
| | | import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; |
| | | import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; |
| | | import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; |
| | | import org.flowable.engine.history.HistoricActivityInstance; |
| | | import org.flowable.job.api.Job; |
| | | import org.flowable.task.api.Task; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Component; |
| | |
| | | public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { |
| | | |
| | | @Resource |
| | | @Lazy // 解决循环依赖 |
| | | private BpmTaskService taskService; |
| | | @Lazy // 延迟加载,避免循环依赖 |
| | | private BpmModelService modelService; |
| | | @Resource |
| | | @Lazy // 解决循环依赖 |
| | | private BpmActivityService activityService; |
| | | private BpmTaskService taskService; |
| | | |
| | | public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() |
| | | .add(FlowableEngineEventType.TASK_CREATED) |
| | | .add(FlowableEngineEventType.TASK_ASSIGNED) |
| | | // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 |
| | | .add(FlowableEngineEventType.ACTIVITY_CANCELLED) |
| | | .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 |
| | | .build(); |
| | | |
| | | public BpmTaskEventListener(){ |
| | |
| | | |
| | | @Override |
| | | protected void taskCreated(FlowableEngineEntityEvent event) { |
| | | taskService.updateTaskStatusWhenCreated((Task) event.getEntity()); |
| | | taskService.processTaskCreated((Task) event.getEntity()); |
| | | } |
| | | |
| | | @Override |
| | | protected void taskAssigned(FlowableEngineEntityEvent event) { |
| | | taskService.updateTaskExtAssign((Task)event.getEntity()); |
| | | taskService.processTaskAssigned((Task) event.getEntity()); |
| | | } |
| | | |
| | | @Override |
| | | protected void activityCancelled(FlowableActivityCancelledEvent event) { |
| | | List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); |
| | | List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId()); |
| | | if (CollUtil.isEmpty(activityList)) { |
| | | log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); |
| | | return; |
| | |
| | | if (StrUtil.isEmpty(activity.getTaskId())) { |
| | | return; |
| | | } |
| | | taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); |
| | | taskService.processTaskCanceled(activity.getTaskId()); |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | @SuppressWarnings("PatternVariableCanBeUsed") |
| | | protected void timerFired(FlowableEngineEntityEvent event) { |
| | | // 1.1 只处理 BoundaryEvent 边界计时时间 |
| | | String processDefinitionId = event.getProcessDefinitionId(); |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); |
| | | Job entity = (Job) event.getEntity(); |
| | | FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); |
| | | if (!(element instanceof BoundaryEvent)) { |
| | | return; |
| | | } |
| | | // 1.2 判断是否为超时处理 |
| | | BoundaryEvent boundaryEvent = (BoundaryEvent) element; |
| | | String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, |
| | | BpmnModelConstants.BOUNDARY_EVENT_TYPE); |
| | | BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); |
| | | if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) { |
| | | return; |
| | | } |
| | | |
| | | // 2. 处理超时 |
| | | String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, |
| | | BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE); |
| | | String taskKey = boundaryEvent.getAttachedToRefId(); |
| | | taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType)); |
| | | } |
| | | } |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.util; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.map.MapUtil; |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.google.common.collect.Maps; |
| | | import com.iailab.framework.common.util.collection.CollectionUtils; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.framework.common.util.string.StrUtils; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.bpmn.converter.BpmnXMLConverter; |
| | | import org.flowable.bpmn.model.Process; |
| | | import org.flowable.bpmn.model.*; |
| | | import org.flowable.common.engine.api.FlowableException; |
| | | import org.flowable.common.engine.impl.util.io.BytesStreamSource; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.*; |
| | | |
| | | import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; |
| | | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; |
| | | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; |
| | | |
| | | /** |
| | | * 流程模型转操作工具类 |
| | | * BPMN Model 操作工具类。目前分成三部分: |
| | | * |
| | | * 1. BPMN 修改 + 解析元素相关的方法 |
| | | * 2. BPMN 简单查找相关的方法 |
| | | * 3. BPMN 复杂遍历相关的方法 |
| | | * 4. BPMN 流程预测相关的方法 |
| | | * |
| | | * @author iailab |
| | | */ |
| | | @Slf4j |
| | | public class BpmnModelUtils { |
| | | |
| | | public static Integer parseCandidateStrategy(FlowElement userTask) { |
| | | return NumberUtils.parseInt(userTask.getAttributeValue( |
| | | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); |
| | | // ========== BPMN 修改 + 解析元素相关的方法 ========== |
| | | |
| | | public static void addExtensionElement(FlowElement element, String name, String value) { |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | ExtensionElement extensionElement = new ExtensionElement(); |
| | | extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); |
| | | extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); |
| | | extensionElement.setElementText(value); |
| | | extensionElement.setName(name); |
| | | element.addExtensionElement(extensionElement); |
| | | } |
| | | |
| | | public static String parseCandidateParam(FlowElement userTask) { |
| | | return userTask.getAttributeValue( |
| | | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); |
| | | public static void addExtensionElement(FlowElement element, String name, Integer value) { |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | addExtensionElement(element, name, String.valueOf(value)); |
| | | } |
| | | |
| | | public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) { |
| | | if (attributes == null) { |
| | | return; |
| | | } |
| | | ExtensionElement extensionElement = new ExtensionElement(); |
| | | extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); |
| | | extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); |
| | | extensionElement.setName(name); |
| | | attributes.forEach((key, value) -> { |
| | | ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value); |
| | | extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); |
| | | extensionElement.addAttribute(extensionAttribute); |
| | | }); |
| | | element.addExtensionElement(extensionElement); |
| | | } |
| | | |
| | | /** |
| | | * 解析扩展元素 |
| | | * |
| | | * @param flowElement 节点 |
| | | * @param elementName 元素名称 |
| | | * @return 扩展元素 |
| | | */ |
| | | public static String parseExtensionElement(FlowElement flowElement, String elementName) { |
| | | if (flowElement == null) { |
| | | return null; |
| | | } |
| | | ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); |
| | | return element != null ? element.getElementText() : null; |
| | | } |
| | | |
| | | /** |
| | | * 给节点添加候选人元素 |
| | | * |
| | | * @param candidateStrategy 候选人策略 |
| | | * @param candidateParam 候选人参数,允许空 |
| | | * @param flowElement 节点 |
| | | */ |
| | | public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) { |
| | | addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, |
| | | candidateStrategy == null ? null : candidateStrategy.toString()); |
| | | addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); |
| | | } |
| | | |
| | | /** |
| | | * 解析候选人策略 |
| | | * |
| | | * @param userTask 任务节点 |
| | | * @return 候选人策略 |
| | | */ |
| | | public static Integer parseCandidateStrategy(FlowElement userTask) { |
| | | Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( |
| | | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); |
| | | // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 |
| | | if (candidateStrategy == null) { |
| | | ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); |
| | | candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null; |
| | | } |
| | | return candidateStrategy; |
| | | } |
| | | |
| | | /** |
| | | * 解析候选人参数 |
| | | * |
| | | * @param userTask 任务节点 |
| | | * @return 候选人参数 |
| | | */ |
| | | public static String parseCandidateParam(FlowElement userTask) { |
| | | String candidateParam = userTask.getAttributeValue( |
| | | BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); |
| | | if (candidateParam == null) { |
| | | ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); |
| | | candidateParam = element != null ? element.getElementText() : null; |
| | | } |
| | | return candidateParam; |
| | | } |
| | | |
| | | /** |
| | | * 解析审批类型 |
| | | * |
| | | * @see BpmUserTaskApproveTypeEnum |
| | | * @param userTask 任务节点 |
| | | * @return 审批类型 |
| | | */ |
| | | public static Integer parseApproveType(FlowElement userTask) { |
| | | return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE)); |
| | | } |
| | | |
| | | /** |
| | | * 添加任务拒绝处理元素 |
| | | * |
| | | * @param rejectHandler 任务拒绝处理 |
| | | * @param userTask 任务节点 |
| | | */ |
| | | public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) { |
| | | if (rejectHandler == null) { |
| | | return; |
| | | } |
| | | addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); |
| | | addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); |
| | | } |
| | | |
| | | /** |
| | | * 解析任务拒绝处理类型 |
| | | * |
| | | * @param userTask 任务节点 |
| | | * @return 任务拒绝处理类型 |
| | | */ |
| | | public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { |
| | | Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); |
| | | return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); |
| | | } |
| | | |
| | | /** |
| | | * 解析任务拒绝返回任务节点 ID |
| | | * |
| | | * @param flowElement 任务节点 |
| | | * @return 任务拒绝返回任务节点 ID |
| | | */ |
| | | public static String parseReturnTaskId(FlowElement flowElement) { |
| | | return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); |
| | | } |
| | | |
| | | /** |
| | | * 给节点添加用户任务的审批人与发起人相同时,处理类型枚举 |
| | | * |
| | | * @see BpmUserTaskAssignStartUserHandlerTypeEnum |
| | | * @param assignStartUserHandlerType 发起人处理类型 |
| | | * @param userTask 任务节点 |
| | | */ |
| | | public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { |
| | | if (assignStartUserHandlerType == null) { |
| | | return; |
| | | } |
| | | addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); |
| | | } |
| | | |
| | | /** |
| | | * 给节点添加用户任务的审批人为空时,处理类型枚举 |
| | | * |
| | | * @see BpmUserTaskAssignEmptyHandlerTypeEnum |
| | | * @param emptyHandler 空处理 |
| | | * @param userTask 任务节点 |
| | | */ |
| | | public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) { |
| | | if (emptyHandler == null) { |
| | | return; |
| | | } |
| | | addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType())); |
| | | addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds())); |
| | | } |
| | | |
| | | /** |
| | | * 解析用户任务的审批人与发起人相同时,处理类型枚举 |
| | | * |
| | | * @param userTask 任务节点 |
| | | * @return 处理类型枚举 |
| | | */ |
| | | public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { |
| | | return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); |
| | | } |
| | | |
| | | /** |
| | | * 解析用户任务的审批人为空时,处理类型枚举 |
| | | * |
| | | * @param userTask 任务节点 |
| | | * @return 处理类型枚举 |
| | | */ |
| | | public static Integer parseAssignEmptyHandlerType(FlowElement userTask) { |
| | | return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE)); |
| | | } |
| | | |
| | | /** |
| | | * 解析用户任务的审批人为空时,处理用户 ID 数组 |
| | | * |
| | | * @param userTask 任务节点 |
| | | * @return 处理用户 ID 数组 |
| | | */ |
| | | public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) { |
| | | return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); |
| | | } |
| | | |
| | | /** |
| | | * 给节点添加表单字段权限元素 |
| | | * |
| | | * @param fieldsPermissions 表单字段权限 |
| | | * @param flowElement 节点 |
| | | */ |
| | | public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) { |
| | | if (CollUtil.isNotEmpty(fieldsPermissions)) { |
| | | fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析表单字段权限 |
| | | * |
| | | * @param bpmnModel bpmnModel 对象 |
| | | * @param flowElementId 元素 ID |
| | | * @return 表单字段权限 |
| | | */ |
| | | public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { |
| | | if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { |
| | | return null; |
| | | } |
| | | FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); |
| | | if (flowElement == null) { |
| | | return null; |
| | | } |
| | | List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); |
| | | if (CollUtil.isEmpty(extensionElements)) { |
| | | return null; |
| | | } |
| | | Map<String, String> fieldsPermission = MapUtil.newHashMap(); |
| | | extensionElements.forEach(element -> { |
| | | String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); |
| | | String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); |
| | | if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { |
| | | fieldsPermission.put(field, permission); |
| | | } |
| | | }); |
| | | return fieldsPermission; |
| | | } |
| | | |
| | | /** |
| | | * 给节点添加操作按钮设置元素 |
| | | */ |
| | | public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) { |
| | | if (CollUtil.isNotEmpty(buttonsSetting)) { |
| | | List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> { |
| | | Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3); |
| | | settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId())); |
| | | settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName()); |
| | | settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable())); |
| | | return settingMap; |
| | | }); |
| | | list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析操作按钮设置 |
| | | * |
| | | * @param bpmnModel bpmnModel 对象 |
| | | * @param flowElementId 元素 ID |
| | | * @return 操作按钮设置 |
| | | */ |
| | | public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { |
| | | FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); |
| | | if (flowElement == null) { |
| | | return null; |
| | | } |
| | | List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT); |
| | | if (CollUtil.isEmpty(extensionElements)) { |
| | | return null; |
| | | } |
| | | Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size()); |
| | | extensionElements.forEach(element -> { |
| | | String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); |
| | | String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); |
| | | String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE); |
| | | if (StrUtil.isNotEmpty(id)) { |
| | | BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting(); |
| | | buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable))); |
| | | } |
| | | }); |
| | | return buttonSettings; |
| | | } |
| | | |
| | | /** |
| | | * 解析边界事件扩展元素 |
| | | * |
| | | * @param boundaryEvent 边界事件 |
| | | * @param customElement 元素 |
| | | * @return 扩展元素 |
| | | */ |
| | | public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { |
| | | if (boundaryEvent == null) { |
| | | return null; |
| | | } |
| | | ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement)); |
| | | return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null); |
| | | } |
| | | |
| | | // ========== BPM 简单查找相关的方法 ========== |
| | | |
| | | /** |
| | | * 根据节点,获取入口连线 |
| | |
| | | * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 |
| | | * @return 元素们 |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { |
| | | List<T> result = new ArrayList<>(); |
| | | model.getProcesses().forEach(process -> { |
| | | process.getFlowElements().forEach(flowElement -> { |
| | | model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> { |
| | | if (flowElement.getClass().isAssignableFrom(clazz)) { |
| | | result.add((T) flowElement); |
| | | } |
| | | }); |
| | | }); |
| | | })); |
| | | return result; |
| | | } |
| | | |
| | |
| | | } |
| | | // 从 flowElementList 找 |
| | | return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent); |
| | | } |
| | | |
| | | public static EndEvent getEndEvent(BpmnModel model) { |
| | | Process process = model.getMainProcess(); |
| | | // 从 flowElementList 找 endEvent |
| | | return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent); |
| | | } |
| | | |
| | | public static BpmnModel getBpmnModel(byte[] bpmnBytes) { |
| | |
| | | return null; |
| | | } |
| | | BpmnXMLConverter converter = new BpmnXMLConverter(); |
| | | return new String(converter.convertToXML(model)); |
| | | return StrUtil.utf8Str(converter.convertToXML(model)); |
| | | } |
| | | |
| | | // ========== 遍历相关的方法 ========== |
| | | public static String getBpmnXml(byte[] bpmnBytes) { |
| | | if (ArrayUtil.isEmpty(bpmnBytes)) { |
| | | return null; |
| | | } |
| | | return StrUtil.utf8Str(bpmnBytes); |
| | | } |
| | | |
| | | // ========== BPMN 复杂遍历相关的方法 ========== |
| | | |
| | | /** |
| | | * 找到 source 节点之前的所有用户任务节点 |
| | |
| | | return userTaskList; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 |
| | | * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 |
| | | * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况 |
| | | * |
| | | * @param source 起始节点 |
| | | * @param target 目标节点 |
| | | * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 |
| | | * @return 结果 |
| | | */ |
| | | @SuppressWarnings("BooleanMethodIsAlwaysInverted") |
| | | public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { |
| | | visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; |
| | | // 不能是开始事件和子流程 |
| | |
| | | return userTaskList; |
| | | } |
| | | |
| | | // ========== BPMN 流程预测相关的方法 ========== |
| | | |
| | | /** |
| | | * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果 |
| | | * |
| | | * @param bpmnModel BPMN 图 |
| | | * @param variables 变量 |
| | | * @return 节点元素数组 |
| | | */ |
| | | public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) { |
| | | List<FlowElement> resultElements = new ArrayList<>(); |
| | | Set<FlowElement> visitElements = new HashSet<>(); |
| | | |
| | | // 从 StartEvent 开始遍历 |
| | | StartEvent startEvent = getStartEvent(bpmnModel); |
| | | simulateNextFlowElements(startEvent, variables, resultElements, visitElements); |
| | | |
| | | // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中 |
| | | List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements, |
| | | flowElement -> flowElement instanceof EndEvent); |
| | | resultElements.addAll(endEvents); |
| | | return resultElements; |
| | | } |
| | | |
| | | @SuppressWarnings("PatternVariableCanBeUsed") |
| | | private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables, |
| | | List<FlowElement> resultElements, Set<FlowElement> visitElements) { |
| | | // 如果为空,或者已经遍历过,则直接结束 |
| | | if (currentElement == null) { |
| | | return; |
| | | } |
| | | if (visitElements.contains(currentElement)) { |
| | | return; |
| | | } |
| | | visitElements.add(currentElement); |
| | | |
| | | // 情况:StartEvent/EndEvent/UserTask/ServiceTask |
| | | if (currentElement instanceof StartEvent |
| | | || currentElement instanceof EndEvent |
| | | || currentElement instanceof UserTask |
| | | || currentElement instanceof ServiceTask) { |
| | | // 添加元素 |
| | | FlowNode flowNode = (FlowNode) currentElement; |
| | | resultElements.add(flowNode); |
| | | // 遍历子节点 |
| | | flowNode.getOutgoingFlows().forEach( |
| | | nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); |
| | | return; |
| | | } |
| | | |
| | | // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的 |
| | | if (currentElement instanceof ExclusiveGateway) { |
| | | // 查找满足条件的 SequenceFlow 路径 |
| | | Gateway gateway = (Gateway) currentElement; |
| | | SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), |
| | | flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) |
| | | && evalConditionExpress(variables, flow.getConditionExpression())); |
| | | if (matchSequenceFlow == null) { |
| | | matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), |
| | | flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())); |
| | | // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 |
| | | if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) { |
| | | matchSequenceFlow = gateway.getOutgoingFlows().get(0); |
| | | } |
| | | } |
| | | // 遍历满足条件的 SequenceFlow 路径 |
| | | if (matchSequenceFlow != null) { |
| | | simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的 |
| | | if (currentElement instanceof InclusiveGateway) { |
| | | // 查找满足条件的 SequenceFlow 路径 |
| | | Gateway gateway = (Gateway) currentElement; |
| | | Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), |
| | | flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) |
| | | && evalConditionExpress(variables, flow.getConditionExpression())); |
| | | if (CollUtil.isEmpty(matchSequenceFlows)) { |
| | | matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), |
| | | flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())); |
| | | // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 |
| | | if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) { |
| | | matchSequenceFlows = gateway.getOutgoingFlows(); |
| | | } |
| | | } |
| | | // 遍历满足条件的 SequenceFlow 路径 |
| | | matchSequenceFlows.forEach( |
| | | flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements)); |
| | | } |
| | | |
| | | // 情况:ParallelGateway 并行,都满足,都走 |
| | | if (currentElement instanceof ParallelGateway) { |
| | | Gateway gateway = (Gateway) currentElement; |
| | | // 遍历子节点 |
| | | gateway.getOutgoingFlows().forEach( |
| | | nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算条件表达式是否为 true 满足条件 |
| | | * |
| | | * @param variables 流程实例 |
| | | * @param express 条件表达式 |
| | | * @return 是否满足条件 |
| | | */ |
| | | public static boolean evalConditionExpress(Map<String, Object> variables, String express) { |
| | | if (express == null) { |
| | | return Boolean.FALSE; |
| | | } |
| | | try { |
| | | Object result = FlowableUtils.getExpressionValue(variables, express); |
| | | return Boolean.TRUE.equals(result); |
| | | } catch (FlowableException ex) { |
| | | log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, variables, ex); |
| | | return Boolean.FALSE; |
| | | } |
| | | } |
| | | |
| | | @SuppressWarnings("PatternVariableCanBeUsed") |
| | | public static boolean isSequentialUserTask(FlowElement flowElement) { |
| | | if (!(flowElement instanceof UserTask)) { |
| | | return false; |
| | | } |
| | | UserTask userTask = (UserTask) flowElement; |
| | | MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics(); |
| | | return loopCharacteristics != null && loopCharacteristics.isSequential(); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.util; |
| | | |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.extra.spring.SpringUtil; |
| | | import com.iailab.framework.tenant.core.context.TenantContextHolder; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants; |
| | | import com.iailab.framework.tenant.core.util.TenantUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| | | import org.flowable.common.engine.api.delegate.Expression; |
| | | import org.flowable.common.engine.api.variable.VariableContainer; |
| | | import org.flowable.common.engine.impl.el.ExpressionManager; |
| | | import org.flowable.common.engine.impl.identity.Authentication; |
| | | import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer; |
| | | import org.flowable.engine.ManagementService; |
| | | import org.flowable.engine.ProcessEngineConfiguration; |
| | | import org.flowable.engine.history.HistoricProcessInstance; |
| | | import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; |
| | |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * Flowable 相关的工具方法 |
| | | * |
| | | * @author iailab |
| | | * @author 芋道源码 |
| | | */ |
| | | public class FlowableUtils { |
| | | |
| | |
| | | public static String getTenantId() { |
| | | Long tenantId = TenantContextHolder.getTenantId(); |
| | | return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; |
| | | } |
| | | |
| | | public static void execute(String tenantIdStr, Runnable runnable) { |
| | | if (ObjectUtil.isEmpty(tenantIdStr) |
| | | || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { |
| | | runnable.run(); |
| | | } else { |
| | | Long tenantId = Long.valueOf(tenantIdStr); |
| | | TenantUtils.execute(tenantId, runnable); |
| | | } |
| | | } |
| | | |
| | | // ========== Execution 相关的工具方法 ========== |
| | |
| | | * @return 状态 |
| | | */ |
| | | private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) { |
| | | return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); |
| | | return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); |
| | | } |
| | | |
| | | /** |
| | | * 获得流程实例的审批原因 |
| | | * |
| | | * @param processInstance 流程实例 |
| | | * @return 审批原因 |
| | | */ |
| | | public static String getProcessInstanceReason(HistoricProcessInstance processInstance) { |
| | | return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); |
| | | } |
| | | |
| | | /** |
| | | * 获得流程实例的表单 |
| | | * |
| | | * @param processInstance 流程实例 |
| | | * @return 表单 |
| | | */ |
| | | public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) { |
| | | Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables()); |
| | | return filterProcessInstanceFormVariable(processVariables); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return 表单 |
| | | */ |
| | | public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) { |
| | | Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables()); |
| | | filterProcessInstanceFormVariable(formVariables); |
| | | return formVariables; |
| | | Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables()); |
| | | return filterProcessInstanceFormVariable(processVariables); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return 过滤后的表单 |
| | | */ |
| | | public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) { |
| | | processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); |
| | | processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); |
| | | return processVariables; |
| | | } |
| | | |
| | |
| | | * @param processInstance 流程实例 |
| | | * @return 发起用户选择的审批人 Map |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) { |
| | | return (Map<String, List<Long>>) processInstance.getProcessVariables().get( |
| | | BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); |
| | | return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null; |
| | | } |
| | | |
| | | /** |
| | | * 获得流程实例的发起用户选择的审批人 Map |
| | | * |
| | | * @param processVariables 流程变量 |
| | | * @return 发起用户选择的审批人 Map |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) { |
| | | if (processVariables == null) { |
| | | return null; |
| | | } |
| | | return (Map<String, List<Long>>) processVariables.get( |
| | | BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); |
| | | } |
| | | |
| | | // ========== Task 相关的工具方法 ========== |
| | |
| | | * @return 状态 |
| | | */ |
| | | public static Integer getTaskStatus(TaskInfo task) { |
| | | return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); |
| | | return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return 审批原因 |
| | | */ |
| | | public static String getTaskReason(TaskInfo task) { |
| | | return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON); |
| | | return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return 过滤后的表单 |
| | | */ |
| | | public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) { |
| | | taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS); |
| | | taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON); |
| | | taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS); |
| | | taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON); |
| | | return taskLocalVariables; |
| | | } |
| | | |
| | | // ========== Expression 相关的工具方法 ========== |
| | | |
| | | public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { |
| | | ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); |
| | | private static Object getExpressionValue(VariableContainer variableContainer, String expressionString, |
| | | ProcessEngineConfigurationImpl processEngineConfiguration) { |
| | | assert processEngineConfiguration != null; |
| | | ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); |
| | | assert expressionManager != null; |
| | |
| | | return expression.getValue(variableContainer); |
| | | } |
| | | |
| | | public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { |
| | | ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); |
| | | if (processEngineConfiguration != null) { |
| | | return getExpressionValue(variableContainer, expressionString, processEngineConfiguration); |
| | | } |
| | | // 如果 ProcessEngineConfigurationImpl 获取不到,则需要通过 ManagementService 来获取 |
| | | ManagementService managementService = SpringUtil.getBean(ManagementService.class); |
| | | assert managementService != null; |
| | | return managementService.executeCommand(context -> |
| | | getExpressionValue(variableContainer, expressionString, CommandContextUtil.getProcessEngineConfiguration())); |
| | | } |
| | | |
| | | public static Object getExpressionValue(Map<String, Object> variable, String expressionString) { |
| | | VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty()); |
| | | return getExpressionValue(variableContainer, expressionString); |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.framework.flowable.core.util; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.lang.Assert; |
| | | import cn.hutool.core.map.MapUtil; |
| | | import cn.hutool.core.util.*; |
| | | import com.iailab.framework.common.util.collection.CollectionUtils; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.enums.definition.*; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| | | import com.iailab.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; |
| | | import org.flowable.bpmn.BpmnAutoLayout; |
| | | import org.flowable.bpmn.constants.BpmnXMLConstants; |
| | | import org.flowable.bpmn.model.Process; |
| | | import org.flowable.bpmn.model.*; |
| | | |
| | | import java.util.*; |
| | | |
| | | import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; |
| | | import static com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; |
| | | import static java.util.Arrays.asList; |
| | | |
| | | /** |
| | | * 仿钉钉/飞书的模型相关的工具方法 |
| | | * <p> |
| | | * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法 |
| | | * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类 |
| | | * |
| | | * @author jason |
| | | */ |
| | | public class SimpleModelUtils { |
| | | |
| | | private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap(); |
| | | |
| | | static { |
| | | List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), |
| | | new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), |
| | | new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert()); |
| | | converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); |
| | | } |
| | | |
| | | /** |
| | | * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model |
| | | * <p> |
| | | * 整体逻辑如下: |
| | | * 1. 创建:BpmnModel、Process 对象 |
| | | * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素 |
| | | * 3. 连接:构建并添加节点之间的连线 Sequence Flow |
| | | * |
| | | * @param processId 流程标识 |
| | | * @param processName 流程名称 |
| | | * @param simpleModelNode 仿钉钉流程设计模型数据结构 |
| | | * @return Bpmn Model |
| | | */ |
| | | public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { |
| | | // 1. 创建 BpmnModel |
| | | BpmnModel bpmnModel = new BpmnModel(); |
| | | bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常 |
| | | // 创建 Process 对象 |
| | | Process process = new Process(); |
| | | process.setId(processId); |
| | | process.setName(processName); |
| | | process.setExecutable(Boolean.TRUE); |
| | | bpmnModel.addProcess(process); |
| | | |
| | | // 2.1 创建 StartNode 节点 |
| | | // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 |
| | | BpmSimpleModelNodeVO startNode = buildStartNode(); |
| | | startNode.setChildNode(simpleModelNode); |
| | | // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中 |
| | | traverseNodeToBuildFlowNode(startNode, process); |
| | | |
| | | // 3. 构建并添加节点之间的连线 Sequence Flow |
| | | EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); |
| | | traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); |
| | | |
| | | // 4. 自动布局 |
| | | new BpmnAutoLayout(bpmnModel).execute(); |
| | | return bpmnModel; |
| | | } |
| | | |
| | | private static BpmSimpleModelNodeVO buildStartNode() { |
| | | return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID) |
| | | .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) |
| | | .setType(BpmSimpleModelNodeType.START_NODE.getType()); |
| | | } |
| | | |
| | | /** |
| | | * 遍历节点,构建 FlowNode 元素 |
| | | * |
| | | * @param node SIMPLE 节点 |
| | | * @param process BPMN 流程 |
| | | */ |
| | | private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { |
| | | // 1. 判断是否有效节点 |
| | | if (!isValidNode(node)) { |
| | | return; |
| | | } |
| | | BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); |
| | | Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType()); |
| | | |
| | | // 2. 处理当前节点 |
| | | NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType); |
| | | Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType()); |
| | | List<? extends FlowElement> flowElements = nodeConvert.convertList(node); |
| | | flowElements.forEach(process::addFlowElement); |
| | | |
| | | // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点 |
| | | if (BpmSimpleModelNodeType.isBranchNode(node.getType()) |
| | | && CollUtil.isNotEmpty(node.getConditionNodes())) { |
| | | // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件 |
| | | node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); |
| | | } |
| | | |
| | | // 3.2 情况二:如果有“子”节点,则递归处理子节点 |
| | | traverseNodeToBuildFlowNode(node.getChildNode(), process); |
| | | } |
| | | |
| | | /** |
| | | * 遍历节点,构建 SequenceFlow 元素 |
| | | * |
| | | * @param process Bpmn 流程 |
| | | * @param node 当前节点 |
| | | * @param targetNodeId 目标节点 ID |
| | | */ |
| | | private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { |
| | | // 1.1 无效节点返回 |
| | | if (!isValidNode(node)) { |
| | | return; |
| | | } |
| | | // 1.2 END_NODE 直接返回 |
| | | BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); |
| | | Assert.notNull(nodeType, "模型节点类型不支持"); |
| | | if (nodeType == BpmSimpleModelNodeType.END_NODE) { |
| | | return; |
| | | } |
| | | |
| | | // 2.1 情况一:普通节点 |
| | | if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { |
| | | traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId); |
| | | } else { |
| | | // 2.2 情况二:分支节点 |
| | | traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 遍历普通(非条件)节点,构建 SequenceFlow 元素 |
| | | * |
| | | * @param process Bpmn 流程 |
| | | * @param node 当前节点 |
| | | * @param targetNodeId 目标节点 ID |
| | | */ |
| | | private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { |
| | | BpmSimpleModelNodeVO childNode = node.getChildNode(); |
| | | boolean isChildNodeValid = isValidNode(childNode); |
| | | // 情况一:有“子”节点,则建立连线 |
| | | // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点 |
| | | String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId; |
| | | SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId); |
| | | process.addFlowElement(sequenceFlow); |
| | | |
| | | // 因为有子节点,递归调用后续子节点 |
| | | if (isChildNodeValid) { |
| | | traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 遍历条件节点,构建 SequenceFlow 元素 |
| | | * |
| | | * @param process Bpmn 流程 |
| | | * @param node 当前节点 |
| | | * @param targetNodeId 目标节点 ID |
| | | */ |
| | | private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { |
| | | BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); |
| | | BpmSimpleModelNodeVO childNode = node.getChildNode(); |
| | | List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes(); |
| | | Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); |
| | | // 分支终点节点 ID |
| | | String branchEndNodeId = null; |
| | | if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支 |
| | | // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID |
| | | branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; |
| | | } else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE |
| | | || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支 |
| | | // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 |
| | | branchEndNodeId = buildGatewayJoinId(node.getId()); |
| | | } |
| | | Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); |
| | | |
| | | // 3. 遍历分支节点 |
| | | // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点 |
| | | for (BpmSimpleModelNodeVO item : conditionNodes) { |
| | | Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()), |
| | | "条件节点类型({})不符合", item.getType()); |
| | | BpmSimpleModelNodeVO conditionChildNode = item.getChildNode(); |
| | | // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况 |
| | | if (isValidNode(conditionChildNode)) { |
| | | // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线 |
| | | SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item); |
| | | process.addFlowElement(sequenceFlow); |
| | | // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线 |
| | | traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId); |
| | | } else { |
| | | // 3.2 分支没有后续节点。例如说,建立 A->D 的连线 |
| | | SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item); |
| | | process.addFlowElement(sequenceFlow); |
| | | } |
| | | } |
| | | |
| | | // 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线 |
| | | if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE |
| | | || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) { |
| | | String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; |
| | | SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId); |
| | | process.addFlowElement(sequenceFlow); |
| | | } |
| | | |
| | | // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线 |
| | | traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); |
| | | } |
| | | |
| | | private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) { |
| | | return buildBpmnSequenceFlow(sourceId, targetId, null, null, null); |
| | | } |
| | | |
| | | private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, |
| | | String sequenceFlowId, String sequenceFlowName, |
| | | String conditionExpression) { |
| | | Assert.notEmpty(sourceId, "sourceId 不能为空"); |
| | | Assert.notEmpty(targetId, "targetId 不能为空"); |
| | | // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。 |
| | | // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。 |
| | | SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); |
| | | if (StrUtil.isNotEmpty(sequenceFlowId)) { |
| | | sequenceFlow.setId(sequenceFlowId); |
| | | } |
| | | if (StrUtil.isNotEmpty(sequenceFlowName)) { |
| | | sequenceFlow.setName(sequenceFlowName); |
| | | } |
| | | if (StrUtil.isNotEmpty(conditionExpression)) { |
| | | sequenceFlow.setConditionExpression(conditionExpression); |
| | | } |
| | | return sequenceFlow; |
| | | } |
| | | |
| | | public static boolean isValidNode(BpmSimpleModelNodeVO node) { |
| | | return node != null && node.getId() != null; |
| | | } |
| | | |
| | | public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { |
| | | return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType()) |
| | | && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod()); |
| | | } |
| | | |
| | | // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ========== |
| | | |
| | | private interface NodeConvert { |
| | | |
| | | default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { |
| | | return Collections.singletonList(convert(node)); |
| | | } |
| | | |
| | | default FlowElement convert(BpmSimpleModelNodeVO node) { |
| | | throw new UnsupportedOperationException("请实现该方法"); |
| | | } |
| | | |
| | | BpmSimpleModelNodeType getType(); |
| | | |
| | | } |
| | | |
| | | private static class StartNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public StartEvent convert(BpmSimpleModelNodeVO node) { |
| | | StartEvent startEvent = new StartEvent(); |
| | | startEvent.setId(node.getId()); |
| | | startEvent.setName(node.getName()); |
| | | return startEvent; |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.START_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class EndNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public EndEvent convert(BpmSimpleModelNodeVO node) { |
| | | EndEvent endEvent = new EndEvent(); |
| | | endEvent.setId(node.getId()); |
| | | endEvent.setName(node.getName()); |
| | | // TODO @芋艿 + jason:要不要加一个终止定义? |
| | | return endEvent; |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.END_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class StartUserNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public UserTask convert(BpmSimpleModelNodeVO node) { |
| | | UserTask userTask = new UserTask(); |
| | | userTask.setId(node.getId()); |
| | | userTask.setName(node.getName()); |
| | | |
| | | // 人工审批 |
| | | addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType()); |
| | | // 候选人策略为发起人自己 |
| | | addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask); |
| | | // 添加表单字段权限属性元素 |
| | | addFormFieldsPermission(node.getFieldsPermission(), userTask); |
| | | // 添加操作按钮配置属性元素 |
| | | addButtonsSetting(node.getButtonsSetting(), userTask); |
| | | // 使用自动通过策略 |
| | | // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过; |
| | | addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask); |
| | | return userTask; |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.START_USER_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class ApproveNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public List<FlowElement> convertList(BpmSimpleModelNodeVO node) { |
| | | List<FlowElement> flowElements = new ArrayList<>(2); |
| | | // 1. 构建用户任务 |
| | | UserTask userTask = buildBpmnUserTask(node); |
| | | flowElements.add(userTask); |
| | | |
| | | // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 |
| | | if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { |
| | | BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler()); |
| | | flowElements.add(boundaryEvent); |
| | | } |
| | | return flowElements; |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.APPROVE_NODE; |
| | | } |
| | | |
| | | /** |
| | | * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 |
| | | * |
| | | * @param userTask 审批任务 |
| | | * @param timeoutHandler 超时处理器 |
| | | * @return BoundaryEvent 超时事件 |
| | | */ |
| | | private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, |
| | | BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) { |
| | | // 1.1 定时器边界事件 |
| | | BoundaryEvent boundaryEvent = new BoundaryEvent(); |
| | | boundaryEvent.setId("Event-" + IdUtil.fastUUID()); |
| | | boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 |
| | | boundaryEvent.setAttachedToRef(userTask); |
| | | // 1.2 定义超时时间、最大提醒次数 |
| | | TimerEventDefinition eventDefinition = new TimerEventDefinition(); |
| | | eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); |
| | | if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) && |
| | | timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { |
| | | eventDefinition.setTimeCycle(String.format("R%d/%s", |
| | | timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); |
| | | } |
| | | boundaryEvent.addEventDefinition(eventDefinition); |
| | | |
| | | // 2.1 添加定时器边界事件类型 |
| | | addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType()); |
| | | // 2.2 添加超时执行动作元素 |
| | | addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType()); |
| | | return boundaryEvent; |
| | | } |
| | | |
| | | private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { |
| | | UserTask userTask = new UserTask(); |
| | | userTask.setId(node.getId()); |
| | | userTask.setName(node.getName()); |
| | | |
| | | // 如果不是审批人节点,则直接返回 |
| | | addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType()); |
| | | if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { |
| | | return userTask; |
| | | } |
| | | |
| | | // 添加候选人元素 |
| | | addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); |
| | | // 添加表单字段权限属性元素 |
| | | addFormFieldsPermission(node.getFieldsPermission(), userTask); |
| | | // 添加操作按钮配置属性元素 |
| | | addButtonsSetting(node.getButtonsSetting(), userTask); |
| | | // 处理多实例(审批方式) |
| | | processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); |
| | | // 添加任务被拒绝的处理元素 |
| | | addTaskRejectElements(node.getRejectHandler(), userTask); |
| | | // 添加用户任务的审批人与发起人相同时的处理元素 |
| | | addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); |
| | | // 添加用户任务的空处理元素 |
| | | addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); |
| | | // 设置审批任务的截止时间 |
| | | if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { |
| | | userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); |
| | | } |
| | | return userTask; |
| | | } |
| | | |
| | | private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { |
| | | BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); |
| | | Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); |
| | | // 添加审批方式的扩展属性 |
| | | addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod); |
| | | if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { |
| | | // 随机审批,不需要设置多实例属性 |
| | | return; |
| | | } |
| | | |
| | | // 处理多实例审批方式 |
| | | MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); |
| | | // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错 |
| | | multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); |
| | | if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) { |
| | | multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); |
| | | multiInstanceCharacteristics.setSequential(false); |
| | | } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) { |
| | | multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); |
| | | multiInstanceCharacteristics.setSequential(true); |
| | | multiInstanceCharacteristics.setLoopCardinality("1"); |
| | | } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) { |
| | | Assert.notNull(approveRatio, "通过比例不能为空"); |
| | | multiInstanceCharacteristics.setCompletionCondition( |
| | | String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D))); |
| | | multiInstanceCharacteristics.setSequential(false); |
| | | } |
| | | userTask.setLoopCharacteristics(multiInstanceCharacteristics); |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class CopyNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public ServiceTask convert(BpmSimpleModelNodeVO node) { |
| | | ServiceTask serviceTask = new ServiceTask(); |
| | | serviceTask.setId(node.getId()); |
| | | serviceTask.setName(node.getName()); |
| | | serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); |
| | | serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}"); |
| | | |
| | | // 添加抄送候选人元素 |
| | | addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); |
| | | // 添加表单字段权限属性元素 |
| | | addFormFieldsPermission(node.getFieldsPermission(), serviceTask); |
| | | return serviceTask; |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.COPY_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class ConditionBranchNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public ExclusiveGateway convert(BpmSimpleModelNodeVO node) { |
| | | ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); |
| | | exclusiveGateway.setId(node.getId()); |
| | | // TODO @jason:setName |
| | | |
| | | // 设置默认的序列流(条件) |
| | | BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), |
| | | item -> BooleanUtil.isTrue(item.getDefaultFlow())); |
| | | Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId()); |
| | | exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); |
| | | return exclusiveGateway; |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class ParallelBranchNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) { |
| | | ParallelGateway parallelGateway = new ParallelGateway(); |
| | | parallelGateway.setId(node.getId()); |
| | | // TODO @jason:setName |
| | | |
| | | // 并行聚合网关由程序创建,前端不需要传入 |
| | | ParallelGateway joinParallelGateway = new ParallelGateway(); |
| | | joinParallelGateway.setId(buildGatewayJoinId(node.getId())); |
| | | // TODO @jason:setName |
| | | return CollUtil.newArrayList(parallelGateway, joinParallelGateway); |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static class InclusiveBranchNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) { |
| | | InclusiveGateway inclusiveGateway = new InclusiveGateway(); |
| | | inclusiveGateway.setId(node.getId()); |
| | | // 设置默认的序列流(条件) |
| | | BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), |
| | | item -> BooleanUtil.isTrue(item.getDefaultFlow())); |
| | | Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId()); |
| | | inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); |
| | | // TODO @jason:setName |
| | | |
| | | // 并行聚合网关由程序创建,前端不需要传入 |
| | | InclusiveGateway joinInclusiveGateway = new InclusiveGateway(); |
| | | joinInclusiveGateway.setId(buildGatewayJoinId(node.getId())); |
| | | // TODO @jason:setName |
| | | return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway); |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE; |
| | | } |
| | | |
| | | } |
| | | |
| | | public static class ConditionNodeConvert implements NodeConvert { |
| | | |
| | | @Override |
| | | public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { |
| | | // 原因是:正常情况下,它不会被调用到 |
| | | throw new UnsupportedOperationException("条件节点不支持转换"); |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeType getType() { |
| | | return BpmSimpleModelNodeType.CONDITION_NODE; |
| | | } |
| | | |
| | | public static SequenceFlow buildSequenceFlow(String sourceId, String targetId, |
| | | BpmSimpleModelNodeVO node) { |
| | | String conditionExpression = buildConditionExpression(node); |
| | | return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression); |
| | | } |
| | | |
| | | /** |
| | | * 构造条件表达式 |
| | | * |
| | | * @param node 条件节点 |
| | | */ |
| | | public static String buildConditionExpression(BpmSimpleModelNodeVO node) { |
| | | BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType()); |
| | | if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) { |
| | | return node.getConditionExpression(); |
| | | } |
| | | if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { |
| | | BpmSimpleModelNodeVO.ConditionGroups conditionGroups = node.getConditionGroups(); |
| | | if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) { |
| | | return null; |
| | | } |
| | | List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> { |
| | | if (CollUtil.isEmpty(item.getRules())) { |
| | | return ""; |
| | | } |
| | | // 构造规则表达式 |
| | | List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> { |
| | | String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() |
| | | : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号 |
| | | return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide); |
| | | }); |
| | | // 构造条件组的表达式 |
| | | Boolean and = item.getAnd(); |
| | | return "(" + CollUtil.join(list, and ? " && " : " || ") + ")"; |
| | | }); |
| | | return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | } |
| | | |
| | | private static String buildGatewayJoinId(String id) { |
| | | return id + "_join"; |
| | | } |
| | | |
| | | // ========== SIMPLE 流程预测相关的方法 ========== |
| | | |
| | | public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) { |
| | | List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>(); |
| | | |
| | | // 从头开始遍历 |
| | | simulateNextNode(rootNode, variables, resultNodes); |
| | | return resultNodes; |
| | | } |
| | | |
| | | private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables, |
| | | List<BpmSimpleModelNodeVO> resultNodes) { |
| | | // 如果不合法(包括为空),则直接结束 |
| | | if (!isValidNode(currentNode)) { |
| | | return; |
| | | } |
| | | BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType()); |
| | | Assert.notNull(nodeType, "模型节点类型不支持"); |
| | | |
| | | // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE |
| | | if (nodeType == BpmSimpleModelNodeType.START_NODE |
| | | || nodeType == BpmSimpleModelNodeType.START_USER_NODE |
| | | || nodeType == BpmSimpleModelNodeType.APPROVE_NODE |
| | | || nodeType == BpmSimpleModelNodeType.COPY_NODE |
| | | || nodeType == BpmSimpleModelNodeType.END_NODE) { |
| | | // 添加元素 |
| | | resultNodes.add(currentNode); |
| | | } |
| | | |
| | | // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的 |
| | | if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { |
| | | // 查找满足条件的 BpmSimpleModelNodeVO 节点 |
| | | BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), |
| | | conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow()) |
| | | && evalConditionExpress(variables, conditionNode)); |
| | | if (matchConditionNode == null) { |
| | | matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), |
| | | conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow())); |
| | | } |
| | | Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode); |
| | | // 遍历满足条件的 BpmSimpleModelNodeVO 节点 |
| | | simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes); |
| | | } |
| | | |
| | | // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的 |
| | | if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { |
| | | // 查找满足条件的 BpmSimpleModelNodeVO 节点 |
| | | Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), |
| | | conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow()) |
| | | && evalConditionExpress(variables, conditionNode)); |
| | | if (CollUtil.isEmpty(matchConditionNodes)) { |
| | | matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), |
| | | conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow())); |
| | | } |
| | | Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode); |
| | | // 遍历满足条件的 BpmSimpleModelNodeVO 节点 |
| | | matchConditionNodes.forEach(matchConditionNode -> |
| | | simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); |
| | | } |
| | | |
| | | // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走 |
| | | if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) { |
| | | // 遍历所有 BpmSimpleModelNodeVO 节点 |
| | | currentNode.getConditionNodes().forEach(matchConditionNode -> |
| | | simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); |
| | | } |
| | | |
| | | // 遍历子节点 |
| | | simulateNextNode(currentNode.getChildNode(), variables, resultNodes); |
| | | } |
| | | |
| | | public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) { |
| | | return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode)); |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | List<BpmCategoryDO> getCategoryListByStatus(Integer status); |
| | | |
| | | /** |
| | | * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增 |
| | | * |
| | | * @param ids 分类编号列表 |
| | | */ |
| | | void updateCategorySortBatch(List<Long> ids); |
| | | |
| | | } |
| | |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| | | import com.iailab.module.bpm.dal.mysql.category.BpmCategoryMapper; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.stream.Collectors; |
| | | import java.util.stream.IntStream; |
| | | |
| | | import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; |
| | |
| | | return bpmCategoryMapper.selectListByStatus(status); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateCategorySortBatch(List<Long> ids) { |
| | | // 校验分类都存在 |
| | | List<BpmCategoryDO> categories = bpmCategoryMapper.selectBatchIds(ids); |
| | | if (categories.size() != ids.size()) { |
| | | throw exception(CATEGORY_NOT_EXISTS); |
| | | } |
| | | |
| | | // 批量更新排序 |
| | | List<BpmCategoryDO> updateList = IntStream.range(0, ids.size()) |
| | | .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index)) |
| | | .collect(Collectors.toList()); |
| | | bpmCategoryMapper.updateBatch(updateList); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.service.definition; |
| | | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.engine.repository.Model; |
| | | |
| | | import javax.validation.Valid; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Flowable流程模型接口 |
| | |
| | | public interface BpmModelService { |
| | | |
| | | /** |
| | | * 获得流程模型分页 |
| | | * 获得流程模型列表 |
| | | * |
| | | * @param pageVO 分页查询 |
| | | * @return 流程模型分页 |
| | | * @param name 模型名称 |
| | | * @return 流程模型列表 |
| | | */ |
| | | PageResult<Model> getModelPage(BpmModelPageReqVO pageVO); |
| | | List<Model> getModelList(String name); |
| | | |
| | | /** |
| | | * 创建流程模型 |
| | | * |
| | | * @param modelVO 创建信息 |
| | | * @param bpmnXml BPMN XML |
| | | * @return 创建的流程模型的编号 |
| | | */ |
| | | String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml); |
| | | String createModel(@Valid BpmModelSaveReqVO modelVO); |
| | | |
| | | /** |
| | | * 获得流程模块 |
| | |
| | | byte[] getModelBpmnXML(String id); |
| | | |
| | | /** |
| | | * 修改流程模型的 BPMN XML |
| | | * |
| | | * @param id 编号 |
| | | * @param bpmnXml BPMN XML |
| | | */ |
| | | void updateModelBpmnXml(String id, String bpmnXml); |
| | | |
| | | /** |
| | | * 修改流程模型 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param updateReqVO 更新信息 |
| | | */ |
| | | void updateModel(@Valid BpmModelUpdateReqVO updateReqVO); |
| | | void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * 批量更新模型排序 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param ids 编号列表 |
| | | */ |
| | | void updateModelSortBatch(Long userId, List<String> ids); |
| | | |
| | | /** |
| | | * 将流程模型,部署成一个流程定义 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param id 编号 |
| | | */ |
| | | void deployModel(String id); |
| | | void deployModel(Long userId, String id); |
| | | |
| | | /** |
| | | * 删除模型 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param id 编号 |
| | | */ |
| | | void deleteModel(String id); |
| | | void deleteModel(Long userId, String id); |
| | | |
| | | /** |
| | | * 修改模型的状态,实际更新的部署的流程定义的状态 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param id 编号 |
| | | * @param state 状态 |
| | | */ |
| | | void updateModelState(String id, Integer state); |
| | | void updateModelState(Long userId, String id, Integer state); |
| | | |
| | | /** |
| | | * 获得流程定义编号对应的 BPMN Model |
| | |
| | | */ |
| | | BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); |
| | | |
| | | // ========== 仿钉钉/飞书的精简模型 ========= |
| | | |
| | | /** |
| | | * 获取仿钉钉流程设计模型结构 |
| | | * |
| | | * @param modelId 流程模型编号 |
| | | * @return 仿钉钉流程设计模型结构 |
| | | */ |
| | | BpmSimpleModelNodeVO getSimpleModel(String modelId); |
| | | |
| | | /** |
| | | * 更新仿钉钉流程设计模型 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param reqVO 请求信息 |
| | | */ |
| | | void updateSimpleModel(Long userId, @Valid BpmSimpleModelUpdateReqVO reqVO); |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.service.definition; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.json.JsonUtils; |
| | | import com.iailab.framework.common.util.object.PageUtils; |
| | | import com.iailab.framework.common.util.validation.ValidationUtils; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| | | import com.iailab.module.bpm.convert.definition.BpmModelConvert; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | | import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; |
| | | import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.validation.Valid; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap; |
| | | import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * Flowable流程模型实现 |
| | | * 主要进行 Flowable {@link Model} 的维护 |
| | | * |
| | | * @author yunlongn |
| | | * @author iailab |
| | | * @author jason |
| | | */ |
| | | @Service |
| | | @Validated |
| | |
| | | private BpmTaskCandidateInvoker taskCandidateInvoker; |
| | | |
| | | @Override |
| | | public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) { |
| | | public List<Model> getModelList(String name) { |
| | | ModelQuery modelQuery = repositoryService.createModelQuery(); |
| | | if (StrUtil.isNotBlank(pageVO.getKey())) { |
| | | modelQuery.modelKey(pageVO.getKey()); |
| | | if (StrUtil.isNotEmpty(name)) { |
| | | modelQuery.modelNameLike(name); |
| | | } |
| | | if (StrUtil.isNotBlank(pageVO.getName())) { |
| | | modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配 |
| | | } |
| | | if (StrUtil.isNotBlank(pageVO.getCategory())) { |
| | | modelQuery.modelCategory(pageVO.getCategory()); |
| | | } |
| | | // 执行查询 |
| | | long count = modelQuery.count(); |
| | | if (count == 0) { |
| | | return PageResult.empty(count); |
| | | } |
| | | // 关闭多租户查询,不添加tenantId条件 |
| | | if (StrUtil.isNotBlank(FlowableUtils.getTenantId())) { |
| | | modelQuery.modelTenantId(FlowableUtils.getTenantId()); |
| | | } |
| | | List<Model> models = modelQuery |
| | | .orderByCreateTime().desc() |
| | | .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); |
| | | return new PageResult<>(models, count); |
| | | return modelQuery.list(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String createModel(@Valid BpmModelCreateReqVO createReqVO, String bpmnXml) { |
| | | public String createModel(@Valid BpmModelSaveReqVO createReqVO) { |
| | | if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { |
| | | throw exception(MODEL_KEY_VALID); |
| | | } |
| | | // 校验流程标识已经存在 |
| | | // 1. 校验流程标识已经存在 |
| | | Model keyModel = getModelByKey(createReqVO.getKey()); |
| | | if (keyModel != null) { |
| | | throw exception(MODEL_KEY_EXISTS, createReqVO.getKey()); |
| | | } |
| | | |
| | | // 创建流程定义 |
| | | // 2.1 创建流程定义 |
| | | createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序 |
| | | Model model = repositoryService.newModel(); |
| | | BpmModelConvert.INSTANCE.copyToCreateModel(model, createReqVO); |
| | | BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); |
| | | model.setTenantId(FlowableUtils.getTenantId()); |
| | | // 保存流程定义 |
| | | // 2.2 保存流程定义 |
| | | repositoryService.saveModel(model); |
| | | // 保存 BPMN XML |
| | | saveModelBpmnXml(model, bpmnXml); |
| | | return model.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 |
| | | public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) { |
| | | // 校验流程模型存在 |
| | | Model model = getModel(updateReqVO.getId()); |
| | | public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) { |
| | | // 1. 校验流程模型存在 |
| | | Model model = validateModelManager(updateReqVO.getId(), userId); |
| | | |
| | | // 修改流程定义 |
| | | BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); |
| | | // 更新模型 |
| | | repositoryService.saveModel(model); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateModelSortBatch(Long userId, List<String> ids) { |
| | | // 1.1 校验流程模型存在 |
| | | List<Model> models = repositoryService.createModelQuery() |
| | | .modelTenantId(FlowableUtils.getTenantId()).list(); |
| | | models.removeIf(model ->!ids.contains(model.getId())); |
| | | if (ids.size() != models.size()) { |
| | | throw exception(MODEL_NOT_EXISTS); |
| | | } |
| | | Map<String, Model> modelMap = convertMap(models, Model::getId); |
| | | // 1.2 校验是否为管理员 |
| | | ids.forEach(id -> validateModelManager(id, userId)); |
| | | |
| | | // 保存排序 |
| | | long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序 |
| | | for (int i = ids.size() - 1; i > 0; i--) { |
| | | Model model = modelMap.get(ids.get(i)); |
| | | // 更新模型 |
| | | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort); |
| | | model.setMetaInfo(JsonUtils.toJsonString(metaInfo)); |
| | | repositoryService.saveModel(model); |
| | | // 更新排序 |
| | | processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort); |
| | | sort--; |
| | | } |
| | | } |
| | | |
| | | private Model validateModelExists(String id) { |
| | | Model model = repositoryService.getModel(id); |
| | | if (model == null) { |
| | | throw exception(MODEL_NOT_EXISTS); |
| | | } |
| | | return model; |
| | | } |
| | | |
| | | // 修改流程定义 |
| | | BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateReqVO); |
| | | // 更新模型 |
| | | repositoryService.saveModel(model); |
| | | // 更新 BPMN XML |
| | | saveModelBpmnXml(model, updateReqVO.getBpmnXml()); |
| | | /** |
| | | * 校验是否有流程模型的管理权限 |
| | | * |
| | | * @param id 流程模型编号 |
| | | * @param userId 用户编号 |
| | | * @return 流程模型 |
| | | */ |
| | | private Model validateModelManager(String id, Long userId) { |
| | | Model model = validateModelExists(id); |
| | | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| | | if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { |
| | | throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName()); |
| | | } |
| | | return model; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 |
| | | public void deployModel(String id) { |
| | | public void deployModel(Long userId, String id) { |
| | | // 1.1 校验流程模型存在 |
| | | Model model = getModel(id); |
| | | if (ObjectUtils.isEmpty(model)) { |
| | | throw exception(MODEL_NOT_EXISTS); |
| | | } |
| | | Model model = validateModelManager(id, userId); |
| | | // 1.2 校验流程图 |
| | | byte[] bpmnBytes = getModelBpmnXML(model.getId()); |
| | | validateBpmnXml(bpmnBytes); |
| | | // 1.3 校验表单已配 |
| | | BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); |
| | | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| | | BpmFormDO form = validateFormConfig(metaInfo); |
| | | // 1.4 校验任务分配规则已配置 |
| | | taskCandidateInvoker.validateBpmnConfig(bpmnBytes); |
| | | // 1.5 获取仿钉钉流程设计器模型数据 |
| | | String simpleJson = getModelSimpleJson(model.getId()); |
| | | |
| | | // 2.1 创建流程定义 |
| | | String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form); |
| | | String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form); |
| | | |
| | | // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 |
| | | updateProcessDefinitionSuspended(model.getDeploymentId()); |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteModel(String id) { |
| | | public void deleteModel(Long userId, String id) { |
| | | // 校验流程模型存在 |
| | | Model model = getModel(id); |
| | | if (model == null) { |
| | | throw exception(MODEL_NOT_EXISTS); |
| | | } |
| | | Model model = validateModelManager(id, userId); |
| | | |
| | | // 执行删除 |
| | | repositoryService.deleteModel(id); |
| | | // 禁用流程定义 |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void updateModelState(String id, Integer state) { |
| | | public void updateModelState(Long userId, String id, Integer state) { |
| | | // 1.1 校验流程模型存在 |
| | | Model model = getModel(id); |
| | | if (model == null) { |
| | | throw exception(MODEL_NOT_EXISTS); |
| | | } |
| | | Model model = validateModelManager(id, userId); |
| | | // 1.2 校验流程定义存在 |
| | | ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId()); |
| | | if (definition == null) { |
| | |
| | | return repositoryService.getBpmnModel(processDefinitionId); |
| | | } |
| | | |
| | | @Override |
| | | public BpmSimpleModelNodeVO getSimpleModel(String modelId) { |
| | | Model model = validateModelExists(modelId); |
| | | // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 |
| | | String json = getModelSimpleJson(model.getId()); |
| | | return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class); |
| | | } |
| | | |
| | | @Override |
| | | public void updateSimpleModel(Long userId, BpmSimpleModelUpdateReqVO reqVO) { |
| | | // 1. 校验流程模型存在 |
| | | Model model = validateModelManager(reqVO.getId(), userId); |
| | | |
| | | // 2.1 JSON 转换成 bpmnModel |
| | | BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); |
| | | // 2.2 保存 Bpmn XML |
| | | updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); |
| | | // 2.3 保存 JSON 数据 |
| | | updateModelSimpleJson(model.getId(), reqVO.getSimpleModel()); |
| | | } |
| | | |
| | | /** |
| | | * 校验流程表单已配置 |
| | | * |
| | | * @param metaInfo 流程模型元数据 |
| | | * @return 表单配置 |
| | | */ |
| | | private BpmFormDO validateFormConfig(BpmModelMetaInfoRespDTO metaInfo) { |
| | | private BpmFormDO validateFormConfig(BpmModelMetaInfoVO metaInfo) { |
| | | if (metaInfo == null || metaInfo.getFormType() == null) { |
| | | throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private void saveModelBpmnXml(Model model, String bpmnXml) { |
| | | @Override |
| | | public void updateModelBpmnXml(String id, String bpmnXml) { |
| | | if (StrUtil.isEmpty(bpmnXml)) { |
| | | return; |
| | | } |
| | | repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml)); |
| | | repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); |
| | | } |
| | | |
| | | @SuppressWarnings("JavaExistingMethodCanBeUsed") |
| | | private String getModelSimpleJson(String id) { |
| | | byte[] bytes = repositoryService.getModelEditorSourceExtra(id); |
| | | if (ArrayUtil.isEmpty(bytes)) { |
| | | return null; |
| | | } |
| | | return StrUtil.utf8Str(bytes); |
| | | } |
| | | |
| | | private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) { |
| | | if (node == null) { |
| | | return; |
| | | } |
| | | byte[] bytes = JsonUtils.toJsonByte(node); |
| | | repositoryService.addModelEditorSourceExtra(id, bytes); |
| | | } |
| | | |
| | | /** |
| | | * 挂起 deploymentId 对应的流程定义 |
| | | * |
| | | * <p> |
| | | * 注意:这里一个 deploymentId 只关联一个流程定义 |
| | | * |
| | | * @param deploymentId 流程发布Id |
| | |
| | | } |
| | | |
| | | private Model getModelByKey(String key) { |
| | | return repositoryService.createModelQuery().modelKey(key).singleResult(); |
| | | return repositoryService.createModelQuery() |
| | | .modelTenantId(FlowableUtils.getTenantId()) |
| | | .modelKey(key).singleResult(); |
| | | } |
| | | |
| | | @Override |
| | |
| | | public byte[] getModelBpmnXML(String id) { |
| | | return repositoryService.getModelEditorSource(id); |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.service.definition; |
| | | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | |
| | | * @param suspensionState 中断状态 |
| | | * @return 流程定义列表 |
| | | */ |
| | | List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState); |
| | | List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState, String categoryId); |
| | | |
| | | /** |
| | | * 基于流程模型,创建流程定义 |
| | |
| | | * @param model 流程模型 |
| | | * @param modelMetaInfo 流程模型元信息 |
| | | * @param bpmnBytes BPMN XML 字节数组 |
| | | * @param simpleJson SIMPLE Model JSON |
| | | * @param form 表单 |
| | | * @return 流程编号 |
| | | */ |
| | | String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form); |
| | | String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, |
| | | byte[] bpmnBytes, String simpleJson, BpmFormDO form); |
| | | |
| | | /** |
| | | * 更新流程定义状态 |
| | |
| | | * @param state 状态 |
| | | */ |
| | | void updateProcessDefinitionState(String id, Integer state); |
| | | |
| | | /** |
| | | * 更新模型编号 |
| | | * |
| | | * @param modelId 流程定义编号 |
| | | * @param sort 排序 |
| | | */ |
| | | void updateProcessDefinitionSortByModelId(String modelId, Long sort); |
| | | |
| | | /** |
| | | * 获得流程定义对应的 BPMN |
| | |
| | | ProcessDefinition getActiveProcessDefinition(String key); |
| | | |
| | | /** |
| | | * 判断用户是否可以使用该流程定义,进行流程的发起 |
| | | * |
| | | * @param processDefinition 流程定义 |
| | | * @param userId 用户编号 |
| | | * @return 是否可以发起流程 |
| | | */ |
| | | boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId); |
| | | |
| | | /** |
| | | * 获得 ids 对应的 Deployment Map |
| | | * |
| | | * @param ids 部署编号的数组 |
| | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.object.BeanUtils; |
| | | import com.iailab.framework.common.util.object.PageUtils; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| | | import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; |
| | | import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.ObjectUtils; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.common.engine.impl.db.SuspensionState; |
| | | import org.flowable.engine.RepositoryService; |
| | |
| | | * 流程定义实现 |
| | | * 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护 |
| | | * |
| | | * @author yunlongn |
| | | * @author ZJQ |
| | | * @author iailab |
| | | */ |
| | | @Service |
| | |
| | | |
| | | @Override |
| | | public ProcessDefinition getActiveProcessDefinition(String key) { |
| | | return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult(); |
| | | return repositoryService.createProcessDefinitionQuery() |
| | | .processDefinitionTenantId(FlowableUtils.getTenantId()) |
| | | .processDefinitionKey(key).active().singleResult(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId) { |
| | | if (processDefinition == null) { |
| | | return false; |
| | | } |
| | | // 为空,则所有人都可以发起 |
| | | if (CollUtil.isEmpty(processDefinition.getStartUserIds())) { |
| | | return true; |
| | | } |
| | | // 不为空,则需要存在里面 |
| | | return processDefinition.getStartUserIds().contains(userId); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, |
| | | byte[] bpmnBytes, BpmFormDO form) { |
| | | public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, |
| | | byte[] bpmnBytes, String simpleJson, BpmFormDO form) { |
| | | // 创建 Deployment 部署 |
| | | Deployment deploy = repositoryService.createDeployment() |
| | | .key(model.getKey()).name(model.getName()).category(model.getCategory()) |
| | |
| | | |
| | | // 插入拓展表 |
| | | BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) |
| | | .setModelId(model.getId()).setProcessDefinitionId(definition.getId()); |
| | | .setModelId(model.getId()).setProcessDefinitionId(definition.getId()) |
| | | .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson); |
| | | |
| | | if (form != null) { |
| | | definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void updateProcessDefinitionSortByModelId(String modelId, Long sort) { |
| | | processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort)); |
| | | } |
| | | |
| | | @Override |
| | | public BpmnModel getProcessDefinitionBpmnModel(String id) { |
| | | return repositoryService.getBpmnModel(id); |
| | | } |
| | |
| | | @Override |
| | | public PageResult<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) { |
| | | ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); |
| | | query.processDefinitionTenantId(FlowableUtils.getTenantId()); |
| | | if (StrUtil.isNotBlank(pageVO.getKey())) { |
| | | query.processDefinitionKey(pageVO.getKey()); |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState) { |
| | | public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState, String categoryId) { |
| | | // 拼接查询条件 |
| | | ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); |
| | | if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), suspensionState)) { |
| | |
| | | } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), suspensionState)) { |
| | | query.active(); |
| | | } |
| | | // 执行查询 |
| | | // 关闭多租户查询,不添加tenantId条件 |
| | | if (StrUtil.isNotBlank(FlowableUtils.getTenantId())) { |
| | | query.processDefinitionTenantId(FlowableUtils.getTenantId()); |
| | | } else { |
| | | query.processDefinitionWithoutTenantId(); |
| | | if(ObjectUtils.isNotEmpty(categoryId)) { |
| | | query.processDefinitionCategory(categoryId); |
| | | } |
| | | // 执行查询 |
| | | query.processDefinitionTenantId(FlowableUtils.getTenantId()); |
| | | return query.list(); |
| | | } |
| | | |
| | |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; |
| | | |
| | | import javax.validation.Valid; |
| | | |
| | |
| | | */ |
| | | void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); |
| | | |
| | | /** |
| | | * 发送任务审批超时的消息 |
| | | * |
| | | * @param reqDTO 发送信息 |
| | | */ |
| | | void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); |
| | | |
| | | } |
| | |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; |
| | | import com.iailab.module.system.api.sms.SmsSendApi; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); |
| | | } |
| | | |
| | | @Override |
| | | public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { |
| | | Map<String, Object> templateParams = new HashMap<>(); |
| | | templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); |
| | | templateParams.put("taskName", reqDTO.getTaskName()); |
| | | templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); |
| | | smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), |
| | | BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)).checkError(); |
| | | } |
| | | |
| | | private String getProcessInstanceDetailUrl(String taskId) { |
| | | return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; |
| | | } |
对比新文件 |
| | |
| | | package com.iailab.module.bpm.service.message.dto; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotEmpty; |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | /** |
| | | * BPM 发送任务审批超时 Request DTO |
| | | */ |
| | | @Data |
| | | public class BpmMessageSendWhenTaskTimeoutReqDTO { |
| | | |
| | | /** |
| | | * 流程实例的编号 |
| | | */ |
| | | @NotEmpty(message = "流程实例的编号不能为空") |
| | | private String processInstanceId; |
| | | /** |
| | | * 流程实例的名字 |
| | | */ |
| | | @NotEmpty(message = "流程实例的名字不能为空") |
| | | private String processInstanceName; |
| | | |
| | | /** |
| | | * 流程任务的编号 |
| | | */ |
| | | @NotEmpty(message = "流程任务的编号不能为空") |
| | | private String taskId; |
| | | /** |
| | | * 流程任务的名字 |
| | | */ |
| | | @NotEmpty(message = "流程任务的名字不能为空") |
| | | private String taskName; |
| | | |
| | | /** |
| | | * 审批人的用户编号 |
| | | */ |
| | | @NotNull(message = "审批人的用户编号不能为空") |
| | | private Long assigneeUserId; |
| | | |
| | | } |
| | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; |
| | | import com.iailab.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; |
| | | import org.flowable.bpmn.model.FlowNode; |
| | | |
| | | import javax.validation.constraints.NotEmpty; |
| | | import java.util.Collection; |
| | | |
| | | /** |
| | |
| | | public interface BpmProcessInstanceCopyService { |
| | | |
| | | /** |
| | | * 流程实例的抄送 |
| | | * 【管理员】流程实例的抄送 |
| | | * |
| | | * @param userIds 抄送的用户编号 |
| | | * @param reason 抄送意见 |
| | | * @param taskId 流程任务编号 |
| | | */ |
| | | void createProcessInstanceCopy(Collection<Long> userIds, String taskId); |
| | | void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId); |
| | | |
| | | /** |
| | | * 【自动抄送】流程实例的抄送 |
| | | * |
| | | * @param userIds 抄送的用户编号 |
| | | * @param reason 抄送意见 |
| | | * @param processInstanceId 流程编号 |
| | | * @param activityId 流程活动编号(对应 {@link FlowNode#getId()}) |
| | | * @param activityName 任务编号(对应 {@link FlowNode#getName()}) |
| | | * @param taskId 任务编号,允许空 |
| | | */ |
| | | void createProcessInstanceCopy(Collection<Long> userIds, String reason, |
| | | @NotEmpty(message = "流程实例编号不能为空") String processInstanceId, |
| | | @NotEmpty(message = "流程活动编号不能为空") String activityId, |
| | | @NotEmpty(message = "流程活动名字不能为空") String activityName, |
| | | String taskId); |
| | | |
| | | /** |
| | | * 获得抄送的流程的分页 |
| | |
| | | import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; |
| | | |
| | | |
| | | /** |
| | | * 流程抄送 Service 实现类 |
| | | * |
| | |
| | | private BpmProcessDefinitionService processDefinitionService; |
| | | |
| | | @Override |
| | | public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) { |
| | | // 1.1 校验任务存在 |
| | | public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) { |
| | | Task task = taskService.getTask(taskId); |
| | | if (ObjectUtil.isNull(task)) { |
| | | throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); |
| | | } |
| | | // 1.2 校验流程实例存在 |
| | | String processInstanceId = task.getProcessInstanceId(); |
| | | // 执行抄送 |
| | | createProcessInstanceCopy(userIds, reason, |
| | | task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName()); |
| | | } |
| | | |
| | | @Override |
| | | public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String processInstanceId, |
| | | String activityId, String activityName, String taskId) { |
| | | // 1.1 校验流程实例存在 |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); |
| | | if (processInstance == null) { |
| | | throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); |
| | | } |
| | | // 1.3 校验流程定义存在 |
| | | // 1.2 校验流程定义存在 |
| | | ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( |
| | | processInstance.getProcessDefinitionId()); |
| | | if (processDefinition == null) { |
| | |
| | | |
| | | // 2. 创建抄送流程 |
| | | List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() |
| | | .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) |
| | | .setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId())) |
| | | .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) |
| | | .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName())); |
| | | .setCategory(processDefinition.getCategory()).setTaskId(taskId) |
| | | .setActivityId(activityId).setActivityName(activityName)); |
| | | processInstanceCopyMapper.insertBatch(copyList); |
| | | } |
| | | |
| | |
| | | return processInstanceCopyMapper.selectPage(userId, pageReqVO); |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; |
| | | import org.flowable.engine.delegate.event.FlowableCancelledEvent; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.instance.*; |
| | | import org.flowable.engine.history.HistoricProcessInstance; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | |
| | |
| | | /** |
| | | * 流程实例 Service 接口 |
| | | * |
| | | * @author iailab |
| | | * @author 芋道源码 |
| | | */ |
| | | public interface BpmProcessInstanceService { |
| | | |
| | | // ========== Query 查询相关方法 ========== |
| | | |
| | | /** |
| | | * 获得流程实例 |
| | |
| | | PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, |
| | | @Valid BpmProcessInstancePageReqVO pageReqVO); |
| | | |
| | | // TODO @芋艿:重点在 review 下 |
| | | /** |
| | | * 获取审批详情。 |
| | | * <p> |
| | | * 可以是准备发起的流程、进行中的流程、已经结束的流程 |
| | | * |
| | | * @param loginUserId 登录人的用户编号 |
| | | * @param reqVO 请求信息 |
| | | * @return 流程实例的进度 |
| | | */ |
| | | BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); |
| | | |
| | | /** |
| | | * 获取流程实例的 BPMN 模型视图 |
| | | * |
| | | * @param id 流程实例的编号 |
| | | * @return BPMN 模型视图 |
| | | */ |
| | | BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id); |
| | | |
| | | // ========== Update 写入相关方法 ========== |
| | | |
| | | /** |
| | | * 创建流程实例(提供给前端) |
| | | * |
| | |
| | | void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); |
| | | |
| | | /** |
| | | * 更新 ProcessInstance 拓展记录为取消 |
| | | * 更新 ProcessInstance 为不通过 |
| | | * |
| | | * @param event 流程取消事件 |
| | | * @param processInstance 流程实例 |
| | | * @param reason 理由。例如说,审批不通过时,需要传递该值 |
| | | */ |
| | | void updateProcessInstanceWhenCancel(FlowableCancelledEvent event); |
| | | void updateProcessInstanceReject(ProcessInstance processInstance, String reason); |
| | | |
| | | // ========== Event 事件相关方法 ========== |
| | | |
| | | /** |
| | | * 更新 ProcessInstance 拓展记录为完成 |
| | | * 处理 ProcessInstance 完成事件,例如说:审批通过、不通过、取消 |
| | | * |
| | | * @param instance 流程任务 |
| | | */ |
| | | void updateProcessInstanceWhenApprove(ProcessInstance instance); |
| | | |
| | | /** |
| | | * 更新 ProcessInstance 拓展记录为不通过 |
| | | * |
| | | * @param id 流程编号 |
| | | * @param reason 理由。例如说,审批不通过时,需要传递该值 |
| | | */ |
| | | void updateProcessInstanceReject(String id, String reason); |
| | | void processProcessInstanceCompleted(ProcessInstance instance); |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO;
import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum;
import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants;
import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.message.BpmMessageService;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
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 javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.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 iailab
*/
@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.getProcessDefinitionId())) {
processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%");
}
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).getCheckedData();
// 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);
}
}
|
| | | package com.iailab.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.object.ObjectUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.*;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.enums.ErrorCodeConstants;
import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType;
import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import com.iailab.module.bpm.enums.task.BpmReasonEnum;
import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.message.BpmMessageService;
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
/**
* 流程实例 Service 实现类
* <p>
* ProcessDefinition & ProcessInstance & Execution & Task 的关系:
* 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />
* <p>
* HistoricProcessInstance & ProcessInstance 的关系:
* 1. <a href=" https://my.oschina.net/843294669/blog/71902" />
* <p>
* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
@Resource
private RuntimeService runtimeService;
@Resource
private HistoryService historyService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
@Lazy // 避免循环依赖
private BpmTaskService taskService;
@Resource
private BpmMessageService messageService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
private BpmProcessInstanceEventPublisher processInstanceEventPublisher;
@Resource
private BpmTaskCandidateInvoker taskCandidateInvoker;
// ========== Query 查询相关方法 ==========
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery()
.includeProcessVariables()
.processInstanceId(id)
.singleResult();
}
@Override
public List<ProcessInstance> getProcessInstances(Set<String> ids) {
return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public HistoricProcessInstance getHistoricProcessInstance(String id) {
return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();
}
@Override
public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
BpmProcessInstancePageReqVO pageReqVO) {
// 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
.includeProcessVariables()
.processInstanceTenantId(FlowableUtils.getTenantId())
.orderByProcessInstanceStartTime().desc();
if (userId != null) { // 【我的流程】菜单时,需要传递该字段
processInstanceQuery.startedBy(String.valueOf(userId));
} else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段
processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));
}
if (StrUtil.isNotEmpty(pageReqVO.getName())) {
processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");
}
if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) {
processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey());
}
if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {
processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());
}
if (pageReqVO.getStatus() != null) {
processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());
}
if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {
processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));
processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));
}
// 查询数量
long processInstanceCount = processInstanceQuery.count();
if (processInstanceCount == 0) {
return PageResult.empty(processInstanceCount);
}
// 查询列表
List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());
return new PageResult<>(processInstanceList, processInstanceCount);
}
private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,
String activityId, String taskId) {
// 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id
if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {
activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))
.map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null);
}
if (StrUtil.isEmpty(activityId)) {
return null;
}
// 2. 从 BpmnModel 中解析表单字段权限
return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId);
}
@Override
public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) {
// 1.1 从 reqVO 中,读取公共变量
Long startUserId = loginUserId; // 流程发起人
HistoricProcessInstance historicProcessInstance = null; // 流程实例
Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态
Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量
// 1.2 如果是流程已发起的场景,则使用流程实例的数据
if (reqVO.getProcessInstanceId() != null) {
historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());
if (historicProcessInstance == null) {
throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
}
startUserId = Long.valueOf(historicProcessInstance.getStartUserId());
processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
processVariables = historicProcessInstance.getProcessVariables();
}
// 1.3 读取其它相关数据
ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId());
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
// 2.1 已结束 + 进行中的活动节点
List<ActivityNode> endActivityNodes = null; // 已结束的审批信息
List<ActivityNode> runActivityNodes = null; // 进行中的审批信息
List<HistoricActivityInstance> activities = null; // 流程实例列表
if (reqVO.getProcessInstanceId() != null) {
activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId());
List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true);
endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo,
historicProcessInstance, processInstanceStatus, activities, tasks);
runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks);
}
// 2.2 流程已经结束,直接 return,无需预测
if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) {
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
processInstanceStatus, endActivityNodes, runActivityNodes, null, null);
}
// 3.1 计算当前登录用户的待办任务
// TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。
BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());
// 3.2 预测未运行节点的审批信息
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo,
processVariables, activities);
// 4. 拼接最终数据
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask);
}
/**
* 拼接审批详情的最终数据
* <p>
* 主要是,拼接审批人的用户信息、部门信息
*/
private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,
BpmnModel bpmnModel,
ProcessDefinition processDefinition,
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance processInstance,
Integer processInstanceStatus,
List<ActivityNode> endApprovalNodeInfos,
List<ActivityNode> runningApprovalNodeInfos,
List<ActivityNode> simulateApprovalNodeInfos,
BpmTaskRespVO todoTask) {
// 1. 获取所有需要读取用户信息的 userIds
List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));
Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 2. 表单权限
Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId());
// 3. 拼接数据
return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance,
processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);
}
/**
* 获得【已结束】的活动节点们
*/
private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,
List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
// 遍历 tasks 列表,只处理已结束的 UserTask
// 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点
List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName())
.setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ?
BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType())
.setStatus(FlowableUtils.getTaskStatus(task))
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
.setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
.setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));
// 如果是取消状态,则跳过
if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) {
return null;
}
return activityNode;
});
// 遍历 activities,只处理已结束的 StartEvent、EndEvent
List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null
&& (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END)));
endActivities.forEach(activity -> {
// StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点
if (ELEMENT_EVENT_START.equals(activity.getActivityType())
&& BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {
ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
ActivityNode startNode = new ActivityNode().setId(startTask.getId())
.setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
.setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType())
.setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask))
.setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));
approvalNodes.add(0, startNode);
return;
}
// EndEvent
if (ELEMENT_EVENT_END.equals(activity.getActivityType())) {
if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) {
// 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示
return;
}
ActivityNode endNode = new ActivityNode().setId(activity.getId())
.setName(BpmSimpleModelNodeType.END_NODE.getName())
.setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus)
.setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));
String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance);
if (StrUtil.isNotEmpty(reason)) {
endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId())
.setStatus(endNode.getStatus()).setReason(reason)));
}
approvalNodes.add(endNode);
}
});
return approvalNodes;
}
/**
* 获得【进行中】的活动节点们
*/
private List<ActivityNode> getRunApproveNodeList(Long startUserId,
BpmnModel bpmnModel,
ProcessDefinition processDefinition,
Map<String, Object> processVariables,
List<HistoricActivityInstance> activities,
List<HistoricTaskInstance> tasks) {
// 构建运行中的任务,基于 activityId 分组
List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null
&& (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER)));
Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId);
// 按照 activityId 分组,构建 ApprovalNodeInfo 节点
Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);
return convertList(runningTaskMap.entrySet(), entry -> {
String activityId = entry.getKey();
List<HistoricActivityInstance> taskActivities = entry.getValue();
// 构建活动节点
FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同
ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName())
.setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
.setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime()))
.setTasks(new ArrayList<>());
// 处理每个任务的 tasks 属性
for (HistoricActivityInstance activity : taskActivities) {
HistoricTaskInstance task = taskMap.get(activity.getTaskId());
activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));
// 加签子任务,需要过滤掉已经完成的加签子任务
List<HistoricTaskInstance> childrenTasks = filterList(
taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks),
childTask -> childTask.getEndTime() == null);
if (CollUtil.isNotEmpty(childrenTasks)) {
activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo));
}
}
// 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task
if (BpmnModelUtils.isSequentialUserTask(flowNode)) {
List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(),
startUserId, processDefinition.getId(), processVariables);
// 截取当前审批人位置后面的候选人,不包含当前审批人
ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks());
Assert.notNull(approvalTaskInfo, "任务不能为空");
int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(),
approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner
activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));
}
return activityNode;
});
}
/**
* 获得【预测(未来)】的活动节点们
*/
private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
List<HistoricActivityInstance> activities) {
// TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录
Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
// 情况一:BPMN 设计器
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,
processDefinitionInfo, processVariables, flowElement, runActivityIds));
}
// 情况二:SIMPLE 设计器
if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) {
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);
List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,
processDefinitionInfo, processVariables, simpleNode, runActivityIds));
}
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
}
private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
BpmSimpleModelNodeVO node, Set<String> runActivityIds) {
// TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录
if (runActivityIds.contains(node.getId())) {
return null;
}
ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())
.setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())
.setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
// 1. 开始节点/审批节点
if (ObjectUtils.equalsAny(node.getType(),
BpmSimpleModelNodeType.START_USER_NODE.getType(),
BpmSimpleModelNodeType.APPROVE_NODE.getType())) {
List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
activityNode.setCandidateUserIds(candidateUserIds);
return activityNode;
}
// 2. 结束节点
if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) {
return activityNode;
}
// 3. 抄送节点
if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人
BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) {
return activityNode;
}
return null;
}
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds) {
if (runActivityIds.contains(node.getId())) {
return null;
}
ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
// 1. 开始节点
if (node instanceof StartEvent) {
return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
.setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType());
}
// 2. 审批节点
if (node instanceof UserTask) {
List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType())
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
.setCandidateUserIds(candidateUserIds);
}
// 3. 结束节点
if (node instanceof EndEvent) {
return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName())
.setNodeType(BpmSimpleModelNodeType.END_NODE.getType());
}
return null;
}
private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
startUserId, processDefinitionId, processVariables);
return new ArrayList<>(userIds);
}
@Override
public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) {
// 1.1 获得流程实例
HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
if (processInstance == null) {
return null;
}
// 1.2 获得流程定义
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId());
if (bpmnModel == null) {
return null;
}
BpmSimpleModelNodeVO simpleModel = null;
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
processInstance.getProcessDefinitionId());
if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) {
simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);
}
// 1.3 获得流程实例对应的活动实例列表 + 任务列表
List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id);
List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true);
// 2.1 拼接进度信息
Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
activityInstance -> activityInstance.getEndTime() == null);
Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
activityInstance -> activityInstance.getEndTime() != null
&& ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,
activityInstance -> activityInstance.getEndTime() != null
&& ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));
// 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉
unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds);
// 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。
// 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点
Set<String> rejectTaskActivityIds = CollUtil.newHashSet();
if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) {
tasks.stream()
.filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task)))
.max(Comparator.comparing(HistoricTaskInstance::getEndTime))
.ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey()));
finishedTaskActivityIds.removeAll(rejectTaskActivityIds);
}
// 2.2 拼接基础信息
Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel,
unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds,
userMap, deptMap);
}
// ========== Update 写入相关方法 ==========
@Override
@Transactional(rollbackFor = Exception.class)
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
// 发起流程
return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,
createReqVO.getStartUserSelectAssignees());
}
@Override
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
// 发起流程
return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),
createReqDTO.getStartUserSelectAssignees());
}
private String createProcessInstance0(Long userId, ProcessDefinition definition,
Map<String, Object> variables, String businessKey,
Map<String, List<Long>> startUserSelectAssignees) {
// 1.1 校验流程定义
if (definition == null) {
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
}
if (definition.isSuspended()) {
throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
}
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId());
if (processDefinitionInfo == null) {
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
}
// 1.2 校验是否能够发起
if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) {
throw exception(PROCESS_INSTANCE_START_USER_CAN_START);
}
// 1.3 校验发起人自选审批人
validateStartUserSelectAssignees(definition, startUserSelectAssignees);
// 2. 创建流程实例
if (variables == null) {
variables = new HashMap<>();
}
FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
BpmProcessInstanceStatusEnum.RUNNING.getStatus());
if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
}
ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
.processDefinitionId(definition.getId())
.businessKey(businessKey)
.name(definition.getName().trim())
.variables(variables)
.start();
return instance.getId();
}
private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {
// 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);
if (CollUtil.isEmpty(tasks)) {
return;
}
// 2. 校验发起人自选审批人的审批人和抄送人是否都配置了
tasks.forEach(task -> {
List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
if (CollUtil.isEmpty(assignees)) {
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());
}
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);
assignees.forEach(assignee -> {
if (userMap.get(assignee) == null) {
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee);
}
});
});
}
@Override
public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
// 1.1 校验流程实例存在
ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
}
// 1.2 只能取消自己的
if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
}
// 2. 取消流程
updateProcessInstanceCancel(cancelReqVO.getId(),
BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));
}
@Override
public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {
// 1.1 校验流程实例存在
ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
}
// 2. 取消流程
AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData();
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));
});
}
}
|
| | |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.collection.CollectionUtils; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.*; |
| | | import com.iailab.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; |
| | | import org.flowable.bpmn.model.UserTask; |
| | | import org.flowable.engine.history.HistoricActivityInstance; |
| | | import org.flowable.task.api.Task; |
| | | import org.flowable.task.api.TaskInfo; |
| | | import org.flowable.task.api.history.HistoricTaskInstance; |
| | | |
| | | import javax.validation.Valid; |
| | |
| | | * 流程任务实例 Service 接口 |
| | | * |
| | | * @author jason |
| | | * @author iailab |
| | | * @author 芋道源码 |
| | | */ |
| | | public interface BpmTaskService { |
| | | |
| | | // ========== Query 查询相关方法 ========== |
| | | |
| | | /** |
| | | * 获得待办的流程任务分页 |
| | |
| | | * @return 流程任务分页 |
| | | */ |
| | | PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * 获得用户在指定流程下,首个需要处理(待办)的任务 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param processInstanceId 流程实例编号 |
| | | * @return 待办任务 |
| | | */ |
| | | BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId); |
| | | |
| | | /** |
| | | * 获得已办的流程任务分页 |
| | |
| | | * 获得指定流程实例的流程任务列表,包括所有状态的 |
| | | * |
| | | * @param processInstanceId 流程实例的编号 |
| | | * @param asc 是否升序 |
| | | * @return 流程任务列表 |
| | | */ |
| | | List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId); |
| | | List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc); |
| | | |
| | | /** |
| | | * 获取任务 |
| | | * |
| | | * @param id 任务编号 |
| | | * @return 任务 |
| | | */ |
| | | Task getTask(String id); |
| | | |
| | | /** |
| | | * 获取历史任务 |
| | | * |
| | | * @param id 任务编号 |
| | | * @return 历史任务 |
| | | */ |
| | | HistoricTaskInstance getHistoricTask(String id); |
| | | |
| | | /** |
| | | * 获取历史任务列表 |
| | | * |
| | | * @param taskIds 任务编号集合 |
| | | * @return 历史任务列表 |
| | | */ |
| | | List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds); |
| | | |
| | | /** |
| | | * 根据条件查询正在进行中的任务 |
| | | * |
| | | * @param processInstanceId 流程实例编号,不允许为空 |
| | | * @param assigned 是否分配了审批人,允许空 |
| | | * @param taskDefineKey 任务定义 Key,允许空 |
| | | */ |
| | | List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, |
| | | Boolean assigned, |
| | | String taskDefineKey); |
| | | |
| | | /** |
| | | * 获取当前任务的可退回的 UserTask 集合 |
| | | * |
| | | * @param id 当前的任务 ID |
| | | * @return 可以退回的节点列表 |
| | | */ |
| | | List<UserTask> getUserTaskListByReturn(String id); |
| | | |
| | | /** |
| | | * 获取指定任务的子任务列表(多层) |
| | | * |
| | | * @param parentTaskId 父任务 ID |
| | | * @param tasks 任务列表 |
| | | * @return 子任务列表 |
| | | */ |
| | | <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks); |
| | | |
| | | /** |
| | | * 获取指定任务的子任务列表 |
| | | * |
| | | * @param parentTaskId 父任务ID |
| | | * @return 子任务列表 |
| | | */ |
| | | List<Task> getTaskListByParentTaskId(String parentTaskId); |
| | | |
| | | /** |
| | | * 获得指定流程实例的活动实例列表 |
| | | * |
| | | * @param processInstanceId 流程实例的编号 |
| | | * @return 活动实例列表 |
| | | */ |
| | | List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId); |
| | | |
| | | /** |
| | | * 获得执行编号对应的活动实例 |
| | | * |
| | | * @param executionId 执行编号 |
| | | * @return 活动实例 |
| | | */ |
| | | List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId); |
| | | |
| | | // ========== Update 写入相关方法 ========== |
| | | |
| | | /** |
| | | * 通过任务 |
| | |
| | | void transferTask(Long userId, BpmTaskTransferReqVO reqVO); |
| | | |
| | | /** |
| | | * 更新 Task 状态,在创建时 |
| | | * 将指定流程实例的、进行中的流程任务,移动到结束节点 |
| | | * |
| | | * @param task 任务实体 |
| | | * @param processInstanceId 流程编号 |
| | | */ |
| | | void updateTaskStatusWhenCreated(Task task); |
| | | void moveTaskToEnd(String processInstanceId); |
| | | |
| | | /** |
| | | * 更新 Task 状态,在取消时 |
| | | * |
| | | * @param taskId 任务的编号 |
| | | */ |
| | | void updateTaskStatusWhenCanceled(String taskId); |
| | | |
| | | /** |
| | | * 更新 Task 拓展记录,并发送通知 |
| | | * |
| | | * @param task 任务实体 |
| | | */ |
| | | void updateTaskExtAssign(Task task); |
| | | |
| | | /** |
| | | * 获取任务 |
| | | * |
| | | * @param id 任务编号 |
| | | * @return 任务 |
| | | */ |
| | | Task getTask(String id); |
| | | |
| | | /** |
| | | * 获取当前任务的可回退的 UserTask 集合 |
| | | * |
| | | * @param id 当前的任务 ID |
| | | * @return 可以回退的节点列表 |
| | | */ |
| | | List<UserTask> getUserTaskListByReturn(String id); |
| | | |
| | | /** |
| | | * 将任务回退到指定的 targetDefinitionKey 位置 |
| | | * 将任务退回到指定的 targetDefinitionKey 位置 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param reqVO 回退的任务key和当前所在的任务ID |
| | | * @param reqVO 退回的任务key和当前所在的任务ID |
| | | */ |
| | | void returnTask(Long userId, BpmTaskReturnReqVO reqVO); |
| | | |
| | |
| | | void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); |
| | | |
| | | /** |
| | | * 获取指定任务的子任务列表 |
| | | * 抄送任务 |
| | | * |
| | | * @param parentTaskId 父任务ID |
| | | * @return 子任务列表 |
| | | * @param userId 用户编号 |
| | | * @param reqVO 通过请求 |
| | | */ |
| | | List<Task> getTaskListByParentTaskId(String parentTaskId); |
| | | void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); |
| | | |
| | | // ========== Event 事件相关方法 ========== |
| | | |
| | | /** |
| | | * 通过任务 ID,查询任务名 Map |
| | | * 处理 Task 创建事件,目前是 |
| | | * <p> |
| | | * 1. 更新它的状态为审批中 |
| | | * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 |
| | | * <p> |
| | | * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 |
| | | * |
| | | * @param taskIds 任务 ID |
| | | * @return 任务 ID 与名字的 Map |
| | | * @param task 任务实体 |
| | | */ |
| | | Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds); |
| | | void processTaskCreated(Task task); |
| | | |
| | | /** |
| | | * 处理 Task 取消事件,目前是更新它的状态为已取消 |
| | | * |
| | | * @param taskId 任务的编号 |
| | | */ |
| | | void processTaskCanceled(String taskId); |
| | | |
| | | /** |
| | | * 处理 Task 设置审批人事件,目前是发送审批消息 |
| | | * |
| | | * @param task 任务实体 |
| | | */ |
| | | void processTaskAssigned(Task task); |
| | | |
| | | /** |
| | | * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 |
| | | * |
| | | * @param processInstanceId 流程示例编号 |
| | | * @param taskDefineKey 任务 Key |
| | | * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} |
| | | */ |
| | | void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); |
| | | |
| | | } |
| | |
| | | package com.iailab.module.bpm.service.task; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.hutool.core.util.IdUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.iailab.framework.common.pojo.CommonResult; |
| | | import cn.hutool.core.lang.Assert; |
| | | import cn.hutool.core.util.*; |
| | | import cn.hutool.extra.spring.SpringUtil; |
| | | import com.iailab.framework.common.pojo.PageResult; |
| | | import com.iailab.framework.common.util.date.DateUtils; |
| | | import com.iailab.framework.common.util.number.NumberUtils; |
| | | import com.iailab.framework.common.util.object.ObjectUtils; |
| | | import com.iailab.framework.common.util.object.PageUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.framework.web.core.util.WebFrameworkUtils; |
| | | import com.iailab.module.bpm.controller.admin.task.vo.task.*; |
| | | import com.iailab.module.bpm.convert.task.BpmTaskConvert; |
| | | import com.iailab.module.bpm.enums.definition.*; |
| | | import com.iailab.module.bpm.enums.task.BpmCommentTypeEnum; |
| | | import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum; |
| | | import com.iailab.module.bpm.enums.task.BpmReasonEnum; |
| | | import com.iailab.module.bpm.enums.task.BpmTaskSignTypeEnum; |
| | | import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants; |
| | | import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| | | import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; |
| | | import com.iailab.module.bpm.service.definition.BpmModelService; |
| | | import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import com.iailab.module.bpm.service.message.BpmMessageService; |
| | | import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; |
| | | import com.iailab.module.system.api.dept.DeptApi; |
| | | import com.iailab.module.system.api.dept.dto.DeptRespDTO; |
| | | import com.iailab.module.system.api.user.AdminUserApi; |
| | | import com.iailab.module.system.api.user.dto.AdminUserRespDTO; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.flowable.bpmn.model.BpmnModel; |
| | | import org.flowable.bpmn.model.EndEvent; |
| | | import org.flowable.bpmn.model.FlowElement; |
| | | import org.flowable.bpmn.model.UserTask; |
| | | import org.flowable.engine.HistoryService; |
| | | import org.flowable.engine.ManagementService; |
| | | import org.flowable.engine.RuntimeService; |
| | | import org.flowable.engine.TaskService; |
| | | import org.flowable.engine.history.HistoricActivityInstance; |
| | | import org.flowable.engine.runtime.ProcessInstance; |
| | | import org.flowable.task.api.DelegationState; |
| | | import org.flowable.task.api.Task; |
| | | import org.flowable.task.api.TaskInfo; |
| | | import org.flowable.task.api.TaskQuery; |
| | | import org.flowable.task.api.history.HistoricTaskInstance; |
| | | import org.flowable.task.api.history.HistoricTaskInstanceQuery; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.transaction.support.TransactionSynchronization; |
| | | import org.springframework.transaction.support.TransactionSynchronizationManager; |
| | | import org.springframework.util.Assert; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.validation.Valid; |
| | |
| | | import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static com.iailab.framework.common.util.collection.CollectionUtils.*; |
| | | import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; |
| | | import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; |
| | | |
| | | /** |
| | | * 流程任务实例 Service 实现类 |
| | | * |
| | | * @author iailab |
| | | * @author 芋道源码 |
| | | * @author jason |
| | | */ |
| | | @Slf4j |
| | |
| | | @Resource |
| | | private BpmProcessInstanceService processInstanceService; |
| | | @Resource |
| | | private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource |
| | | private BpmProcessInstanceCopyService processInstanceCopyService; |
| | | @Resource |
| | | private BpmModelService bpmModelService; |
| | | private BpmModelService modelService; |
| | | @Resource |
| | | private BpmMessageService messageService; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | |
| | | // ========== Query 查询相关方法 ========== |
| | | |
| | | @Override |
| | | public PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) { |
| | |
| | | } |
| | | if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { |
| | | taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); |
| | | taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1])); |
| | | taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); |
| | | } |
| | | long count = taskQuery.count(); |
| | | if (count == 0) { |
| | |
| | | } |
| | | List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); |
| | | return new PageResult<>(tasks, count); |
| | | } |
| | | |
| | | @Override |
| | | public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) { |
| | | if (processInstanceId == null) { |
| | | return null; |
| | | } |
| | | // 1. 查询所有任务 |
| | | List<Task> tasks = taskService.createTaskQuery() |
| | | .active() |
| | | .processInstanceId(processInstanceId) |
| | | .includeTaskLocalVariables() |
| | | .includeProcessVariables() |
| | | .orderByTaskCreateTime().asc() // 按创建时间升序 |
| | | .list(); |
| | | if (CollUtil.isEmpty(tasks)) { |
| | | return null; |
| | | } |
| | | |
| | | // 2.1 查询我的首个任务 |
| | | Task todoTask = CollUtil.findOne(tasks, task -> { |
| | | return isAssignUserTask(userId, task) // 当前用户为审批人 |
| | | || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签) |
| | | }); |
| | | if (todoTask == null) { |
| | | return null; |
| | | } |
| | | // 2.2 查询该任务的子任务 |
| | | List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks); |
| | | |
| | | // 3. 转换返回 |
| | | BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId()); |
| | | Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting( |
| | | bpmnModel, todoTask.getTaskDefinitionKey()); |
| | | return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { |
| | | taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); |
| | | taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1])); |
| | | taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); |
| | | } |
| | | // 执行查询 |
| | | long count = taskQuery.count(); |
| | |
| | | } |
| | | if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { |
| | | taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); |
| | | taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1])); |
| | | taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); |
| | | } |
| | | // 执行查询 |
| | | long count = taskQuery.count(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) { |
| | | List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery() |
| | | public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) { |
| | | HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() |
| | | .includeTaskLocalVariables() |
| | | .processInstanceId(processInstanceId) |
| | | .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序 |
| | | .list(); |
| | | .processInstanceId(processInstanceId); |
| | | if (Boolean.TRUE.equals(asc)) { |
| | | query.orderByHistoricTaskInstanceStartTime().asc(); |
| | | } else { |
| | | query.orderByHistoricTaskInstanceStartTime().desc(); |
| | | } |
| | | return query.list(); |
| | | } |
| | | |
| | | /** |
| | | * 校验任务是否存在,并且是否是分配给自己的任务 |
| | | * |
| | | * @param userId 用户 id |
| | | * @param taskId task id |
| | | */ |
| | | private Task validateTask(Long userId, String taskId) { |
| | | Task task = validateTaskExist(taskId); |
| | | // 为什么判断 assignee 非空的情况下? |
| | | // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 |
| | | if (StrUtil.isNotBlank(task.getAssignee()) |
| | | && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { |
| | | throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | private Task validateTaskExist(String id) { |
| | | Task task = getTask(id); |
| | | if (task == null) { |
| | | throw exception(TASK_NOT_EXISTS); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | @Override |
| | | public Task getTask(String id) { |
| | | return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| | | } |
| | | |
| | | @Override |
| | | public HistoricTaskInstance getHistoricTask(String id) { |
| | | return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| | | } |
| | | |
| | | @Override |
| | | public List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds) { |
| | | return historyService.createHistoricTaskInstanceQuery().taskIds(taskIds).includeTaskLocalVariables().list(); |
| | | } |
| | | |
| | | @Override |
| | | public List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) { |
| | | Assert.notNull(processInstanceId, "processInstanceId 不能为空"); |
| | | TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active() |
| | | .includeTaskLocalVariables(); |
| | | if (BooleanUtil.isTrue(assigned)) { |
| | | taskQuery.taskAssigned(); |
| | | } else if (BooleanUtil.isFalse(assigned)) { |
| | | taskQuery.taskUnassigned(); |
| | | } |
| | | if (StrUtil.isNotEmpty(defineKey)) { |
| | | taskQuery.taskDefinitionKey(defineKey); |
| | | } |
| | | return taskQuery.list(); |
| | | } |
| | | |
| | | @Override |
| | | public List<UserTask> getUserTaskListByReturn(String id) { |
| | | // 1.1 校验当前任务 task 存在 |
| | | Task task = validateTaskExist(id); |
| | | // 1.2 根据流程定义获取流程模型信息 |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |
| | | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| | | if (source == null) { |
| | | throw exception(TASK_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2.1 查询该任务的前置任务节点的 key 集合 |
| | | List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); |
| | | if (CollUtil.isEmpty(previousUserList)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 |
| | | previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); |
| | | return previousUserList; |
| | | } |
| | | |
| | | @Override |
| | | public <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks) { |
| | | if (CollUtil.isEmpty(tasks)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return tasks; |
| | | Map<String, List<T>> parentTaskMap = convertMultiMap( |
| | | filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId); |
| | | if (CollUtil.isEmpty(parentTaskMap)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | List<T> result = new ArrayList<>(); |
| | | // 1. 递归获取子级 |
| | | Stack<String> stack = new Stack<>(); |
| | | stack.push(parentTaskId); |
| | | // 2. 递归遍历 |
| | | for (int i = 0; i < Short.MAX_VALUE; i++) { |
| | | if (stack.isEmpty()) { |
| | | break; |
| | | } |
| | | // 2.1 获取子任务们 |
| | | String taskId = stack.pop(); |
| | | List<T> childTaskList = filterList(tasks, task -> StrUtil.equals(task.getParentTaskId(), taskId)); |
| | | // 2.2 如果非空,则添加到 stack 进一步递归 |
| | | if (CollUtil.isNotEmpty(childTaskList)) { |
| | | stack.addAll(convertList(childTaskList, TaskInfo::getId)); |
| | | result.addAll(childTaskList); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 获得所有子任务列表 |
| | | * |
| | | * @param parentTask 父任务 |
| | | * @return 所有子任务列表 |
| | | */ |
| | | private List<Task> getAllChildTaskList(Task parentTask) { |
| | | List<Task> result = new ArrayList<>(); |
| | | // 1. 递归获取子级 |
| | | Stack<Task> stack = new Stack<>(); |
| | | stack.push(parentTask); |
| | | // 2. 递归遍历 |
| | | for (int i = 0; i < Short.MAX_VALUE; i++) { |
| | | if (stack.isEmpty()) { |
| | | break; |
| | | } |
| | | // 2.1 获取子任务们 |
| | | Task task = stack.pop(); |
| | | List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); |
| | | // 2.2 如果非空,则添加到 stack 进一步递归 |
| | | if (CollUtil.isNotEmpty(childTaskList)) { |
| | | stack.addAll(childTaskList); |
| | | result.addAll(childTaskList); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public List<Task> getTaskListByParentTaskId(String parentTaskId) { |
| | | String tableName = managementService.getTableName(TaskEntity.class); |
| | | // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 |
| | | String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; |
| | | return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); |
| | | } |
| | | |
| | | /** |
| | | * 获取子任务个数 |
| | | * |
| | | * @param parentTaskId 父任务 ID |
| | | * @return 剩余子任务个数 |
| | | */ |
| | | private Long getTaskCountByParentTaskId(String parentTaskId) { |
| | | String tableName = managementService.getTableName(TaskEntity.class); |
| | | String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; |
| | | return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); |
| | | } |
| | | |
| | | /** |
| | | * 获得任务根任务的父任务编号 |
| | | * |
| | | * @param task 任务 |
| | | * @return 根任务的父任务编号 |
| | | */ |
| | | private String getTaskRootParentId(Task task) { |
| | | if (task == null || task.getParentTaskId() == null) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < Short.MAX_VALUE; i++) { |
| | | Task parentTask = getTask(task.getParentTaskId()); |
| | | if (parentTask == null) { |
| | | return null; |
| | | } |
| | | if (parentTask.getParentTaskId() == null) { |
| | | return parentTask.getId(); |
| | | } |
| | | task = parentTask; |
| | | } |
| | | throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId())); |
| | | } |
| | | |
| | | @Override |
| | | public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) { |
| | | return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) |
| | | .orderByHistoricActivityInstanceStartTime().asc().list(); |
| | | } |
| | | |
| | | @Override |
| | | public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) { |
| | | return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); |
| | | } |
| | | |
| | | /** |
| | | * 判断指定用户,是否是当前任务的审批人 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param task 任务 |
| | | * @return 是否 |
| | | */ |
| | | private boolean isAssignUserTask(Long userId, Task task) { |
| | | Long assignee = NumberUtil.parseLong(task.getAssignee(), null); |
| | | return ObjectUtil.equals(userId, assignee); |
| | | } |
| | | |
| | | /** |
| | | * 判断指定用户,是否是当前任务的拥有人 |
| | | * |
| | | * @param userId 用户编号 |
| | | * @param task 任务 |
| | | * @return 是否 |
| | | */ |
| | | private boolean isOwnerUserTask(Long userId, Task task) { |
| | | Long assignee = NumberUtil.parseLong(task.getOwner(), null); |
| | | return ObjectUtil.equal(userId, assignee); |
| | | } |
| | | |
| | | /** |
| | | * 判断指定用户,是否是当前任务的加签人 |
| | | * |
| | | * @param userId 用户 Id |
| | | * @param task 任务 |
| | | * @return 是否 |
| | | */ |
| | | private boolean isAddSignUserTask(Long userId, Task task) { |
| | | return (isAssignUserTask(userId, task) || isOwnerUserTask(userId, task)) |
| | | && BpmTaskSignTypeEnum.of(task.getScopeType()) != null; |
| | | } |
| | | |
| | | // ========== Update 写入相关方法 ========== |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); |
| | | if (instance == null) { |
| | | throw exception(PROCESS_INSTANCE_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2. 抄送用户 |
| | | if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { |
| | | processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); |
| | | } |
| | | |
| | | // 情况一:被委派的任务,不调用 complete 去完成任务 |
| | |
| | | } |
| | | |
| | | // 情况三:审批普通的任务。大多数情况下,都是这样 |
| | | // 3.1 更新 task 状态、原因 |
| | | // 2.1 更新 task 状态、原因 |
| | | updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason()); |
| | | // 3.2 添加评论 |
| | | // 2.2 添加评论 |
| | | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), |
| | | BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); |
| | | // 3.3 调用 BPM complete 去完成任务 |
| | | // 2.3 调用 BPM complete 去完成任务 |
| | | // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 |
| | | if (CollUtil.isNotEmpty(reqVO.getVariables())) { |
| | | Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); |
| | | // 修改表单的值需要存储到 ProcessInstance 变量 |
| | | runtimeService.setVariables(task.getProcessInstanceId(), variables); |
| | | taskService.complete(task.getId(), variables, true); |
| | | } else { |
| | | taskService.complete(task.getId()); |
| | |
| | | |
| | | /** |
| | | * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务: |
| | | * |
| | | * <p> |
| | | * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批 |
| | | * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批 |
| | | * |
| | |
| | | } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { |
| | | // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 |
| | | // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 |
| | | Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); |
| | | Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); |
| | | if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) { |
| | | return; |
| | | } |
| | |
| | | throw exception(PROCESS_INSTANCE_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2.1 更新流程实例为不通过 |
| | | // 2.1 更新流程任务为不通过 |
| | | updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); |
| | | // 2.2 添加评论 |
| | | // 2.2 添加流程评论 |
| | | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), |
| | | BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); |
| | | // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 |
| | | // 疑问:为什么要标记未通过呢? |
| | | // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 |
| | | if (task.getParentTaskId() != null) { |
| | | String rootParentId = getTaskRootParentId(task); |
| | | updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(), |
| | | BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); |
| | | taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), |
| | | BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); |
| | | } |
| | | |
| | | // 3. 更新流程实例,审批不通过! |
| | | processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); |
| | | // 3. 根据不同的 RejectHandler 处理策略 |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |
| | | FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| | | // 3.1 情况一:驳回到指定的任务节点 |
| | | BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); |
| | | if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { |
| | | String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); |
| | | Assert.notNull(returnTaskId, "退回的节点不能为空"); |
| | | returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) |
| | | .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); |
| | | return; |
| | | } |
| | | // 3.2 情况二:直接结束,审批不通过 |
| | | processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason()); // 标记不通过 |
| | | moveTaskToEnd(task.getProcessInstanceId()); // 结束流程 |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param status 状态 |
| | | */ |
| | | private void updateTaskStatus(String id, Integer status) { |
| | | taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status); |
| | | taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_STATUS, status); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | private void updateTaskStatusAndReason(String id, Integer status, String reason) { |
| | | updateTaskStatus(id, status); |
| | | taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason); |
| | | } |
| | | |
| | | /** |
| | | * 校验任务是否存在,并且是否是分配给自己的任务 |
| | | * |
| | | * @param userId 用户 id |
| | | * @param taskId task id |
| | | */ |
| | | private Task validateTask(Long userId, String taskId) { |
| | | Task task = validateTaskExist(taskId); |
| | | if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { |
| | | throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | @Override |
| | | public void updateTaskStatusWhenCreated(Task task) { |
| | | Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); |
| | | if (status != null) { |
| | | log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); |
| | | return; |
| | | } |
| | | updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); |
| | | } |
| | | |
| | | @Override |
| | | public void updateTaskStatusWhenCanceled(String taskId) { |
| | | Task task = getTask(taskId); |
| | | // 1. 可能只是活动,不是任务,所以查询不到 |
| | | if (task == null) { |
| | | log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); |
| | | return; |
| | | } |
| | | |
| | | // 2. 更新 task 状态 + 原因 |
| | | Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); |
| | | if (BpmTaskStatusEnum.isEndStatus(status)) { |
| | | log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); |
| | | return; |
| | | } |
| | | updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); |
| | | // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 |
| | | } |
| | | |
| | | @Override |
| | | public void updateTaskExtAssign(Task task) { |
| | | // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 |
| | | TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { |
| | | |
| | | @Override |
| | | public void afterCommit() { |
| | | if (StrUtil.isEmpty(task.getAssignee())) { |
| | | return; |
| | | } |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); |
| | | AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); |
| | | messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); |
| | | } |
| | | |
| | | }); |
| | | } |
| | | |
| | | private Task validateTaskExist(String id) { |
| | | Task task = getTask(id); |
| | | if (task == null) { |
| | | throw exception(TASK_NOT_EXISTS); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | @Override |
| | | public Task getTask(String id) { |
| | | return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| | | } |
| | | |
| | | private HistoricTaskInstance getHistoricTask(String id) { |
| | | return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| | | } |
| | | |
| | | @Override |
| | | public List<UserTask> getUserTaskListByReturn(String id) { |
| | | // 1.1 校验当前任务 task 存在 |
| | | Task task = validateTaskExist(id); |
| | | // 1.2 根据流程定义获取流程模型信息 |
| | | BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |
| | | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| | | if (source == null) { |
| | | throw exception(TASK_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2.1 查询该任务的前置任务节点的 key 集合 |
| | | List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); |
| | | if (CollUtil.isEmpty(previousUserList)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 |
| | | previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); |
| | | return previousUserList; |
| | | taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_REASON, reason); |
| | | } |
| | | |
| | | @Override |
| | |
| | | FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), |
| | | reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); |
| | | |
| | | // 2. 调用 Flowable 框架的回退逻辑 |
| | | // 2. 调用 Flowable 框架的退回逻辑 |
| | | returnTask(task, targetElement, reqVO); |
| | | } |
| | | |
| | | /** |
| | | * 回退流程节点时,校验目标任务节点是否可回退 |
| | | * 退回流程节点时,校验目标任务节点是否可退回 |
| | | * |
| | | * @param sourceKey 当前任务节点 Key |
| | | * @param targetKey 目标任务节点 key |
| | |
| | | */ |
| | | private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { |
| | | // 1.1 获取流程模型信息 |
| | | BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); |
| | | // 1.3 获取当前任务节点元素 |
| | | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); |
| | | // 1.3 获取跳转的节点元素 |
| | |
| | | throw exception(TASK_TARGET_NODE_NOT_EXISTS); |
| | | } |
| | | |
| | | // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 |
| | | // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 |
| | | if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { |
| | | throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 执行回退逻辑 |
| | | * 执行退回逻辑 |
| | | * |
| | | * @param currentTask 当前回退的任务 |
| | | * @param targetElement 需要回退到的目标任务 |
| | | * @param currentTask 当前退回的任务 |
| | | * @param targetElement 需要退回到的目标任务 |
| | | * @param reqVO 前端参数封装 |
| | | */ |
| | | public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { |
| | |
| | | List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); |
| | | List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); |
| | | |
| | | // 2. 给当前要被回退的 task 数组,设置回退意见 |
| | | // 2. 给当前要被退回的 task 数组,设置退回意见 |
| | | taskList.forEach(task -> { |
| | | // 需要排除掉,不需要设置回退意见的任务 |
| | | // 需要排除掉,不需要设置退回意见的任务 |
| | | if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { |
| | | return; |
| | | } |
| | |
| | | updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); |
| | | }); |
| | | |
| | | // 3. 执行驳回 |
| | | // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 |
| | | runtimeService.setVariable(currentTask.getProcessInstanceId(), |
| | | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); |
| | | |
| | | // 4. 执行驳回 |
| | | runtimeService.createChangeActivityStateBuilder() |
| | | .processInstanceId(currentTask.getProcessInstanceId()) |
| | | .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多) |
| | |
| | | taskService.setOwner(taskId, task.getAssignee()); |
| | | // 3.2 执行委派,将任务委派给 delegateUser |
| | | taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); |
| | | // 3.3 更新 task 状态。 |
| | | // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用 |
| | | updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus()); |
| | | // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 |
| | | } |
| | | |
| | | @Override |
| | |
| | | // 3.2 执行转派(审批人),将任务转派给 assigneeUser |
| | | // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!! |
| | | taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); |
| | | } |
| | | |
| | | @Override |
| | | public void moveTaskToEnd(String processInstanceId) { |
| | | List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null); |
| | | if (CollUtil.isEmpty(taskList)) { |
| | | return; |
| | | } |
| | | |
| | | // 1. 其它未结束的任务,直接取消 |
| | | // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? |
| | | // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 |
| | | taskList.forEach(task -> { |
| | | Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); |
| | | if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { |
| | | return; |
| | | } |
| | | processTaskCanceled(task.getId()); |
| | | }); |
| | | |
| | | // 2. 终止流程 |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); |
| | | List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); |
| | | EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); |
| | | Assert.notNull(endEvent, "结束节点不能未空"); |
| | | runtimeService.createChangeActivityStateBuilder() |
| | | .processInstanceId(processInstanceId) |
| | | .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId()) |
| | | .changeState(); |
| | | } |
| | | |
| | | @Override |
| | |
| | | handleParentTaskIfSign(task.getParentTaskId()); |
| | | } |
| | | |
| | | @Override |
| | | public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) { |
| | | processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 校验任务是否能被减签 |
| | | * |
| | |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 获得所有子任务列表 |
| | | * |
| | | * @param parentTask 父任务 |
| | | * @return 所有子任务列表 |
| | | */ |
| | | private List<Task> getAllChildTaskList(Task parentTask) { |
| | | List<Task> result = new ArrayList<>(); |
| | | // 1. 递归获取子级 |
| | | Stack<Task> stack = new Stack<>(); |
| | | stack.push(parentTask); |
| | | // 2. 递归遍历 |
| | | for (int i = 0; i < Short.MAX_VALUE; i++) { |
| | | if (stack.isEmpty()) { |
| | | break; |
| | | } |
| | | // 2.1 获取子任务们 |
| | | Task task = stack.pop(); |
| | | List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); |
| | | // 2.2 如果非空,则添加到 stack 进一步递归 |
| | | if (CollUtil.isNotEmpty(childTaskList)) { |
| | | stack.addAll(childTaskList); |
| | | result.addAll(childTaskList); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | // ========== Event 事件相关方法 ========== |
| | | |
| | | @Override |
| | | public List<Task> getTaskListByParentTaskId(String parentTaskId) { |
| | | String tableName = managementService.getTableName(TaskEntity.class); |
| | | // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 |
| | | String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; |
| | | return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); |
| | | public void processTaskCreated(Task task) { |
| | | // 1. 设置为待办中 |
| | | Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); |
| | | if (status != null) { |
| | | log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); |
| | | return; |
| | | } |
| | | updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); |
| | | |
| | | // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); |
| | | if (processInstance == null) { |
| | | log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); |
| | | return; |
| | | } |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); |
| | | FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| | | Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement); |
| | | Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement); |
| | | TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { |
| | | |
| | | @Override |
| | | public void afterCompletion(int transactionStatus) { |
| | | // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以 |
| | | // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时 |
| | | if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) { |
| | | return; |
| | | } |
| | | // TODO 芋艿:可以后续优化成 getSelf(); |
| | | // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 |
| | | if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { |
| | | // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝 |
| | | if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) { |
| | | return; |
| | | } |
| | | if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { |
| | | SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() |
| | | .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); |
| | | } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { |
| | | SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() |
| | | .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); |
| | | } |
| | | // 特殊情况二:【自动审核】审批类型为自动通过、不通过 |
| | | } else { |
| | | if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { |
| | | SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() |
| | | .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); |
| | | } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { |
| | | SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() |
| | | .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 获取子任务个数 |
| | | * |
| | | * @param parentTaskId 父任务 ID |
| | | * @return 剩余子任务个数 |
| | | * 重要补充说明:该方法目前主要有两个情况会调用到: |
| | | * <p> |
| | | * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 |
| | | * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 |
| | | */ |
| | | private Long getTaskCountByParentTaskId(String parentTaskId) { |
| | | String tableName = managementService.getTableName(TaskEntity.class); |
| | | String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; |
| | | return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); |
| | | @Override |
| | | public void processTaskCanceled(String taskId) { |
| | | Task task = getTask(taskId); |
| | | // 1. 可能只是活动,不是任务,所以查询不到 |
| | | if (task == null) { |
| | | log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); |
| | | return; |
| | | } |
| | | |
| | | // 2. 更新 task 状态 + 原因 |
| | | Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); |
| | | if (BpmTaskStatusEnum.isEndStatus(status)) { |
| | | log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); |
| | | return; |
| | | } |
| | | updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); |
| | | // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) { |
| | | if (CollUtil.isEmpty(taskIds)) { |
| | | return Collections.emptyMap(); |
| | | public void processTaskAssigned(Task task) { |
| | | // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 |
| | | TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { |
| | | |
| | | @Override |
| | | public void afterCommit() { |
| | | if (StrUtil.isEmpty(task.getAssignee())) { |
| | | log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId()); |
| | | return; |
| | | } |
| | | List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list(); |
| | | return convertMap(tasks, Task::getId, Task::getName); |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); |
| | | if (processInstance == null) { |
| | | log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); |
| | | return; |
| | | } |
| | | // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 |
| | | if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { |
| | | // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 |
| | | // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 |
| | | Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), |
| | | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); |
| | | if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { |
| | | BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); |
| | | if (bpmnModel == null) { |
| | | log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); |
| | | return; |
| | | } |
| | | FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| | | Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); |
| | | |
| | | // 情况一:自动跳过 |
| | | if (ObjectUtils.equalsAny(assignStartUserHandlerType, |
| | | BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { |
| | | getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) |
| | | .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); |
| | | return; |
| | | } |
| | | // 情况二:转交给部门负责人审批 |
| | | if (ObjectUtils.equalsAny(assignStartUserHandlerType, |
| | | BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { |
| | | AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); |
| | | Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); |
| | | DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()).getCheckedData() : null; |
| | | Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); |
| | | // 找不到部门负责人的情况下,自动审批通过 |
| | | // noinspection DataFlowIssue |
| | | if (dept.getLeaderUserId() == null) { |
| | | getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) |
| | | .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); |
| | | return; |
| | | } |
| | | // 找得到部门负责人的情况下,修改负责人 |
| | | if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { |
| | | getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() |
| | | .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) |
| | | .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); |
| | | return; |
| | | } |
| | | // 如果部门负责人是自己,还是自己审批吧~ |
| | | } |
| | | } |
| | | } |
| | | |
| | | AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); |
| | | messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); |
| | | } |
| | | |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) { |
| | | ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); |
| | | if (processInstance == null) { |
| | | log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); |
| | | return; |
| | | } |
| | | List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey); |
| | | // TODO 优化:未来需要考虑加签的情况 |
| | | if (CollUtil.isEmpty(taskList)) { |
| | | log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey); |
| | | return; |
| | | } |
| | | |
| | | taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { |
| | | // 情况一:自动提醒 |
| | | if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType())) { |
| | | messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() |
| | | .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) |
| | | .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); |
| | | return; |
| | | } |
| | | |
| | | // 情况二:自动同意 |
| | | if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.APPROVE.getType())) { |
| | | approveTask(Long.parseLong(task.getAssignee()), |
| | | new BpmTaskApproveReqVO().setId(task.getId()).setReason(BpmReasonEnum.TIMEOUT_APPROVE.getReason())); |
| | | return; |
| | | } |
| | | |
| | | // 情况三:自动拒绝 |
| | | if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REJECT.getType())) { |
| | | rejectTask(Long.parseLong(task.getAssignee()), |
| | | new BpmTaskRejectReqVO().setId(task.getId()).setReason(BpmReasonEnum.REJECT_TASK.getReason())); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | /** |
| | | * 获得自身的代理对象,解决 AOP 生效问题 |
| | | * |
| | | * @return 自己 |
| | | */ |
| | | private BpmTaskServiceImpl getSelf() { |
| | | return SpringUtil.getBean(getClass()); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.iailab.framework.tenant.core.db.dynamic.TenantDS; |
| | | import com.iailab.module.model.mcs.pre.entity.MmModelArithSettingsEntity; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | * @param list |
| | | */ |
| | | void insertList(List<MmModelArithSettingsEntity> list); |
| | | |
| | | void updatePyFile(@Param("likeValue") String likeValue,@Param("value") String value); |
| | | } |
| | |
| | | void saveList(List<MmModelArithSettingsEntity> list); |
| | | |
| | | List<MmModelArithSettingsEntity> getByModelId(String modelId); |
| | | |
| | | void updatePyFile(String pyModule, String fileName); |
| | | } |
| | |
| | | modelIdMap.put(modelId, list); |
| | | return list; |
| | | } |
| | | |
| | | @Override |
| | | public void updatePyFile(String pyModule, String fileName) { |
| | | baseMapper.updatePyFile(pyModule + "." + fileName.substring(0,fileName.lastIndexOf("_")),pyModule + "." + fileName); |
| | | } |
| | | } |
| | |
| | | import com.iailab.framework.tenant.core.db.dynamic.TenantDS; |
| | | import com.iailab.module.model.mcs.sche.entity.StScheduleModelSettingEntity; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | /** |
| | | * @author PanZhibao |
| | |
| | | @TenantDS |
| | | @Mapper |
| | | public interface StScheduleModelSettingDao extends BaseMapperX<StScheduleModelSettingEntity> { |
| | | void updatePyFile(@Param("likeValue") String likeValue, @Param("value") String value); |
| | | } |
| | |
| | | void deleteByModelId(String modelId); |
| | | |
| | | void saveList(String modelId, List<StScheduleModelSettingSaveReqVO> saveList); |
| | | |
| | | void updatePyFile(String pyModule, String fileName); |
| | | } |
| | |
| | | baseDao.insert(entity); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void updatePyFile(String pyModule, String fileName) { |
| | | baseDao.updatePyFile(pyModule + "." + fileName.substring(0,fileName.lastIndexOf("_")),pyModule + "." + fileName); |
| | | } |
| | | } |
| | |
| | | import com.iailab.framework.security.core.util.SecurityFrameworkUtils; |
| | | import com.iailab.framework.tenant.core.context.TenantContextHolder; |
| | | import com.iailab.module.infra.api.config.ConfigApi; |
| | | import com.iailab.module.model.mcs.pre.service.MmModelArithSettingsService; |
| | | import com.iailab.module.model.mcs.sche.service.StScheduleModelSettingService; |
| | | import com.iailab.module.model.mpk.common.MdkConstant; |
| | | import com.iailab.module.model.mpk.common.utils.CmdUtils; |
| | | import com.iailab.module.model.mpk.common.utils.DllUtils; |
| | |
| | | |
| | | @Autowired |
| | | private ProjectPackageHistoryModelService projectPackageHistoryModelService; |
| | | |
| | | @Autowired |
| | | private MmModelArithSettingsService mmModelArithSettingsService; |
| | | |
| | | @Autowired |
| | | private StScheduleModelSettingService stScheduleModelSettingService; |
| | | |
| | | @Autowired |
| | | private ConfigApi configApi; |
| | |
| | | } |
| | | } |
| | | |
| | | // 修改预测项pyFile参数 |
| | | mmModelArithSettingsService.updatePyFile(dto.getPyModule(),fileName); |
| | | // 修改调度项pyFile参数 |
| | | stScheduleModelSettingService.updatePyFile(dto.getPyModule(),fileName); |
| | | |
| | | MpkFileEntity entity = ConvertUtils.sourceToTarget(dto, MpkFileEntity.class); |
| | | entity.setUpdater(SecurityFrameworkUtils.getLoginUserId()); |
| | | entity.setUpdateDate(new Date()); |
| | |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | |
| | | <mapper namespace="com.iailab.module.model.mcs.pre.dao.MmModelArithSettingsDao"> |
| | | <update id="updatePyFile"> |
| | | update t_mm_model_arith_settings set value = #{value} where `key` = 'pyFile' and `value` like CONCAT(#{likeValue},'%') |
| | | </update> |
| | | <select id="getMmModelArithSettings" resultType="com.iailab.module.model.mcs.pre.entity.MmModelArithSettingsEntity" |
| | | parameterType="map"> |
| | | SELECT |
对比新文件 |
| | |
| | | <?xml version="1.0" encoding="UTF-8" ?> |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > |
| | | <mapper namespace="com.iailab.module.model.mcs.sche.dao.StScheduleModelSettingDao"> |
| | | <update id="updatePyFile"> |
| | | update t_st_schedule_model_setting set value = #{value} where `key` = 'pyFile' and `value` like CONCAT(#{likeValue},'%') |
| | | </update> |
| | | </mapper> |
| | |
| | | jstring keyJString = env->NewStringUTF("pyFile"); |
| | | jobject javaValueObj = env->CallObjectMethod(settings, getMID, keyJString); |
| | | const char* strValue = env->GetStringUTFChars((jstring)javaValueObj, NULL); |
| | | cout << strValue << endl; |
| | | |
| | | PyObject* pModule = create_py_module(strValue); |
| | | /*PyObject* pModule = create_py_module("${pyModule}.${pyName}");*/ |
| | |
| | | LambdaQueryWrapperX<MenuDO> menuWrapper = new LambdaQueryWrapperX<>(); |
| | | menuWrapper.eq(MenuDO::getAppId, app.getId()); |
| | | menuWrapper.eq(MenuDO::getType, MenuTypeEnum.DIR.getType()); |
| | | menuWrapper.eq(MenuDO::getParentId, 0); |
| | | MenuDO menu = menuMapper.selectOne(menuWrapper); |
| | | TenantDO tenantDO = tenantMapper.selectById(app.getTenantId()); |
| | | if(ObjectUtils.isNotEmpty(menu) && ObjectUtils.isNotEmpty(tenantDO)) { |