Commit 528a321f0aa178b4b36b764075371ca1d3ada7e0

Authored by YunaiV
1 parent acea73c9

BPM:增加「发起人自选」的任务审批人的分配策略

Showing 33 changed files with 305 additions and 189 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java
... ... @@ -32,14 +32,13 @@ public class BpmProcessInstanceCreateReqDTO {
32 32 @NotEmpty(message = "业务的唯一标识")
33 33 private String businessKey;
34 34  
35   - // TODO @hai:assignees 复数
36 35 /**
37   - * 提前指派的审批人
  36 + * 发起人自选审批人 Map
38 37 *
39 38 * key:taskKey 任务编码
40 39 * value:审批人的数组
41   - * 例如: { taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
  40 + * 例如:{ taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
42 41 */
43   - private Map<String, List<Long>> assignee;
  42 + private Map<String, List<Long>> startUserSelectAssignees;
44 43  
45 44 }
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
... ... @@ -29,12 +29,13 @@ public interface ErrorCodeConstants {
29 29 ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1_009_003_001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
30 30 ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_009_003_002, "流程定义不存在");
31 31 ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1_009_003_003, "流程定义处于挂起状态");
32   - ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1_009_003_004, "流程定义的模型不存在");
33 32  
34 33 // ========== 流程实例 1-009-004-000 ==========
35 34 ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
36 35 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
37 36 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
  37 + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置");
  38 + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在");
38 39  
39 40 // ========== 流程任务 1-009-005-000 ==========
40 41 ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
... ... @@ -50,8 +51,6 @@ public interface ErrorCodeConstants {
50 51 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
51 52 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
52 53 ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
53   -
54   - // ========== 流程任务分配规则 1-009-006-000 TODO 芋艿:这里要改下 ==========
55 54 ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
56 55  
57 56 // ========== 动态表单模块 1-009-010-000 ==========
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConver
9 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
10 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
11 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  12 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
12 13 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
13 14 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
14 15 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
... ... @@ -16,6 +17,8 @@ import io.swagger.v3.oas.annotations.Operation;
16 17 import io.swagger.v3.oas.annotations.Parameter;
17 18 import io.swagger.v3.oas.annotations.tags.Tag;
18 19 import jakarta.annotation.Resource;
  20 +import org.flowable.bpmn.model.BpmnModel;
  21 +import org.flowable.bpmn.model.UserTask;
19 22 import org.flowable.engine.repository.Deployment;
20 23 import org.flowable.engine.repository.ProcessDefinition;
21 24 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -89,13 +92,23 @@ public class BpmProcessDefinitionController {
89 92 list, null, processDefinitionMap, null, null));
90 93 }
91 94  
92   - @GetMapping ("/get-bpmn-xml")
93   - @Operation(summary = "获得流程定义的 BPMN XML")
94   - @Parameter(name = "id", description = "编号", required = true, example = "1024")
  95 + @GetMapping ("/get")
  96 + @Operation(summary = "获得流程定义")
  97 + @Parameter(name = "id", description = "流程编号", required = true, example = "1024")
  98 + @Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
95 99 @PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
96   - public CommonResult<String> getProcessDefinitionBpmnXML(@RequestParam("id") String id) {
97   - String bpmnXML = processDefinitionService.getProcessDefinitionBpmnXML(id);
98   - return success(bpmnXML);
  100 + public CommonResult<BpmProcessDefinitionRespVO> getProcessDefinition(
  101 + @RequestParam(value = "id", required = false) String id,
  102 + @RequestParam(value = "key", required = false) String key) {
  103 + ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id)
  104 + : processDefinitionService.getActiveProcessDefinition(key);
  105 + if (processDefinition == null) {
  106 + return success(null);
  107 + }
  108 + BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
  109 + List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
  110 + return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
  111 + processDefinition, null, null, null, null, bpmnModel, userTaskList));
99 112 }
100 113  
101 114 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java
... ... @@ -18,6 +18,9 @@ public class BpmCategorySaveReqVO {
18 18 @NotEmpty(message = "分类名不能为空")
19 19 private String name;
20 20  
  21 + @Schema(description = "分类描述", example = "你猜")
  22 + private String description;
  23 +
21 24 @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
22 25 @NotEmpty(message = "分类标志不能为空")
23 26 private String code;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
... ... @@ -48,7 +48,7 @@ public class BpmProcessDefinitionRespVO {
48 48 private String formCustomViewPath;
49 49  
50 50 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
51   - private Integer suspensionState;
  51 + private Integer suspensionState; // 参见 SuspensionState 枚举
52 52  
53 53 @Schema(description = "部署时间")
54 54 private LocalDateTime deploymentTime; // 需要从对应的 Deployment 读取,非必须返回
... ... @@ -56,4 +56,19 @@ public class BpmProcessDefinitionRespVO {
56 56 @Schema(description = "BPMN XML")
57 57 private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
58 58  
  59 + @Schema(description = "发起用户需要选择审批人的任务数组")
  60 + private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
  61 +
  62 + @Schema(description = "BPMN UserTask 用户任务")
  63 + @Data
  64 + public static class UserTask {
  65 +
  66 + @Schema(description = "任务标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "sudo")
  67 + private String id;
  68 +
  69 + @Schema(description = "任务名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  70 + private String name;
  71 +
  72 + }
  73 +
59 74 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java
... ... @@ -7,6 +7,8 @@ import lombok.Data;
7 7 import org.springframework.format.annotation.DateTimeFormat;
8 8  
9 9 import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +import java.util.Map;
10 12  
11 13 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
12 14  
... ... @@ -30,6 +32,9 @@ public class BpmOALeaveCreateReqVO {
30 32 @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读芋道源码")
31 33 private String reason;
32 34  
  35 + @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}")
  36 + private Map<String, List<Long>> startUserSelectAssignees;
  37 +
33 38 @AssertTrue(message = "结束时间,需要在开始时间之后")
34 39 public boolean isEndTimeValid() {
35 40 return !getEndTime().isBefore(getStartTime());
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
... ... @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI
11 11 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
12 12 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
13 13 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  14 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
14 15 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
15 16 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
16 17 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
... ... @@ -130,7 +131,8 @@ public class BpmProcessInstanceController {
130 131 processInstance.getProcessDefinitionId());
131 132 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
132 133 processInstance.getProcessDefinitionId());
133   - String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId());
  134 + String bpmnXml = BpmnModelUtils.getBpmnXml(
  135 + processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()));
134 136 AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
135 137 DeptRespDTO dept = null;
136 138 if (startUser != null) {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java
... ... @@ -18,8 +18,7 @@ public class BpmProcessInstanceCreateReqVO {
18 18 @Schema(description = "变量实例(动态表单)")
19 19 private Map<String, Object> variables;
20 20  
21   - // TODO @hai:assignees 复数
22   - @Schema(description = "提前指派的审批人", requiredMode = Schema.RequiredMode.REQUIRED, example = "{taskKey1: [1, 2]}")
23   - private Map<String, List<Long>> assignee;
  21 + @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}")
  22 + private Map<String, List<Long>> startUserSelectAssignees;
24 23  
25 24 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
... ... @@ -4,12 +4,14 @@ import cn.hutool.core.date.LocalDateTimeUtil;
4 4 import cn.hutool.core.map.MapUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7   -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
10 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
11 10 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
12 11 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  12 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  13 +import org.flowable.bpmn.model.BpmnModel;
  14 +import org.flowable.bpmn.model.UserTask;
13 15 import org.flowable.common.engine.impl.db.SuspensionState;
14 16 import org.flowable.engine.repository.Deployment;
15 17 import org.flowable.engine.repository.ProcessDefinition;
... ... @@ -47,27 +49,50 @@ public interface BpmProcessDefinitionConvert {
47 49 Map<Long, BpmFormDO> formMap,
48 50 Map<String, BpmCategoryDO> categoryMap) {
49 51 return CollectionUtils.convertList(list, definition -> {
50   - BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
51   - respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
52   - // Deployment
53   - MapUtils.findAndThen(deploymentMap, definition.getDeploymentId(),
54   - deployment -> respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime())));
55   - // BpmProcessDefinitionInfoDO
  52 + Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
56 53 BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class);
  54 + BpmFormDO form = null;
57 55 if (processDefinitionInfo != null) {
58   - copyTo(processDefinitionInfo, respVO);
59   - // Form
60   - BpmFormDO form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
61   - if (form != null) {
62   - respVO.setFormName(form.getName());
63   - }
  56 + form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
64 57 }
65   - // Category
66   - MapUtils.findAndThen(categoryMap, definition.getCategory(), category -> respVO.setCategoryName(category.getName()));
67   - return respVO;
  58 + BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class);
  59 + return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null);
68 60 });
69 61 }
70 62  
  63 + default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition,
  64 + Deployment deployment,
  65 + BpmProcessDefinitionInfoDO processDefinitionInfo,
  66 + BpmFormDO form,
  67 + BpmCategoryDO category,
  68 + BpmnModel bpmnModel,
  69 + List<UserTask> startUserSelectUserTaskList) {
  70 + BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
  71 + respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
  72 + // Deployment
  73 + if (deployment != null) {
  74 + respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime()));
  75 + }
  76 + // BpmProcessDefinitionInfoDO
  77 + if (processDefinitionInfo != null) {
  78 + copyTo(processDefinitionInfo, respVO);
  79 + // Form
  80 + if (form != null) {
  81 + respVO.setFormName(form.getName());
  82 + }
  83 + }
  84 + // Category
  85 + if (category != null) {
  86 + respVO.setCategoryName(category.getName());
  87 + }
  88 + // BpmnModel
  89 + if (bpmnModel != null) {
  90 + respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
  91 + respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class));
  92 + }
  93 + return respVO;
  94 + }
  95 +
71 96 @Mapping(source = "from.id", target = "to.id", ignore = true)
72 97 void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
73 98  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
... ... @@ -68,7 +68,7 @@ public interface BpmProcessInstanceConvert {
68 68 DeptRespDTO dept) {
69 69 BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class);
70 70 respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance));
71   - respVO.setFormVariables(FlowableUtils.filterProcessInstanceFormVariable(processInstance.getProcessVariables()));
  71 + respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
72 72 // definition
73 73 respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
74 74 copyTo(processDefinitionExt, respVO.getProcessDefinition());
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmCategoryDO.java
1 1 package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
2 2  
3   -import lombok.*;
4   -import java.util.*;
5   -import java.time.LocalDateTime;
6   -import java.time.LocalDateTime;
7   -import com.baomidou.mybatisplus.annotation.*;
8 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import com.baomidou.mybatisplus.annotation.KeySequence;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.*;
9 8  
10 9 /**
11 10 * BPM 流程分类 DO
... ... @@ -42,7 +41,7 @@ public class BpmCategoryDO extends BaseDO {
42 41 /**
43 42 * 分类状态
44 43 *
45   - * 枚举 {@link TODO common_status 对应的类}
  44 + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
46 45 */
47 46 private Integer status;
48 47 /**
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/category/BpmCategoryMapper.java
... ... @@ -21,7 +21,7 @@ public interface BpmCategoryMapper extends BaseMapperX&lt;BpmCategoryDO&gt; {
21 21 default PageResult<BpmCategoryDO> selectPage(BpmCategoryPageReqVO reqVO) {
22 22 return selectPage(reqVO, new LambdaQueryWrapperX<BpmCategoryDO>()
23 23 .likeIfPresent(BpmCategoryDO::getName, reqVO.getName())
24   - .eqIfPresent(BpmCategoryDO::getCode, reqVO.getCode())
  24 + .likeIfPresent(BpmCategoryDO::getCode, reqVO.getCode())
25 25 .eqIfPresent(BpmCategoryDO::getStatus, reqVO.getStatus())
26 26 .betweenIfPresent(BpmCategoryDO::getCreateTime, reqVO.getCreateTime())
27 27 .orderByAsc(BpmCategoryDO::getSort));
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/FlowableContextHolder.java deleted
1   -package cn.iocoder.yudao.module.bpm.framework;
2   -
3   -import cn.hutool.core.collection.CollUtil;
4   -import com.alibaba.ttl.TransmittableThreadLocal;
5   -
6   -import java.util.Collections;
7   -import java.util.List;
8   -import java.util.Map;
9   -
10   -/**
11   - * 工作流--用户用到的上下文相关信息
12   - */
13   -@Deprecated // TODO 芋艿:找个方式,去掉这个上下文
14   -public class FlowableContextHolder {
15   -
16   - private static final ThreadLocal<Map<String, List<Long>>> ASSIGNEE = new TransmittableThreadLocal<>();
17   -
18   - /**
19   - * 通过流程任务的定义 key ,拿到提前选好的审批人
20   - * 此方法目的:首次创建流程实例时,数据库中还查询不到 assignee 字段,所以存入上下文中获取
21   - *
22   - * @param taskDefinitionKey 流程任务 key
23   - * @return 审批人 ID 集合
24   - */
25   - public static List<Long> getAssigneeByTaskDefinitionKey(String taskDefinitionKey) {
26   - if (CollUtil.isNotEmpty(ASSIGNEE.get())) {
27   - return ASSIGNEE.get().get(taskDefinitionKey);
28   - }
29   - return Collections.emptyList();
30   - }
31   -
32   - /**
33   - * 存入提前选好的审批人到上下文线程变量中
34   - *
35   - * @param assignee 流程任务 key -> 审批人 ID 炅和
36   - */
37   - public static void setAssignee(Map<String, List<Long>> assignee) {
38   - ASSIGNEE.set(assignee);
39   - }
40   -
41   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
... ... @@ -4,16 +4,13 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7   -import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
9 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
10   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
11 9 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
12 10 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
13 11 import com.google.common.annotations.VisibleForTesting;
14 12 import lombok.extern.slf4j.Slf4j;
15 13 import org.flowable.bpmn.model.BpmnModel;
16   -import org.flowable.bpmn.model.FlowElement;
17 14 import org.flowable.bpmn.model.UserTask;
18 15 import org.flowable.engine.delegate.DelegateExecution;
19 16  
... ... @@ -60,9 +57,13 @@ public class BpmTaskCandidateInvoker {
60 57 // 遍历所有的 UserTask,校验审批人配置
61 58 userTaskList.forEach(userTask -> {
62 59 // 1. 非空校验
63   - Integer strategy = parseCandidateStrategy(userTask);
64   - String param = parseCandidateParam(userTask);
65   - if (strategy == null || StrUtil.isBlank(param)) {
  60 + Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
  61 + String param = BpmnModelUtils.parseCandidateParam(userTask);
  62 + if (strategy == null) {
  63 + throw exception(MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG, userTask.getName());
  64 + }
  65 + BpmTaskCandidateStrategy candidateStrategy = getCandidateStrategy(strategy);
  66 + if (candidateStrategy.isParamRequired() && StrUtil.isBlank(param)) {
66 67 throw exception(MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG, userTask.getName());
67 68 }
68 69 // 2. 具体策略校验
... ... @@ -77,16 +78,8 @@ public class BpmTaskCandidateInvoker {
77 78 * @return 用户编号集合
78 79 */
79 80 public Set<Long> calculateUsers(DelegateExecution execution) {
80   - // TODO 芋艿:这里需要重构
81   -// // 1. 先从提前选好的审批人中获取
82   -// List<Long> assignee = processInstanceService.getAssigneeByProcessInstanceIdAndTaskDefinitionKey(
83   -// execution.getProcessInstanceId(), execution.getCurrentActivityId());
84   -// if (CollUtil.isNotEmpty(assignee)) {
85   -// // TODO @hai:new HashSet 即可
86   -// return convertSet(assignee, Function.identity());
87   -// }
88   - Integer strategy = parseCandidateStrategy(execution.getCurrentFlowElement());
89   - String param = parseCandidateParam(execution.getCurrentFlowElement());
  81 + Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
  82 + String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
90 83 // 1.1 计算任务的候选人
91 84 Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
92 85 // 1.2 移除被禁用的用户
... ... @@ -113,16 +106,6 @@ public class BpmTaskCandidateInvoker {
113 106 });
114 107 }
115 108  
116   - private static Integer parseCandidateStrategy(FlowElement userTask) {
117   - return NumberUtils.parseInt(userTask.getAttributeValue(
118   - BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
119   - }
120   -
121   - private static String parseCandidateParam(FlowElement userTask) {
122   - return userTask.getAttributeValue(
123   - BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
124   - }
125   -
126 109 private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
127 110 BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
128 111 Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
... ... @@ -36,4 +36,13 @@ public interface BpmTaskCandidateStrategy {
36 36 */
37 37 Set<Long> calculateUsers(DelegateExecution execution, String param);
38 38  
  39 + /**
  40 + * 是否一定要输入参数
  41 + *
  42 + * @return 是否
  43 + */
  44 + default boolean isParamRequired() {
  45 + return true;
  46 + }
  47 +
39 48 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java
... ... @@ -32,12 +32,12 @@ public class BpmTaskAssignLeaderExpression {
32 32 private DeptApi deptApi;
33 33  
34 34 @Resource
35   - private BpmProcessInstanceService bpmProcessInstanceService;
  35 + private BpmProcessInstanceService processInstanceService;
36 36  
37 37 protected Set<Long> calculateUsers(DelegateExecution execution, int level) {
38 38 Assert.isTrue(level > 0, "level 必须大于 0");
39 39 // 获得发起人
40   - ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
  40 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
41 41 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
42 42 // 获得对应 leve 的部门
43 43 DeptRespDTO dept = null;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java
... ... @@ -19,10 +19,10 @@ import java.util.Set;
19 19 public class BpmTaskAssignStartUserExpression {
20 20  
21 21 @Resource
22   - private BpmProcessInstanceService bpmProcessInstanceService;
  22 + private BpmProcessInstanceService processInstanceService;
23 23  
24 24 public Set<Long> calculateUsers(DelegateExecution execution) {
25   - ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
  25 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
26 26 Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
27 27 return SetUtils.asSet(startUserId);
28 28 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  7 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
  9 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
  10 +import jakarta.annotation.Resource;
  11 +import org.flowable.bpmn.model.BpmnModel;
  12 +import org.flowable.bpmn.model.UserTask;
  13 +import org.flowable.engine.delegate.DelegateExecution;
  14 +import org.flowable.engine.runtime.ProcessInstance;
  15 +import org.springframework.context.annotation.Lazy;
  16 +import org.springframework.stereotype.Component;
  17 +
  18 +import java.util.*;
  19 +
  20 +/**
  21 + * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@Component
  26 +public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidateStrategy {
  27 +
  28 + @Resource
  29 + @Lazy // 延迟加载,避免循环依赖
  30 + private BpmProcessInstanceService processInstanceService;
  31 +
  32 + @Override
  33 + public BpmTaskCandidateStrategyEnum getStrategy() {
  34 + return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
  35 + }
  36 +
  37 + @Override
  38 + public void validateParam(String param) {}
  39 +
  40 + @Override
  41 + public Set<Long> calculateUsers(DelegateExecution execution, String param) {
  42 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
  43 + Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
  44 + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
  45 + Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空",
  46 + execution.getProcessInstanceId());
  47 + // 获得审批人
  48 + List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
  49 + return new LinkedHashSet<>(assignees);
  50 + }
  51 +
  52 + @Override
  53 + public boolean isParamRequired() {
  54 + return false;
  55 + }
  56 +
  57 + /**
  58 + * 获得发起人自选审批人的 UserTask 列表
  59 + *
  60 + * @param bpmnModel BPMN 模型
  61 + * @return UserTask 列表
  62 + */
  63 + public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) {
  64 + if (bpmnModel == null) {
  65 + return null;
  66 + }
  67 + List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
  68 + if (CollUtil.isEmpty(userTaskList)) {
  69 + return null;
  70 + }
  71 + userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask),
  72 + BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
  73 + return userTaskList;
  74 + }
  75 +
  76 +}
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java
... ... @@ -15,6 +15,12 @@ public class BpmConstants {
15 15 * @see ProcessInstance#getProcessVariables()
16 16 */
17 17 public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS";
  18 + /**
  19 + * 流程实例的变量 - 发起用户选择的审批人 Map
  20 + *
  21 + * @see ProcessInstance#getProcessVariables()
  22 + */
  23 + public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
18 24  
19 25 /**
20 26 * 任务的变量 - 状态
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
... ... @@ -20,6 +20,7 @@ public enum BpmTaskCandidateStrategyEnum {
20 20 DEPT_LEADER(21, "部门的负责人"),
21 21 POST(22, "岗位"),
22 22 USER(30, "用户"),
  23 + START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
23 24 USER_GROUP(40, "用户组"),
24 25 EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
25 26 ;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ArrayUtil;
  5 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  6 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
5 7 import org.flowable.bpmn.converter.BpmnXMLConverter;
6 8 import org.flowable.bpmn.model.Process;
7 9 import org.flowable.bpmn.model.*;
... ... @@ -14,6 +16,16 @@ import java.util.*;
14 16 */
15 17 public class BpmnModelUtils {
16 18  
  19 + public static Integer parseCandidateStrategy(FlowElement userTask) {
  20 + return NumberUtils.parseInt(userTask.getAttributeValue(
  21 + BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
  22 + }
  23 +
  24 + public static String parseCandidateParam(FlowElement userTask) {
  25 + return userTask.getAttributeValue(
  26 + BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
  27 + }
  28 +
17 29 /**
18 30 * 根据节点,获取入口连线
19 31 *
... ... @@ -91,6 +103,14 @@ public class BpmnModelUtils {
91 103 return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false);
92 104 }
93 105  
  106 + public static String getBpmnXml(BpmnModel model) {
  107 + if (model == null) {
  108 + return null;
  109 + }
  110 + BpmnXMLConverter converter = new BpmnXMLConverter();
  111 + return new String(converter.convertToXML(model));
  112 + }
  113 +
94 114 // ========== 遍历相关的方法 ==========
95 115  
96 116 /**
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
... ... @@ -12,6 +12,7 @@ import org.flowable.engine.runtime.ProcessInstance;
12 12 import org.flowable.task.api.TaskInfo;
13 13  
14 14 import java.util.HashMap;
  15 +import java.util.List;
15 16 import java.util.Map;
16 17  
17 18 /**
... ... @@ -79,7 +80,7 @@ public class FlowableUtils {
79 80 * @param processInstance 流程实例
80 81 * @return 表单
81 82 */
82   - public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) {
  83 + public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) {
83 84 Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
84 85 filterProcessInstanceFormVariable(formVariables);
85 86 return formVariables;
... ... @@ -98,6 +99,18 @@ public class FlowableUtils {
98 99 return processVariables;
99 100 }
100 101  
  102 + /**
  103 + * 获得流程实例的发起用户选择的审批人 Map
  104 + *
  105 + * @param processInstance 流程实例
  106 + * @return 发起用户选择的审批人 Map
  107 + */
  108 + @SuppressWarnings("unchecked")
  109 + public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) {
  110 + return (Map<String, List<Long>>) processInstance.getProcessVariables().get(
  111 + BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
  112 + }
  113 +
101 114 // ========== Task 相关的工具方法 ==========
102 115  
103 116 /**
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceImpl.java
... ... @@ -33,7 +33,7 @@ public class BpmFormServiceImpl implements BpmFormService {
33 33  
34 34 @Override
35 35 public Long createForm(BpmFormSaveReqVO createReqVO) {
36   - this.vadateFields(createReqVO.getFields());
  36 + this.validateFields(createReqVO.getFields());
37 37 // 插入
38 38 BpmFormDO form = BeanUtils.toBean(createReqVO, BpmFormDO.class);
39 39 formMapper.insert(form);
... ... @@ -43,7 +43,7 @@ public class BpmFormServiceImpl implements BpmFormService {
43 43  
44 44 @Override
45 45 public void updateForm(BpmFormSaveReqVO updateReqVO) {
46   - vadateFields(updateReqVO.getFields());
  46 + validateFields(updateReqVO.getFields());
47 47 // 校验存在
48 48 validateFormExists(updateReqVO.getId());
49 49 // 更新
... ... @@ -93,7 +93,7 @@ public class BpmFormServiceImpl implements BpmFormService {
93 93 *
94 94 * @param fields field 数组
95 95 */
96   - private void vadateFields(List<String> fields) {
  96 + private void validateFields(List<String> fields) {
97 97 if (true) { // TODO 芋艿:兼容 Vue3 工作流:因为采用了新的表单设计器,所以暂时不校验
98 98 return;
99 99 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmPro
5 5 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
6 6 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
7 7 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
  8 +import org.flowable.bpmn.model.BpmnModel;
8 9 import org.flowable.engine.repository.Deployment;
9 10 import org.flowable.engine.repository.Model;
10 11 import org.flowable.engine.repository.ProcessDefinition;
... ... @@ -61,12 +62,12 @@ public interface BpmProcessDefinitionService {
61 62 void updateProcessDefinitionState(String id, Integer state);
62 63  
63 64 /**
64   - * 获得流程定义对应的 BPMN XML
  65 + * 获得流程定义对应的 BPMN
65 66 *
66 67 * @param id 流程定义编号
67   - * @return BPMN XML
  68 + * @return BPMN
68 69 */
69   - String getProcessDefinitionBpmnXML(String id);
  70 + BpmnModel getProcessDefinitionBpmnModel(String id);
70 71  
71 72 /**
72 73 * 获得流程定义的信息
... ... @@ -89,9 +90,9 @@ public interface BpmProcessDefinitionService {
89 90 }
90 91  
91 92 /**
92   - * 获得编号对应的 ProcessDefinition
  93 + * 获得流程定义编号对应的 ProcessDefinition
93 94 *
94   - * @param id 编号
  95 + * @param id 流程定义编号
95 96 * @return 流程定义
96 97 */
97 98 ProcessDefinition getProcessDefinition(String id);
... ... @@ -139,7 +140,7 @@ public interface BpmProcessDefinitionService {
139 140 * @return 流程部署 Map
140 141 */
141 142 default Map<String, Deployment> getDeploymentMap(Set<String> ids) {
142   - return convertMap(getDeployments(ids), Deployment::getId);
  143 + return convertMap(getDeploymentList(ids), Deployment::getId);
143 144 }
144 145  
145 146 /**
... ... @@ -148,7 +149,7 @@ public interface BpmProcessDefinitionService {
148 149 * @param ids 部署编号的数组
149 150 * @return 流程部署的数组
150 151 */
151   - List<Deployment> getDeployments(Set<String> ids);
  152 + List<Deployment> getDeploymentList(Set<String> ids);
152 153  
153 154 /**
154 155 * 获得 id 对应的 Deployment
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
... ... @@ -14,7 +14,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConsta
14 14 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
15 15 import jakarta.annotation.Resource;
16 16 import lombok.extern.slf4j.Slf4j;
17   -import org.flowable.bpmn.converter.BpmnXMLConverter;
18 17 import org.flowable.bpmn.model.BpmnModel;
19 18 import org.flowable.common.engine.impl.db.SuspensionState;
20 19 import org.flowable.engine.RepositoryService;
... ... @@ -84,7 +83,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
84 83 }
85 84  
86 85 @Override
87   - public List<Deployment> getDeployments(Set<String> ids) {
  86 + public List<Deployment> getDeploymentList(Set<String> ids) {
88 87 if (CollUtil.isEmpty(ids)) {
89 88 return emptyList();
90 89 }
... ... @@ -156,13 +155,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
156 155 }
157 156  
158 157 @Override
159   - public String getProcessDefinitionBpmnXML(String id) {
160   - BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
161   - if (bpmnModel == null) {
162   - return null;
163   - }
164   - BpmnXMLConverter converter = new BpmnXMLConverter();
165   - return StrUtil.utf8Str(converter.convertToXML(bpmnModel));
  158 + public BpmnModel getProcessDefinitionBpmnModel(String id) {
  159 + return repositoryService.getBpmnModel(id);
166 160 }
167 161  
168 162 @Override
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java
... ... @@ -56,7 +56,8 @@ public class BpmOALeaveServiceImpl implements BpmOALeaveService {
56 56 processInstanceVariables.put("day", day);
57 57 String processInstanceId = processInstanceApi.createProcessInstance(userId,
58 58 new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
59   - .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId())));
  59 + .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId()))
  60 + .setStartUserSelectAssignees(createReqVO.getStartUserSelectAssignees()));
60 61  
61 62 // 将工作流的编号,更新到 OA 请假单中
62 63 leaveMapper.updateById(new BpmOALeaveDO().setId(leave.getId()).setProcessInstanceId(processInstanceId));
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
... ... @@ -50,17 +50,6 @@ public interface BpmProcessInstanceService {
50 50 }
51 51  
52 52 /**
53   - * 获得流程实例名字 Map
54   - *
55   - * @param ids 流程实例的编号集合
56   - * @return 对应的映射关系
57   - */
58   - default Map<String, String> getProcessInstanceNameMap(Set<String> ids) {
59   - return convertMap(getProcessInstances(ids),
60   - ProcessInstance::getProcessInstanceId, ProcessInstance::getName);
61   - }
62   -
63   - /**
64 53 * 获得历史的流程实例
65 54 *
66 55 * @param id 流程实例的编号
... ... @@ -152,14 +141,4 @@ public interface BpmProcessInstanceService {
152 141 */
153 142 void updateProcessInstanceReject(String id, String reason);
154 143  
155   - // TODO @hai:改成 getProcessInstanceAssigneesByTaskDefinitionKey(String id, String taskDefinitionKey)
156   - /**
157   - * 获取流程实例中,取出指定流程任务提前指定的审批人
158   - *
159   - * @param processInstanceId 流程实例的编号
160   - * @param taskDefinitionKey 流程任务定义的 key
161   - * @return 审批人集合
162   - */
163   - List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey);
164   -
165 144 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1   -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.FlowableContextHolder; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .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.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置上下文信息 // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } @Override public List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { // 1. 先从上下文获取,首次提交数据库中查询不到 List<Long> result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } // 2. 从数据库中获取 // TODO @芋艿:指定审批人,这里的存储方案有问题,后续优化下 // BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); // if (instance == null) { // throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); // } // if (CollUtil.isNotEmpty(instance.getAssignee())) { // return instance.getAssignee().get(taskDefinitionKey); // } return Collections.emptyList(); } }
2 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .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. 创建流程实例 FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } }
3 3 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java
... ... @@ -30,8 +30,9 @@ public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest {
30 30 private AdminUserApi adminUserApi;
31 31 @Mock
32 32 private DeptApi deptApi;
  33 +
33 34 @Mock
34   - private BpmProcessInstanceService bpmProcessInstanceService;
  35 + private BpmProcessInstanceService processInstanceService;
35 36  
36 37 @Test
37 38 public void testCalculateUsers_noDept() {
... ... @@ -96,7 +97,7 @@ public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest {
96 97 // mock 返回 startUserId
97 98 ExecutionEntityImpl processInstance = new ExecutionEntityImpl();
98 99 processInstance.setStartUserId(String.valueOf(startUserId));
99   - when(bpmProcessInstanceService.getProcessInstance(eq(execution.getProcessInstanceId())))
  100 + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId())))
100 101 .thenReturn(processInstance);
101 102 return execution;
102 103 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/category/BpmCategoryServiceImplTest.java
1 1 package cn.iocoder.yudao.module.bpm.service.category;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
3 6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
4 7 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO;
5   -import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl;
6   -import org.junit.jupiter.api.Disabled;
7   -import org.junit.jupiter.api.Test;
8   -
9   -import jakarta.annotation.Resource;
10   -
11   -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
12   -
13 8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
14 9 import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
15   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
16   -
  10 +import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl;
  11 +import jakarta.annotation.Resource;
  12 +import org.junit.jupiter.api.Test;
17 13 import org.springframework.context.annotation.Import;
18 14  
19   -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
20   -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
  15 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  16 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
  17 +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
  18 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  19 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
21 20 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
22   -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
23   -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
  21 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS;
24 22 import static org.junit.jupiter.api.Assertions.*;
25 23  
26 24 /**
... ... @@ -40,7 +38,8 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
40 38 @Test
41 39 public void testCreateCategory_success() {
42 40 // 准备参数
43   - BpmCategorySaveReqVO createReqVO = randomPojo(BpmCategorySaveReqVO.class).setId(null);
  41 + BpmCategorySaveReqVO createReqVO = randomPojo(BpmCategorySaveReqVO.class).setId(null)
  42 + .setStatus(randomCommonStatus());
44 43  
45 44 // 调用
46 45 Long categoryId = categoryService.createCategory(createReqVO);
... ... @@ -59,6 +58,7 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
59 58 // 准备参数
60 59 BpmCategorySaveReqVO updateReqVO = randomPojo(BpmCategorySaveReqVO.class, o -> {
61 60 o.setId(dbCategory.getId()); // 设置更新的 ID
  61 + o.setStatus(randomCommonStatus());
62 62 });
63 63  
64 64 // 调用
... ... @@ -101,29 +101,28 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
101 101 }
102 102  
103 103 @Test
104   - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
105 104 public void testGetCategoryPage() {
106 105 // mock 数据
107 106 BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class, o -> { // 等会查询到
108   - o.setName(null);
109   - o.setCode(null);
110   - o.setStatus(null);
111   - o.setCreateTime(null);
  107 + o.setName("芋头");
  108 + o.setCode("xiaodun");
  109 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  110 + o.setCreateTime(buildTime(2023, 2, 2));
112 111 });
113 112 categoryMapper.insert(dbCategory);
114 113 // 测试 name 不匹配
115   - categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName(null)));
  114 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName("小盾")));
116 115 // 测试 code 不匹配
117   - categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCode(null)));
  116 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCode("tudou")));
118 117 // 测试 status 不匹配
119   - categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(null)));
  118 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
120 119 // 测试 createTime 不匹配
121   - categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCreateTime(null)));
  120 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCreateTime(buildTime(2024, 2, 2))));
122 121 // 准备参数
123 122 BpmCategoryPageReqVO reqVO = new BpmCategoryPageReqVO();
124   - reqVO.setName(null);
125   - reqVO.setCode(null);
126   - reqVO.setStatus(null);
  123 + reqVO.setName("芋");
  124 + reqVO.setCode("xiao");
  125 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
127 126 reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
128 127  
129 128 // 调用
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceTest.java
... ... @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
7 7 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO;
8 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
9   -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
10 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
11 10 import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmFormMapper;
12 11 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmFormFieldRespDTO;
... ... @@ -66,7 +65,7 @@ public class BpmFormServiceTest extends BaseDbUnitTest {
66 65 });
67 66 formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据
68 67 // 准备参数
69   - BpmFormUpdateReqVO reqVO = randomPojo(BpmFormUpdateReqVO.class, o -> {
  68 + BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> {
70 69 o.setId(dbForm.getId()); // 设置更新的 ID
71 70 o.setConf("{'yudao': 'yuanma'}");
72 71 o.setFields(randomFields());
... ... @@ -82,7 +81,7 @@ public class BpmFormServiceTest extends BaseDbUnitTest {
82 81 @Test
83 82 public void testUpdateForm_notExists() {
84 83 // 准备参数
85   - BpmFormUpdateReqVO reqVO = randomPojo(BpmFormUpdateReqVO.class, o -> {
  84 + BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> {
86 85 o.setConf("{'yudao': 'yuanma'}");
87 86 o.setFields(randomFields());
88 87 });
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/resources/sql/clean.sql
1 1 DELETE FROM "bpm_form";
2 2 DELETE FROM "bpm_user_group";
  3 +DELETE FROM "bpm_category";
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/test/resources/sql/create_tables.sql
... ... @@ -12,6 +12,21 @@ CREATE TABLE IF NOT EXISTS &quot;bpm_user_group&quot; (
12 12 PRIMARY KEY ("id")
13 13 ) COMMENT '用户组';
14 14  
  15 +CREATE TABLE IF NOT EXISTS "bpm_category" (
  16 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  17 + "name" varchar(63) NOT NULL,
  18 + "code" varchar(63) NOT NULL,
  19 + "description" varchar(255) NOT NULL,
  20 + "status" tinyint NOT NULL,
  21 + "sort" int NOT NULL,
  22 + "creator" varchar(64) DEFAULT '',
  23 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  24 + "updater" varchar(64) DEFAULT '',
  25 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  26 + "deleted" bit NOT NULL DEFAULT FALSE,
  27 + PRIMARY KEY ("id")
  28 +) COMMENT '分类';
  29 +
15 30 CREATE TABLE IF NOT EXISTS "bpm_form" (
16 31 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
17 32 "name" varchar(63) NOT NULL,
... ...