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,9 +51,6 @@ public interface ErrorCodeConstants {
51 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务"); 51 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
52 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人"); 52 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
53 ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); 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 ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); 54 ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
58 55
59 // ========== 动态表单模块 1-009-010-000 ========== 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,6 +14,7 @@ import lombok.Getter;
14 public enum BpmFieldPermissionEnum { 14 public enum BpmFieldPermissionEnum {
15 15
16 // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3) 16 // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3)
  17 + // @芋艿 我看钉钉页面的顺序 是 可编辑 只读 隐藏
17 WRITE(1, "可编辑"), 18 WRITE(1, "可编辑"),
18 READ(2, "只读"), 19 READ(2, "只读"),
19 NONE(3, "隐藏"); 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,12 +13,8 @@ import lombok.Getter;
13 @AllArgsConstructor 13 @AllArgsConstructor
14 public enum BpmUserTaskRejectHandlerType { 14 public enum BpmUserTaskRejectHandlerType {
15 15
16 - // TODO @jason:是不是收敛成 2 个:FINISH_PROCESS => 1. 直接结束流程;RETURN_PRE_USER_TASK => 2. 驳回到指定节点(RETURN_USER_TASK【去掉 PRE】)  
17 FINISH_PROCESS(1, "终止流程"), 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 private final Integer type; 19 private final Integer type;
24 private final String name; 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,11 +149,10 @@ public class BpmModelController {
149 149
150 // ========== 仿钉钉/飞书的精简模型 ========= 150 // ========== 仿钉钉/飞书的精简模型 =========
151 151
152 - // TODO @jason:modelId => id 哈。一般属于自己的模块,可以简化命名。  
153 @GetMapping("/simple/get") 152 @GetMapping("/simple/get")
154 @Operation(summary = "获得仿钉钉流程设计模型") 153 @Operation(summary = "获得仿钉钉流程设计模型")
155 @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") 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 return success(modelService.getSimpleModel(modelId)); 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,10 +43,9 @@ public class BpmSimpleModelNodeVO {
43 @Schema(description = "节点的属性") 43 @Schema(description = "节点的属性")
44 private Map<String, Object> attributes; // TODO @jason:建议是字段分拆下;类似说: 44 private Map<String, Object> attributes; // TODO @jason:建议是字段分拆下;类似说:
45 45
46 - // TODO @jason:看看是不是可以简化; 46 + // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到
47 /** 47 /**
48 * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。 48 * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。
49 - * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。  
50 */ 49 */
51 @JsonIgnore 50 @JsonIgnore
52 private String attachNodeId; 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,10 +11,9 @@ import lombok.Data;
11 @Data 11 @Data
12 public class BpmSimpleModelUpdateReqVO { 12 public class BpmSimpleModelUpdateReqVO {
13 13
14 - // TODO @jason:=> id  
15 @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 14 @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16 @NotEmpty(message = "流程模型编号不能为空") 15 @NotEmpty(message = "流程模型编号不能为空")
17 - private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 16 + private String id; // 对应 Flowable act_re_model 表 ID_ 字段
18 17
19 @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) 18 @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
20 @NotNull(message = "仿钉钉流程设计模型对象不能为空") 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,18 +7,17 @@ import jakarta.annotation.Resource;
7 import org.flowable.bpmn.model.FlowElement; 7 import org.flowable.bpmn.model.FlowElement;
8 import org.flowable.engine.delegate.DelegateExecution; 8 import org.flowable.engine.delegate.DelegateExecution;
9 import org.flowable.engine.delegate.JavaDelegate; 9 import org.flowable.engine.delegate.JavaDelegate;
10 -import org.springframework.stereotype.Service; 10 +import org.springframework.stereotype.Component;
11 11
12 import java.util.Set; 12 import java.util.Set;
13 13
14 -// TODO @jason:类名可以改成 BpmCopyTaskDelegate  
15 /** 14 /**
16 * 处理抄送用户的 {@link JavaDelegate} 的实现类 15 * 处理抄送用户的 {@link JavaDelegate} 的实现类
17 * 16 *
18 * @author jason 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 @Resource 22 @Resource
24 private BpmTaskCandidateInvoker taskCandidateInvoker; 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,16 +57,6 @@ public interface BpmnModelConstants {
57 String USER_TASK_APPROVE_METHOD = "approveMethod"; 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 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 60 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
71 */ 61 */
72 String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; 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,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,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
5 import cn.hutool.core.util.ArrayUtil; 5 import cn.hutool.core.util.ArrayUtil;
6 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
9 import org.flowable.bpmn.converter.BpmnXMLConverter; 10 import org.flowable.bpmn.converter.BpmnXMLConverter;
10 import org.flowable.bpmn.model.Process; 11 import org.flowable.bpmn.model.Process;
@@ -27,8 +28,7 @@ public class BpmnModelUtils { @@ -27,8 +28,7 @@ public class BpmnModelUtils {
27 // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 28 // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
28 if (candidateStrategy == null) { 29 if (candidateStrategy == null) {
29 ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); 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 return candidateStrategy; 33 return candidateStrategy;
34 } 34 }
@@ -38,18 +38,26 @@ public class BpmnModelUtils { @@ -38,18 +38,26 @@ public class BpmnModelUtils {
38 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); 38 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
39 if (candidateParam == null) { 39 if (candidateParam == null) {
40 ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); 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 return candidateParam; 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 public static String parseExtensionElement(FlowElement flowElement, String elementName) { 55 public static String parseExtensionElement(FlowElement flowElement, String elementName) {
48 if (flowElement == null) { 56 if (flowElement == null) {
49 return null; 57 return null;
50 } 58 }
51 ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); 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 // TODO @jason:貌似这个没地方调用??? @芋艿 在 BpmTaskConvert里面。暂时注释掉了。 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,7 +26,6 @@ import java.util.Objects;
26 26
27 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; 27 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT;
28 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; 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 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; 29 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
31 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 30 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
32 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; 31 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
@@ -55,9 +54,9 @@ public class SimpleModelUtils { @@ -55,9 +54,9 @@ public class SimpleModelUtils {
55 public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }"; 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 // TODO-DONE @jason:建议方法名,改成 buildBpmnModel 61 // TODO-DONE @jason:建议方法名,改成 buildBpmnModel
63 // TODO @yunai:注释需要完善下; 62 // TODO @yunai:注释需要完善下;
@@ -185,8 +184,9 @@ public class SimpleModelUtils { @@ -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 * @param attachNodeId 附属节点 Id 190 * @param attachNodeId 附属节点 Id
191 * @param targetNodeId 目标节点 Id 191 * @param targetNodeId 目标节点 Id
192 */ 192 */
@@ -344,28 +344,9 @@ public class SimpleModelUtils { @@ -344,28 +344,9 @@ public class SimpleModelUtils {
344 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); 344 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
345 flowElements.add(boundaryEvent); 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 return flowElements; 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 private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { 350 private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
370 // 定时器边界事件 351 // 定时器边界事件
371 BoundaryEvent boundaryEvent = new BoundaryEvent(); 352 BoundaryEvent boundaryEvent = new BoundaryEvent();
@@ -406,7 +387,7 @@ public class SimpleModelUtils { @@ -406,7 +387,7 @@ public class SimpleModelUtils {
406 serviceTask.setId(node.getId()); 387 serviceTask.setId(node.getId());
407 serviceTask.setName(node.getName()); 388 serviceTask.setName(node.getName());
408 serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); 389 serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
409 - serviceTask.setImplementation("${copyUserDelegate}"); 390 + serviceTask.setImplementation("${bpmCopyTaskDelegate}");
410 391
411 // 添加抄送候选人元素 392 // 添加抄送候选人元素
412 addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), 393 addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY),
@@ -515,11 +496,10 @@ public class SimpleModelUtils { @@ -515,11 +496,10 @@ public class SimpleModelUtils {
515 multiInstanceCharacteristics.setLoopCardinality("1"); 496 multiInstanceCharacteristics.setLoopCardinality("1");
516 userTask.setLoopCharacteristics(multiInstanceCharacteristics); 497 userTask.setLoopCharacteristics(multiInstanceCharacteristics);
517 } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) { 498 } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) {
518 - multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION);  
519 - multiInstanceCharacteristics.setSequential(false);  
520 Assert.notNull(approveRatio, "通过比例不能为空"); 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 userTask.setLoopCharacteristics(multiInstanceCharacteristics); 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,7 +226,7 @@ public class BpmModelServiceImpl implements BpmModelService {
226 @Override 226 @Override
227 public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) { 227 public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) {
228 // 1. 校验流程模型存在 228 // 1. 校验流程模型存在
229 - Model model = getModel(reqVO.getModelId()); 229 + Model model = getModel(reqVO.getId());
230 if (model == null) { 230 if (model == null) {
231 throw exception(MODEL_NOT_EXISTS); 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,7 +13,13 @@ import java.util.Collection;
13 */ 13 */
14 public interface BpmProcessInstanceCopyService { 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 package cn.iocoder.yudao.module.bpm.service.task; 1 package cn.iocoder.yudao.module.bpm.service.task;
2 2
  3 +import cn.hutool.core.util.ObjectUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
5 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; 6 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
@@ -10,6 +11,7 @@ import jakarta.annotation.Resource; @@ -10,6 +11,7 @@ import jakarta.annotation.Resource;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 import org.flowable.engine.repository.ProcessDefinition; 12 import org.flowable.engine.repository.ProcessDefinition;
12 import org.flowable.engine.runtime.ProcessInstance; 13 import org.flowable.engine.runtime.ProcessInstance;
  14 +import org.flowable.task.api.Task;
13 import org.springframework.context.annotation.Lazy; 15 import org.springframework.context.annotation.Lazy;
14 import org.springframework.stereotype.Service; 16 import org.springframework.stereotype.Service;
15 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
@@ -44,21 +46,25 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy @@ -44,21 +46,25 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
44 @Lazy // 延迟加载,避免循环依赖 46 @Lazy // 延迟加载,避免循环依赖
45 private BpmProcessDefinitionService processDefinitionService; 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 // TODO @芋艿:这里多加了一个 name; 59 // TODO @芋艿:这里多加了一个 name;
48 @Override 60 @Override
49 public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) { 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 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); 63 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
58 if (processInstance == null) { 64 if (processInstance == null) {
59 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); 65 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
60 } 66 }
61 - // 1.3 校验流程定义存在 67 + // 1.2 校验流程定义存在
62 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( 68 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
63 processInstance.getProcessDefinitionId()); 69 processInstance.getProcessDefinitionId());
64 if (processDefinition == null) { 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 \ No newline at end of file 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 \ No newline at end of file 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,8 +54,6 @@ import java.util.stream.Stream;
54 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 54 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
55 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 55 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
56 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 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 * 流程任务实例 Service 实现类 59 * 流程任务实例 Service 实现类
@@ -189,8 +187,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -189,8 +187,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
189 187
190 // 2. 抄送用户 188 // 2. 抄送用户
191 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { 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 // 情况一:被委派的任务,不调用 complete 去完成任务 193 // 情况一:被委派的任务,不调用 complete 去完成任务
@@ -338,35 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -338,35 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService {
338 335
339 // 3.1 解析用户任务的拒绝处理类型 336 // 3.1 解析用户任务的拒绝处理类型
340 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); 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 BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId) 344 BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId)
354 .setReason(reqVO.getReason()); 345 .setReason(reqVO.getReason());
355 returnTask(userId, returnReq); 346 returnTask(userId, returnReq);
356 return; 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 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), 350 processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(),
371 task.getTaskDefinitionKey(), reqVO.getReason()); 351 task.getTaskDefinitionKey(), reqVO.getReason());
372 } 352 }