Commit 633a7c50ae151c1a5047fa73758f5cd5e02d6726

Authored by jason
1 parent 41b9ab2b

仿钉钉流程设计- 简化审批拒绝流程, code review 修改

Showing 18 changed files with 61 additions and 259 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
... ... @@ -51,9 +51,6 @@ public interface ErrorCodeConstants {
51 51 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
52 52 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
53 53 ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
54   - ErrorCode TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID = new ErrorCode(1_009_005_015, "回退任务未指定目标任务编号");
55   - ErrorCode TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR = new ErrorCode(1_009_005_016, "按拒绝人数比例终止流程只能用于会签任务");
56   -
57 54 ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
58 55  
59 56 // ========== 动态表单模块 1-009-010-000 ==========
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java
... ... @@ -14,6 +14,7 @@ import lombok.Getter;
14 14 public enum BpmFieldPermissionEnum {
15 15  
16 16 // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3)
  17 + // @芋艿 我看钉钉页面的顺序 是 可编辑 只读 隐藏
17 18 WRITE(1, "可编辑"),
18 19 READ(2, "只读"),
19 20 NONE(3, "隐藏");
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
... ... @@ -13,12 +13,8 @@ import lombok.Getter;
13 13 @AllArgsConstructor
14 14 public enum BpmUserTaskRejectHandlerType {
15 15  
16   - // TODO @jason:是不是收敛成 2 个:FINISH_PROCESS => 1. 直接结束流程;RETURN_PRE_USER_TASK => 2. 驳回到指定节点(RETURN_USER_TASK【去掉 PRE】)
17 16 FINISH_PROCESS(1, "终止流程"),
18   - RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"),
19   -
20   - FINISH_PROCESS_BY_REJECT_NUMBER(3, "按拒绝人数终止流程"), // 用于会签
21   - FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支
  17 + RETURN_USER_TASK(2, "驳回到指定任务节点");
22 18  
23 19 private final Integer type;
24 20 private final String name;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
... ... @@ -149,11 +149,10 @@ public class BpmModelController {
149 149  
150 150 // ========== 仿钉钉/飞书的精简模型 =========
151 151  
152   - // TODO @jason:modelId => id 哈。一般属于自己的模块,可以简化命名。
153 152 @GetMapping("/simple/get")
154 153 @Operation(summary = "获得仿钉钉流程设计模型")
155 154 @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
156   - public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("modelId") String modelId){
  155 + public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){
157 156 return success(modelService.getSimpleModel(modelId));
158 157 }
159 158  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
... ... @@ -43,10 +43,9 @@ public class BpmSimpleModelNodeVO {
43 43 @Schema(description = "节点的属性")
44 44 private Map<String, Object> attributes; // TODO @jason:建议是字段分拆下;类似说:
45 45  
46   - // TODO @jason:看看是不是可以简化;
  46 + // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到
47 47 /**
48 48 * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。
49   - * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。
50 49 */
51 50 @JsonIgnore
52 51 private String attachNodeId;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
... ... @@ -11,10 +11,9 @@ import lombok.Data;
11 11 @Data
12 12 public class BpmSimpleModelUpdateReqVO {
13 13  
14   - // TODO @jason:=> id
15 14 @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16 15 @NotEmpty(message = "流程模型编号不能为空")
17   - private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段
  16 + private String id; // 对应 Flowable act_re_model 表 ID_ 字段
18 17  
19 18 @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
20 19 @NotNull(message = "仿钉钉流程设计模型对象不能为空")
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java
... ... @@ -7,18 +7,17 @@ import jakarta.annotation.Resource;
7 7 import org.flowable.bpmn.model.FlowElement;
8 8 import org.flowable.engine.delegate.DelegateExecution;
9 9 import org.flowable.engine.delegate.JavaDelegate;
10   -import org.springframework.stereotype.Service;
  10 +import org.springframework.stereotype.Component;
11 11  
12 12 import java.util.Set;
13 13  
14   -// TODO @jason:类名可以改成 BpmCopyTaskDelegate
15 14 /**
16 15 * 处理抄送用户的 {@link JavaDelegate} 的实现类
17 16 *
18 17 * @author jason
19 18 */
20   -@Service // TODO @jason:这种注解,建议用 @Component
21   -public class CopyUserDelegate implements JavaDelegate {
  19 +@Component
  20 +public class BpmCopyTaskDelegate implements JavaDelegate {
22 21  
23 22 @Resource
24 23 private BpmTaskCandidateInvoker taskCandidateInvoker;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java deleted
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import cn.hutool.core.util.BooleanUtil;
5   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
6   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
7   -import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
8   -import jakarta.annotation.Resource;
9   -import org.flowable.engine.delegate.DelegateExecution;
10   -import org.flowable.engine.delegate.JavaDelegate;
11   -import org.springframework.stereotype.Component;
12   -
13   -// TODO @jason:微信已经讨论,简化哈
14   -/**
15   - * 处理会签 Service Task 代理
16   - *
17   - * @author jason
18   - */
19   -@Component
20   -public class MultiInstanceServiceTaskDelegate implements JavaDelegate {
21   -
22   - @Resource
23   - private BpmProcessInstanceService processInstanceService;
24   -
25   - @Override
26   - public void execute(DelegateExecution execution) {
27   -
28   - String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(),
29   - BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); // TODO @jason:上面不需要加空行哈;
30   - Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空");
31   - // 获取会签任务是否被拒绝
32   - Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class);
33   - // 如果会签任务被拒绝, 终止流程, 跳转到 EndEvent 节点
34   - if (BooleanUtil.isTrue(userTaskRejected)) {
35   - processInstanceService.updateProcessInstanceReject(execution.getProcessInstanceId(),
36   - execution.getCurrentActivityId(), "会签任务未达到通过比例" );
37   - }
38   - }
39   -
40   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java deleted
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.expression;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
5   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6   -import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7   -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
8   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
9   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
10   -import lombok.extern.slf4j.Slf4j;
11   -import org.flowable.bpmn.model.FlowElement;
12   -import org.flowable.engine.delegate.DelegateExecution;
13   -import org.springframework.stereotype.Component;
14   -
15   -import java.util.Objects;
16   -
17   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
18   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.APPROVE_BY_RATIO;
19   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD;
20   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO;
21   -
22   -// TODO @jason:微信已经讨论,简化哈
23   -/**
24   - * 按拒绝人数计算会签的完成条件的流程表达式实现
25   - *
26   - * @author jason
27   - */
28   -@Component
29   -@Slf4j
30   -public class CompleteByRejectCountExpression {
31   -
32   - /**
33   - * 会签的完成条件
34   - */
35   - public boolean completionCondition(DelegateExecution execution) {
36   - FlowElement flowElement = execution.getCurrentFlowElement();
37   - // 实例总数
38   - Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
39   - // 完成的实例数
40   - Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
41   - // 审批方式
42   - Integer approveMethod = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_METHOD));
43   - Assert.notNull(approveMethod, "审批方式不能空");
44   - if (!Objects.equals(APPROVE_BY_RATIO.getMethod(), approveMethod)) {
45   - log.error("[completionCondition] the execution is [{}] 审批方式[{}] 不匹配", execution, approveMethod);
46   - throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION);
47   - }
48   - // 获取拒绝人数
49   - // TODO @jason:CollUtil.filter().size();貌似可以更简洁 @芋艿 CollUtil.filter().size() 使用这个会报错,好坑了.
50   - Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(),
51   - item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0,
52   - Integer::sum, 0);
53   - // 同意人数: 完成人数 - 拒绝人数
54   - int agreeCount = nrOfCompletedInstances - rejectCount;
55   - // 多人会签(按通过比例)
56   - Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO));
57   - Assert.notNull(approveRatio, "通过比例不能空");
58   - // 判断通过比例
59   - double approvePct = approveRatio / (double) 100;
60   - double realApprovePct = (double) agreeCount / nrOfInstances;
61   - if (realApprovePct >= approvePct) {
62   - return true;
63   - }
64   - double rejectPct = (100 - approveRatio) / (double) 100;
65   - double realRejectPct = (double) rejectCount / nrOfInstances;
66   - // 判断拒绝比例
67   - if (realRejectPct > rejectPct) {
68   - execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE);
69   - return true;
70   - }
71   - return false;
72   - }
73   -
74   -}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
... ... @@ -57,16 +57,6 @@ public interface BpmnModelConstants {
57 57 String USER_TASK_APPROVE_METHOD = "approveMethod";
58 58  
59 59 /**
60   - * BPMN UserTask 的扩展属性,当审批方式为按通过比例时, 标记会签通过比例
61   - */
62   - String USER_TASK_APPROVE_RATIO = "approveRatio";
63   -
64   - /**
65   - * BPMN ExtensionElement 的扩展属性,用于标记 服务任务附属的用户任务 Id
66   - */
67   - String SERVICE_TASK_ATTACH_USER_TASK_ID = "attachUserTaskId";
68   -
69   - /**
70 60 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
71 61 */
72 62 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission";
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
... ... @@ -73,48 +73,4 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
73 73 });
74 74 }
75 75  
76   - // TODO @jason:这块如果不需要,可以删除掉~~~
77   -// @Override
78   -// protected void activityMessageReceived(FlowableMessageEvent event) {
79   -// BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId());
80   -// FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId());
81   -// if (element instanceof BoundaryEvent) {
82   -// BoundaryEvent boundaryEvent = (BoundaryEvent) element;
83   -// String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
84   -// // 如果自定义类型为拒绝后处理,进行拒绝处理
85   -// if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) {
86   -// String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE);
87   -// rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType));
88   -// }
89   -// }
90   -// }
91   -//
92   -// private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) {
93   -// BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
94   -// if (userTaskRejectHandlerType != null) {
95   -// List<Task> taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey);
96   -// taskList.forEach(task -> {
97   -// Integer taskStatus = FlowableUtils.getTaskStatus(task);
98   -// // 只有处于拒绝状态下才处理
99   -// if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) {
100   -// // 终止流程
101   -// if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) {
102   -// processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task));
103   -// return;
104   -// }
105   -// // 驳回
106   -// if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
107   -// String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID);
108   -// if (returnTaskId != null) {
109   -// BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId())
110   -// .setTargetTaskDefinitionKey(returnTaskId)
111   -// .setReason("任务拒绝回退");
112   -// taskService.returnTask(getLoginUserId(), reqVO);
113   -// }
114   -// }
115   -// }
116   -// });
117   -// }
118   -// }
119   -
120 76 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
6 6 import cn.hutool.core.util.StrUtil;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
8 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
9 10 import org.flowable.bpmn.converter.BpmnXMLConverter;
10 11 import org.flowable.bpmn.model.Process;
... ... @@ -27,8 +28,7 @@ public class BpmnModelUtils {
27 28 // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
28 29 if (candidateStrategy == null) {
29 30 ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
30   - // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? NumberUtils.parseInt(element.getElementText()) : null;
31   - candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null));
  31 + candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null;
32 32 }
33 33 return candidateStrategy;
34 34 }
... ... @@ -38,18 +38,26 @@ public class BpmnModelUtils {
38 38 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
39 39 if (candidateParam == null) {
40 40 ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
41   - // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? element.getElementText() : null;
42   - candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
  41 + candidateParam = element != null ? element.getElementText() : null;
43 42 }
44 43 return candidateParam;
45 44 }
46 45  
  46 + public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
  47 + Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
  48 + return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
  49 + }
  50 +
  51 + public static String parseReturnTaskId(FlowElement flowElement) {
  52 + return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
  53 + }
  54 +
47 55 public static String parseExtensionElement(FlowElement flowElement, String elementName) {
48 56 if (flowElement == null) {
49 57 return null;
50 58 }
51 59 ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
52   - return Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
  60 + return element != null ? element.getElementText() : null;
53 61 }
54 62  
55 63 // TODO @jason:貌似这个没地方调用??? @芋艿 在 BpmTaskConvert里面。暂时注释掉了。
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
... ... @@ -26,7 +26,6 @@ import java.util.Objects;
26 26  
27 27 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT;
28 28 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
29   -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER;
30 29 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
31 30 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
32 31 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
... ... @@ -55,9 +54,9 @@ public class SimpleModelUtils {
55 54 public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
56 55  
57 56 /**
58   - * 按拒绝人数计算多实例完成条件的表达式
  57 + * 按通过比例完成表达式
59 58 */
60   - public static final String COMPLETE_BY_REJECT_COUNT_EXPRESSION = "${completeByRejectCountExpression.completionCondition(execution)}";
  59 + public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
61 60  
62 61 // TODO-DONE @jason:建议方法名,改成 buildBpmnModel
63 62 // TODO @yunai:注释需要完善下;
... ... @@ -185,8 +184,9 @@ public class SimpleModelUtils {
185 184 }
186 185  
187 186 /**
188   - * 构建有附加节点的连线
189   - * @param nodeId 当前节点 Id
  187 + * 构建有附加节点的连线
  188 + *
  189 + * @param nodeId 当前节点 Id
190 190 * @param attachNodeId 附属节点 Id
191 191 * @param targetNodeId 目标节点 Id
192 192 */
... ... @@ -344,28 +344,9 @@ public class SimpleModelUtils {
344 344 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
345 345 flowElements.add(boundaryEvent);
346 346 }
347   - // 如果按拒绝人数终止流程。需要添加附加的 ServiceTask 处理
348   - if (userTaskConfig.getRejectHandler() != null &&
349   - Objects.equals(FINISH_PROCESS_BY_REJECT_NUMBER.getType(), userTaskConfig.getRejectHandler().getType())) {
350   - ServiceTask serviceTask = buildMultiInstanceServiceTask(node);
351   - flowElements.add(serviceTask);
352   - }
353 347 return flowElements;
354 348 }
355 349  
356   - private static ServiceTask buildMultiInstanceServiceTask(BpmSimpleModelNodeVO node) {
357   - ServiceTask serviceTask = new ServiceTask();
358   - String id = String.format("Activity-%s", IdUtil.fastSimpleUUID());
359   - serviceTask.setId(id);
360   - serviceTask.setName("会签服务任务");
361   - serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
362   - serviceTask.setImplementation("${multiInstanceServiceTaskDelegate}");
363   - serviceTask.setAsynchronous(false);
364   - addExtensionElement(serviceTask, SERVICE_TASK_ATTACH_USER_TASK_ID, node.getId());
365   - node.setAttachNodeId(id);
366   - return serviceTask;
367   - }
368   -
369 350 private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
370 351 // 定时器边界事件
371 352 BoundaryEvent boundaryEvent = new BoundaryEvent();
... ... @@ -406,7 +387,7 @@ public class SimpleModelUtils {
406 387 serviceTask.setId(node.getId());
407 388 serviceTask.setName(node.getName());
408 389 serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
409   - serviceTask.setImplementation("${copyUserDelegate}");
  390 + serviceTask.setImplementation("${bpmCopyTaskDelegate}");
410 391  
411 392 // 添加抄送候选人元素
412 393 addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY),
... ... @@ -515,11 +496,10 @@ public class SimpleModelUtils {
515 496 multiInstanceCharacteristics.setLoopCardinality("1");
516 497 userTask.setLoopCharacteristics(multiInstanceCharacteristics);
517 498 } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) {
518   - multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION);
519   - multiInstanceCharacteristics.setSequential(false);
520 499 Assert.notNull(approveRatio, "通过比例不能为空");
521   - // 添加通过比例的扩展属性
522   - addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_RATIO, approveRatio.toString());
  500 + double approvePct = approveRatio / (double) 100;
  501 + multiInstanceCharacteristics.setCompletionCondition(String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approvePct)));
  502 + multiInstanceCharacteristics.setSequential(false);
523 503 }
524 504 userTask.setLoopCharacteristics(multiInstanceCharacteristics);
525 505 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -226,7 +226,7 @@ public class BpmModelServiceImpl implements BpmModelService {
226 226 @Override
227 227 public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) {
228 228 // 1. 校验流程模型存在
229   - Model model = getModel(reqVO.getModelId());
  229 + Model model = getModel(reqVO.getId());
230 230 if (model == null) {
231 231 throw exception(MODEL_NOT_EXISTS);
232 232 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
... ... @@ -13,7 +13,13 @@ import java.util.Collection;
13 13 */
14 14 public interface BpmProcessInstanceCopyService {
15 15  
16   - // TODO @jason:要不把 createProcessInstanceCopy 搞 2 个方法,一个方法参数是之前的 userIds、taskId;一个方法是现在 userIds、processInstanceId、taskId、taskName;
  16 + /**
  17 + * 流程实例的抄送
  18 + *
  19 + * @param userIds 抄送的用户编号
  20 + * @param taskId 流程任务编号
  21 + */
  22 + void createProcessInstanceCopy(Collection<Long> userIds, String taskId);
17 23  
18 24 /**
19 25 * 流程实例的抄送
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
1 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
5 6 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
... ... @@ -10,6 +11,7 @@ import jakarta.annotation.Resource;
10 11 import lombok.extern.slf4j.Slf4j;
11 12 import org.flowable.engine.repository.ProcessDefinition;
12 13 import org.flowable.engine.runtime.ProcessInstance;
  14 +import org.flowable.task.api.Task;
13 15 import org.springframework.context.annotation.Lazy;
14 16 import org.springframework.stereotype.Service;
15 17 import org.springframework.validation.annotation.Validated;
... ... @@ -44,21 +46,25 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
44 46 @Lazy // 延迟加载,避免循环依赖
45 47 private BpmProcessDefinitionService processDefinitionService;
46 48  
  49 + @Override
  50 + public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) {
  51 + Task task = taskService.getTask(taskId);
  52 + if (ObjectUtil.isNull(task)) {
  53 + throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
  54 + }
  55 + String processInstanceId = task.getProcessInstanceId();
  56 + createProcessInstanceCopy(userIds, processInstanceId, task.getId(), task.getName());
  57 + }
  58 +
47 59 // TODO @芋艿:这里多加了一个 name;
48 60 @Override
49 61 public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) {
50   - // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(UserTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行
51   -// Task task = taskService.getTask(taskId);
52   -// if (ObjectUtil.isNull(task)) {
53   -// throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
54   -// }
55   - // 1.2 校验流程实例存在
56   -// String processInstanceId = task.getProcessInstanceId();
  62 + // 1.1 校验流程实例存在
57 63 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
58 64 if (processInstance == null) {
59 65 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
60 66 }
61   - // 1.3 校验流程定义存在
  67 + // 1.2 校验流程定义存在
62 68 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
63 69 processInstance.getProcessDefinitionId());
64 70 if (processDefinition == null) {
... ...
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.lang.Assert; 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.BpmnModelUtils; 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.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.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); // 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; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 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 currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } }
2 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; 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.BpmnModelUtils; 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.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.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); // 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; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 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 currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 // @芋艿,如果是未完成的会签任务。会被取消, 流程会结束。 但是貌似并行任务的时候。流程是不会结束的。任务还是在进行中的 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } 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/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -54,8 +54,6 @@ import java.util.stream.Stream;
54 54 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
55 55 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
56 56 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
57   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE;
58   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID;
59 57  
60 58 /**
61 59 * 流程任务实例 Service 实现类
... ... @@ -189,8 +187,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
189 187  
190 188 // 2. 抄送用户
191 189 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
192   - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(),
193   - reqVO.getId(), task.getName());
  190 + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId());
194 191 }
195 192  
196 193 // 情况一:被委派的任务,不调用 complete 去完成任务
... ... @@ -338,35 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService {
338 335  
339 336 // 3.1 解析用户任务的拒绝处理类型
340 337 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
341   - // TODO @jason:342 到 344 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy
342   - UserTask flowElement = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
343   - Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE));
344   - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
345   - // 3.2 类型为驳回到指定的任务节点 TODO @jason:下面这种判断,最好是 JSON
346   - if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
347   - // TODO @jason:348 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy
348   - String returnTaskId = BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
349   - // TODO @jason:这里如果找不到,直接抛出系统异常;因为说白了,已经不是业务异常啦。
350   - if (returnTaskId == null) {
351   - throw exception(TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID);
352   - }
  338 + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  339 + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement);
  340 + // 3.2 类型为驳回到指定的任务节点
  341 + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
  342 + String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement);
  343 + Assert.notNull(returnTaskId, "回退的节点不能为空");
353 344 BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId)
354 345 .setReason(reqVO.getReason());
355 346 returnTask(userId, returnReq);
356 347 return;
357   - } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) {
358   - // TODO @jason:微信沟通,去掉类似的逻辑;
359   - // 3.3 按拒绝人数终止流程
360   - if (!flowElement.hasMultiInstanceLoopCharacteristics()) {
361   - log.error("[rejectTask] 按拒绝人数终止流程类型,只能用于会签任务. 当前任务【{}】不是会签任务", task.getId());
362   - throw new IllegalStateException("按拒绝人数终止流程类型,只能用于会签任务");
363   - }
364   - // 设置变量值为拒绝
365   - runtimeService.setVariableLocal(task.getExecutionId(), BpmConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus());
366   - taskService.complete(task.getId());
367   - return;
368 348 }
369   - // 3.4 其他情况 终止流程。
  349 + // 3.3 其他情况 终止流程。
370 350 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(),
371 351 task.getTaskDefinitionKey(), reqVO.getReason());
372 352 }
... ...