Commit bc46c67a9955bc6e4cef6ca2931286c9b2dfa966

Authored by jason
2 parents 620a0d8c 40db4cf7

Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm

Showing 22 changed files with 346 additions and 258 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
@@ -13,8 +13,7 @@ import lombok.Getter; @@ -13,8 +13,7 @@ import lombok.Getter;
13 @AllArgsConstructor 13 @AllArgsConstructor
14 public enum BpmBoundaryEventType { 14 public enum BpmBoundaryEventType {
15 15
16 - USER_TASK_TIMEOUT(1,"用户任务超时"),  
17 - USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理"); 16 + USER_TASK_TIMEOUT(1,"用户任务超时");
18 17
19 private final Integer type; 18 private final Integer type;
20 private final String name; 19 private final String name;
@@ -22,4 +21,5 @@ public enum BpmBoundaryEventType { @@ -22,4 +21,5 @@ public enum BpmBoundaryEventType {
22 public static BpmBoundaryEventType typeOf(Integer type) { 21 public static BpmBoundaryEventType typeOf(Integer type) {
23 return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); 22 return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
24 } 23 }
  24 +
25 } 25 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.enums.definition;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +/**
  8 + * BPM 用户任务的审批人与发起人相同时,处理类型枚举
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@RequiredArgsConstructor
  13 +@Getter
  14 +public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable {
  15 +
  16 + START_USER_AUDIT(1), // 由发起人对自己审批
  17 + SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过
  18 + ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过
  19 +
  20 + private final Integer type;
  21 +
  22 + @Override
  23 + public int[] array() {
  24 + return new int[0];
  25 + }
  26 +
  27 +}
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java
@@ -20,6 +20,7 @@ public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable { @@ -20,6 +20,7 @@ public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable {
20 APPROVE(2, "自动同意"), 20 APPROVE(2, "自动同意"),
21 REJECT(3, "自动拒绝"); 21 REJECT(3, "自动拒绝");
22 22
  23 + // TODO @jason:type 是不是更合适哈;
23 private final Integer action; 24 private final Integer action;
24 private final String name; 25 private final String name;
25 26
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
@@ -14,7 +14,8 @@ public enum BpmMessageEnum { @@ -14,7 +14,8 @@ public enum BpmMessageEnum {
14 14
15 PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 15 PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
16 PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 16 PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
17 - TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 17 + TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人
  18 + TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人
18 19
19 /** 20 /**
20 * 短信模板的标识 21 * 短信模板的标识
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; 1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum;  
5 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;  
6 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;  
7 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; 4 +import cn.iocoder.yudao.module.bpm.enums.definition.*;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
9 import com.fasterxml.jackson.annotation.JsonIgnore; 6 import com.fasterxml.jackson.annotation.JsonIgnore;
10 import com.fasterxml.jackson.annotation.JsonInclude; 7 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -84,6 +81,10 @@ public class BpmSimpleModelNodeVO { @@ -84,6 +81,10 @@ public class BpmSimpleModelNodeVO {
84 */ 81 */
85 private TimeoutHandler timeoutHandler; 82 private TimeoutHandler timeoutHandler;
86 83
  84 + @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
  85 + @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
  86 + private Integer assignStartUserHandlerType;
  87 +
87 @Data 88 @Data
88 @Schema(description = "审批节点拒绝处理策略") 89 @Schema(description = "审批节点拒绝处理策略")
89 public static class RejectHandler { 90 public static class RejectHandler {
@@ -96,6 +97,7 @@ public class BpmSimpleModelNodeVO { @@ -96,6 +97,7 @@ public class BpmSimpleModelNodeVO {
96 private String returnNodeId; 97 private String returnNodeId;
97 } 98 }
98 99
  100 + // TODO @芋艿:参数校验
99 @Data 101 @Data
100 @Schema(description = "审批节点超时处理策略") 102 @Schema(description = "审批节点超时处理策略")
101 public static class TimeoutHandler { 103 public static class TimeoutHandler {
@@ -103,6 +105,7 @@ public class BpmSimpleModelNodeVO { @@ -103,6 +105,7 @@ public class BpmSimpleModelNodeVO {
103 @Schema(description = "是否开启超时处理", example = "false") 105 @Schema(description = "是否开启超时处理", example = "false")
104 private Boolean enable; 106 private Boolean enable;
105 107
  108 + // TODO @jason:type 是不是更合适哈;
106 @Schema(description = "任务超时未处理的行为", example = "1") 109 @Schema(description = "任务超时未处理的行为", example = "1")
107 @InEnum(BpmUserTaskTimeoutHandlerType.class) 110 @InEnum(BpmUserTaskTimeoutHandlerType.class)
108 private Integer action; 111 private Integer action;
@@ -112,6 +115,7 @@ public class BpmSimpleModelNodeVO { @@ -112,6 +115,7 @@ public class BpmSimpleModelNodeVO {
112 115
113 @Schema(description = "最大提醒次数", example = "1") 116 @Schema(description = "最大提醒次数", example = "1")
114 private Integer maxRemindCount; 117 private Integer maxRemindCount;
  118 +
115 } 119 }
116 120
117 @Data 121 @Data
@@ -129,14 +133,7 @@ public class BpmSimpleModelNodeVO { @@ -129,14 +133,7 @@ public class BpmSimpleModelNodeVO {
129 } 133 }
130 134
131 // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用 135 // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用
132 - // Integer approveMethod; 审批方式;仅审批节点会使用  
133 - // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级  
134 - // TODO @芋艿:① 审批人的选择;  
135 // TODO @芋艿:⑥ 没有人的策略? 136 // TODO @芋艿:⑥ 没有人的策略?
136 - // TODO @芋艿:② 审批拒绝的策略?  
137 - // TODO @芋艿:③ 配置的可操作列表?(操作权限)  
138 - // TODO @芋艿:④ 表单的权限列表?  
139 - // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔;  
140 // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 137 // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持
141 138
142 } 139 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
@@ -176,7 +176,6 @@ public interface BpmTaskConvert { @@ -176,7 +176,6 @@ public interface BpmTaskConvert {
176 childTask.setParentTaskId(parentTask.getId()); 176 childTask.setParentTaskId(parentTask.getId());
177 childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); 177 childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
178 childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); 178 childTask.setProcessInstanceId(parentTask.getProcessInstanceId());
179 -// childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错)  
180 childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); 179 childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
181 childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); 180 childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId());
182 childTask.setPriority(parentTask.getPriority()); 181 childTask.setPriority(parentTask.getPriority());
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; @@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.extra.spring.SpringUtil;
6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 8 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; 9 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
  10 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 11 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 12 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  13 +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 15 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 import com.google.common.annotations.VisibleForTesting; 16 import com.google.common.annotations.VisibleForTesting;
@@ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; @@ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
14 import org.flowable.bpmn.model.BpmnModel; 18 import org.flowable.bpmn.model.BpmnModel;
15 import org.flowable.bpmn.model.UserTask; 19 import org.flowable.bpmn.model.UserTask;
16 import org.flowable.engine.delegate.DelegateExecution; 20 import org.flowable.engine.delegate.DelegateExecution;
  21 +import org.flowable.engine.runtime.ProcessInstance;
17 22
18 import java.util.HashMap; 23 import java.util.HashMap;
19 import java.util.List; 24 import java.util.List;
@@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker { @@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker {
86 Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); 91 Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
87 // 1.2 移除被禁用的用户 92 // 1.2 移除被禁用的用户
88 removeDisableUsers(userIds); 93 removeDisableUsers(userIds);
  94 + // 1.3 移除发起人的用户
  95 + removeStartUserIfSkip(execution, userIds);
89 96
90 // 2. 校验是否有候选人 97 // 2. 校验是否有候选人
91 if (CollUtil.isEmpty(userIds)) { 98 if (CollUtil.isEmpty(userIds)) {
@@ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker { @@ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker {
108 }); 115 });
109 } 116 }
110 117
  118 + /**
  119 + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人
  120 + *
  121 + * 注意:如果只有一个候选人,则不处理,避免无法审批
  122 + *
  123 + * @param execution 执行中的任务
  124 + * @param assigneeUserIds 当前分配的候选人
  125 + */
  126 + @VisibleForTesting
  127 + void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) {
  128 + if (CollUtil.size(assigneeUserIds) <= 1) {
  129 + return;
  130 + }
  131 + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement());
  132 + if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
  133 + return;
  134 + }
  135 + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
  136 + .getProcessInstance(execution.getProcessInstanceId());
  137 + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
  138 + assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId()));
  139 + }
  140 +
111 private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { 141 private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
112 BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); 142 BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
113 Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); 143 Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
@@ -29,12 +29,17 @@ public interface BpmnModelConstants { @@ -29,12 +29,17 @@ public interface BpmnModelConstants {
29 String BOUNDARY_EVENT_TYPE = "boundaryEventType"; 29 String BOUNDARY_EVENT_TYPE = "boundaryEventType";
30 30
31 // TODO @jason:这个命名,应该也要改哈 31 // TODO @jason:这个命名,应该也要改哈
  32 + // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;
32 /** 33 /**
33 * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 34 * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
34 */ 35 */
35 String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; 36 String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction";
36 37
37 - // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;2)rejectHandlerType 改成 rejectHandler 哇? 38 + /**
  39 + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型
  40 + */
  41 + String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType";
  42 +
38 /** 43 /**
39 * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 44 * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型
40 */ 45 */
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  7 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
  8 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
  9 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  10 +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
5 import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; 11 import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
6 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; 12 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
7 import com.google.common.collect.ImmutableSet; 13 import com.google.common.collect.ImmutableSet;
8 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
9 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
  16 +import org.flowable.bpmn.model.BoundaryEvent;
  17 +import org.flowable.bpmn.model.BpmnModel;
  18 +import org.flowable.bpmn.model.FlowElement;
10 import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; 19 import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
11 import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; 20 import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
12 import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; 21 import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
13 import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; 22 import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent;
14 import org.flowable.engine.history.HistoricActivityInstance; 23 import org.flowable.engine.history.HistoricActivityInstance;
  24 +import org.flowable.job.api.Job;
15 import org.flowable.task.api.Task; 25 import org.flowable.task.api.Task;
16 import org.springframework.context.annotation.Lazy; 26 import org.springframework.context.annotation.Lazy;
17 import org.springframework.stereotype.Component; 27 import org.springframework.stereotype.Component;
@@ -29,6 +39,9 @@ import java.util.Set; @@ -29,6 +39,9 @@ import java.util.Set;
29 public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { 39 public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
30 40
31 @Resource 41 @Resource
  42 + @Lazy // 延迟加载,避免循环依赖
  43 + private BpmModelService modelService;
  44 + @Resource
32 @Lazy // 解决循环依赖 45 @Lazy // 解决循环依赖
33 private BpmTaskService taskService; 46 private BpmTaskService taskService;
34 @Resource 47 @Resource
@@ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
40 .add(FlowableEngineEventType.TASK_ASSIGNED) 53 .add(FlowableEngineEventType.TASK_ASSIGNED)
41 // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 54 // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
42 .add(FlowableEngineEventType.ACTIVITY_CANCELLED) 55 .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
  56 + .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
43 .build(); 57 .build();
44 58
45 public BpmTaskEventListener() { 59 public BpmTaskEventListener() {
@@ -72,4 +86,30 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -72,4 +86,30 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
72 }); 86 });
73 } 87 }
74 88
  89 + @Override
  90 + protected void timerFired(FlowableEngineEntityEvent event) {
  91 + // 1.1 只处理 BoundaryEvent 边界计时时间
  92 + String processDefinitionId = event.getProcessDefinitionId();
  93 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
  94 + Job entity = (Job) event.getEntity();
  95 + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
  96 + if (!(element instanceof BoundaryEvent)) {
  97 + return;
  98 + }
  99 + // 1.2 判断是否为超时处理
  100 + BoundaryEvent boundaryEvent = (BoundaryEvent) element;
  101 + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
  102 + BpmnModelConstants.BOUNDARY_EVENT_TYPE);
  103 + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
  104 + if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) {
  105 + return;
  106 + }
  107 +
  108 + // 2. 处理超时
  109 + String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
  110 + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION);
  111 + String taskKey = boundaryEvent.getAttachedToRefId();
  112 + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutAction));
  113 + }
  114 +
75 } 115 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java deleted
1 -package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;  
2 -  
3 -import cn.iocoder.yudao.framework.common.util.number.NumberUtils;  
4 -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;  
5 -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;  
6 -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO;  
7 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;  
8 -import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType;  
9 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;  
10 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;  
11 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer;  
12 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;  
13 -import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;  
14 -import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;  
15 -import com.google.common.collect.ImmutableSet;  
16 -import jakarta.annotation.Resource;  
17 -import lombok.extern.slf4j.Slf4j;  
18 -import org.flowable.bpmn.model.BoundaryEvent;  
19 -import org.flowable.bpmn.model.BpmnModel;  
20 -import org.flowable.bpmn.model.FlowElement;  
21 -import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;  
22 -import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;  
23 -import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;  
24 -import org.flowable.job.api.Job;  
25 -import org.flowable.task.api.Task;  
26 -import org.springframework.context.annotation.Lazy;  
27 -import org.springframework.stereotype.Component;  
28 -  
29 -import java.util.List;  
30 -import java.util.Set;  
31 -  
32 -// TODO @芋艿:这块需要仔细再瞅瞅  
33 -/**  
34 - * 监听定时器触发事件  
35 - *  
36 - * @author jason  
37 - */  
38 -@Component  
39 -@Slf4j  
40 -public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener {  
41 -  
42 - @Resource  
43 - @Lazy // 延迟加载,避免循环依赖  
44 - private BpmModelService bpmModelService;  
45 - @Resource  
46 - @Lazy // 延迟加载,避免循环依赖  
47 - private BpmTaskService bpmTaskService;  
48 -  
49 - @Resource  
50 - private TodoTaskReminderProducer todoTaskReminderProducer;  
51 -  
52 - public static final Set<FlowableEngineEventType> TIME_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()  
53 - .add(FlowableEngineEventType.TIMER_FIRED)  
54 - .build();  
55 -  
56 - public BpmTimerFiredEventListener() {  
57 - super(TIME_EVENTS);  
58 - }  
59 -  
60 - @Override  
61 - protected void timerFired(FlowableEngineEntityEvent event) {  
62 - String processDefinitionId = event.getProcessDefinitionId();  
63 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);  
64 - Job entity = (Job) event.getEntity();  
65 - FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());  
66 - // 如果是定时器边界事件  
67 - if (element instanceof BoundaryEvent) {  
68 - BoundaryEvent boundaryEvent = (BoundaryEvent) element;  
69 - String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);  
70 - BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));  
71 - // 类型为用户任务超时未处理的情况  
72 - if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) {  
73 - String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION);  
74 - userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction));  
75 - }  
76 - }  
77 - }  
78 -  
79 - private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) {  
80 - BpmUserTaskTimeoutHandlerType userTaskTimeoutAction = BpmUserTaskTimeoutHandlerType.typeOf(timeoutAction);  
81 - if (userTaskTimeoutAction != null) {  
82 - // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ???  
83 - List<Task> taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefKey);  
84 - taskList.forEach(task -> {  
85 - // 自动提醒  
86 - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REMINDER) {  
87 - TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId()))  
88 - .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName());  
89 - todoTaskReminderProducer.sendReminderMessage(message);  
90 - }  
91 - // 自动同意  
92 - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.APPROVE) {  
93 - // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调  
94 - TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId()));  
95 - TenantContextHolder.setIgnore(false);  
96 - BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId())  
97 - .setReason("超时系统自动同意");  
98 - bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req);  
99 - }  
100 - // 自动拒绝  
101 - if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REJECT) {  
102 - // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调  
103 - TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId()));  
104 - TenantContextHolder.setIgnore(false);  
105 - BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝");  
106 - bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req);  
107 - }  
108 - });  
109 - }  
110 - }  
111 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java deleted
1 -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;  
5 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;  
6 -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;  
7 -import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;  
8 -import jakarta.annotation.Resource;  
9 -import lombok.extern.slf4j.Slf4j;  
10 -import org.springframework.context.event.EventListener;  
11 -import org.springframework.scheduling.annotation.Async;  
12 -import org.springframework.stereotype.Component;  
13 -  
14 -import java.util.Map;  
15 -  
16 -/**  
17 - * 待办任务提醒 - 站内信的消费者  
18 - *  
19 - * @author jason  
20 - */  
21 -@Component  
22 -@Slf4j  
23 -public class SysNotifyTodoTaskReminderConsumer {  
24 -  
25 - private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind";  
26 -  
27 - @Resource  
28 - private NotifyMessageSendApi notifyMessageSendApi;  
29 -  
30 - @EventListener  
31 - @Async  
32 - public void onMessage(TodoTaskReminderMessage message) {  
33 - log.info("站内信消费者接收到消息 [消息内容({})] ", message);  
34 - TenantUtils.execute(message.getTenantId(), ()-> {  
35 - Map<String,Object> templateParams = MapUtil.newHashMap();  
36 - templateParams.put("name", message.getTaskName());  
37 - NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId())  
38 - .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams);  
39 - notifyMessageSendApi.sendSingleMessageToAdmin(req);  
40 - });  
41 - }  
42 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java deleted
1 -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task;  
2 -  
3 -import jakarta.validation.constraints.NotEmpty;  
4 -import jakarta.validation.constraints.NotNull;  
5 -import lombok.Data;  
6 -  
7 -/**  
8 - * 待办任务提醒消息  
9 - *  
10 - * @author jason  
11 - */  
12 -@Data  
13 -public class TodoTaskReminderMessage {  
14 -  
15 - /**  
16 - * 租户 Id  
17 - */  
18 - @NotNull(message = "租户 Id 不能未空")  
19 - private Long tenantId;  
20 -  
21 - /**  
22 - * 用户Id  
23 - */  
24 - @NotNull(message = "用户 Id 不能未空")  
25 - private Long userId;  
26 -  
27 - /**  
28 - * 任务名称  
29 - */  
30 - @NotEmpty(message = "任务名称不能未空")  
31 - private String taskName;  
32 -  
33 - // TODO 暂时只有站内信通知. 后面可以增加  
34 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java deleted
1 -package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task;  
2 -  
3 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;  
4 -import jakarta.annotation.Resource;  
5 -import jakarta.validation.Valid;  
6 -import org.springframework.context.ApplicationContext;  
7 -import org.springframework.stereotype.Component;  
8 -import org.springframework.validation.annotation.Validated;  
9 -  
10 -// TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~  
11 -/**  
12 - * 待办任务提醒 Producer  
13 - *  
14 - * @author jason  
15 - */  
16 -@Component  
17 -@Validated  
18 -public class TodoTaskReminderProducer {  
19 -  
20 - @Resource  
21 - private ApplicationContext applicationContext;  
22 -  
23 - public void sendReminderMessage(@Valid TodoTaskReminderMessage message) {  
24 - applicationContext.publishEvent(message);  
25 - }  
26 -  
27 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -53,6 +53,10 @@ public class BpmnModelUtils { @@ -53,6 +53,10 @@ public class BpmnModelUtils {
53 return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); 53 return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
54 } 54 }
55 55
  56 + public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
  57 + return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
  58 + }
  59 +
56 public static String parseExtensionElement(FlowElement flowElement, String elementName) { 60 public static String parseExtensionElement(FlowElement flowElement, String elementName) {
57 if (flowElement == null) { 61 if (flowElement == null) {
58 return null; 62 return null;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2
  3 +import cn.hutool.core.util.ObjectUtil;
3 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 4 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  5 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
5 import org.flowable.common.engine.api.delegate.Expression; 7 import org.flowable.common.engine.api.delegate.Expression;
6 import org.flowable.common.engine.api.variable.VariableContainer; 8 import org.flowable.common.engine.api.variable.VariableContainer;
@@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo; @@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo;
16 import java.util.HashMap; 18 import java.util.HashMap;
17 import java.util.List; 19 import java.util.List;
18 import java.util.Map; 20 import java.util.Map;
  21 +import java.util.Objects;
19 22
20 /** 23 /**
21 * Flowable 相关的工具方法 24 * Flowable 相关的工具方法
@@ -39,6 +42,16 @@ public class FlowableUtils { @@ -39,6 +42,16 @@ public class FlowableUtils {
39 return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; 42 return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID;
40 } 43 }
41 44
  45 + public static void execute(String tenantIdStr, Runnable runnable) {
  46 + if (ObjectUtil.isEmpty(tenantIdStr)
  47 + || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) {
  48 + runnable.run();
  49 + } else {
  50 + Long tenantId = Long.valueOf(tenantIdStr);
  51 + TenantUtils.execute(tenantId, runnable);
  52 + }
  53 + }
  54 +
42 // ========== Execution 相关的工具方法 ========== 55 // ========== Execution 相关的工具方法 ==========
43 56
44 /** 57 /**
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler;
12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; 12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum;
  13 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
13 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; 14 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
14 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; 15 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
15 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 16 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
@@ -25,7 +26,6 @@ import java.util.Objects; @@ -25,7 +26,6 @@ import java.util.Objects;
25 26
26 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; 27 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
27 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; 28 import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
28 -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT;  
29 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; 29 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
30 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; 30 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER;
31 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 31 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
@@ -59,7 +59,6 @@ public class SimpleModelUtils { @@ -59,7 +59,6 @@ public class SimpleModelUtils {
59 */ 59 */
60 public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; 60 public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
61 61
62 - // TODO-DONE @jason:建议方法名,改成 buildBpmnModel  
63 // TODO @yunai:注释需要完善下; 62 // TODO @yunai:注释需要完善下;
64 63
65 /** 64 /**
@@ -339,32 +338,41 @@ public class SimpleModelUtils { @@ -339,32 +338,41 @@ public class SimpleModelUtils {
339 List<FlowElement> flowElements = new ArrayList<>(); 338 List<FlowElement> flowElements = new ArrayList<>();
340 UserTask userTask = buildBpmnUserTask(node); 339 UserTask userTask = buildBpmnUserTask(node);
341 flowElements.add(userTask); 340 flowElements.add(userTask);
  341 +
  342 + // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
342 if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { 343 if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
343 - // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理  
344 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); 344 BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler());
345 flowElements.add(boundaryEvent); 345 flowElements.add(boundaryEvent);
346 } 346 }
347 return flowElements; 347 return flowElements;
348 } 348 }
349 349
  350 + /**
  351 + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
  352 + *
  353 + * @param userTask 审批任务
  354 + * @param timeoutHandler 超时处理器
  355 + * @return BoundaryEvent 超时事件
  356 + */
350 private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { 357 private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
351 - // 定时器边界事件 358 + // 1.1 定时器边界事件
352 BoundaryEvent boundaryEvent = new BoundaryEvent(); 359 BoundaryEvent boundaryEvent = new BoundaryEvent();
353 boundaryEvent.setId("Event-" + IdUtil.fastUUID()); 360 boundaryEvent.setId("Event-" + IdUtil.fastUUID());
354 - // 设置关联的任务为不会被中断  
355 - boundaryEvent.setCancelActivity(false); 361 + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
356 boundaryEvent.setAttachedToRef(userTask); 362 boundaryEvent.setAttachedToRef(userTask);
  363 + // 1.2 定义超时时间、最大提醒次数
357 TimerEventDefinition eventDefinition = new TimerEventDefinition(); 364 TimerEventDefinition eventDefinition = new TimerEventDefinition();
358 eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); 365 eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
359 if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && 366 if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) &&
360 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { 367 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
361 - // 最大提醒次数  
362 - eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); 368 + eventDefinition.setTimeCycle(String.format("R%d/%s",
  369 + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
363 } 370 }
364 boundaryEvent.addEventDefinition(eventDefinition); 371 boundaryEvent.addEventDefinition(eventDefinition);
365 - // 添加定时器边界事件类型  
366 - addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());  
367 - // 添加超时执行动作元素 372 +
  373 + // 2.1 添加定时器边界事件类型
  374 + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString());
  375 + // 2.2 添加超时执行动作元素
368 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); 376 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction()));
369 return boundaryEvent; 377 return boundaryEvent;
370 } 378 }
@@ -448,8 +456,6 @@ public class SimpleModelUtils { @@ -448,8 +456,6 @@ public class SimpleModelUtils {
448 userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); 456 userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
449 } 457 }
450 458
451 - // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批  
452 -  
453 // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? 459 // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈?
454 // 添加候选人元素 460 // 添加候选人元素
455 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); 461 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
@@ -461,10 +467,11 @@ public class SimpleModelUtils { @@ -461,10 +467,11 @@ public class SimpleModelUtils {
461 processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); 467 processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
462 // 添加任务被拒绝的处理元素 468 // 添加任务被拒绝的处理元素
463 addTaskRejectElements(node.getRejectHandler(), userTask); 469 addTaskRejectElements(node.getRejectHandler(), userTask);
  470 + // 添加用户任务的审批人与发起人相同时的处理元素
  471 + addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
464 return userTask; 472 return userTask;
465 } 473 }
466 474
467 -  
468 private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { 475 private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) {
469 if (rejectHandler == null) { 476 if (rejectHandler == null) {
470 return; 477 return;
@@ -473,6 +480,13 @@ public class SimpleModelUtils { @@ -473,6 +480,13 @@ public class SimpleModelUtils {
473 addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); 480 addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
474 } 481 }
475 482
  483 + private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
  484 + if (assignStartUserHandlerType == null) {
  485 + return;
  486 + }
  487 + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
  488 + }
  489 +
476 private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { 490 private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
477 BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); 491 BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod);
478 if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { 492 if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) {
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.message; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.message;
3 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; 3 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
4 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; 4 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
5 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; 5 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
6 - 6 +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
9 /** 9 /**
@@ -36,4 +36,11 @@ public interface BpmMessageService { @@ -36,4 +36,11 @@ public interface BpmMessageService {
36 */ 36 */
37 void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); 37 void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO);
38 38
  39 + /**
  40 + * 发送任务审批超时的消息
  41 + *
  42 + * @param reqDTO 发送信息
  43 + */
  44 + void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO);
  45 +
39 } 46 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum;
6 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; 6 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
7 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; 7 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
8 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; 8 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
  9 +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
9 import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; 10 import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
@@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService { @@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService {
61 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); 62 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams));
62 } 63 }
63 64
  65 + @Override
  66 + public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) {
  67 + Map<String, Object> templateParams = new HashMap<>();
  68 + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
  69 + templateParams.put("taskName", reqDTO.getTaskName());
  70 + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
  71 + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(),
  72 + BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams));
  73 + }
  74 +
64 private String getProcessInstanceDetailUrl(String taskId) { 75 private String getProcessInstanceDetailUrl(String taskId) {
65 return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; 76 return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId;
66 } 77 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.service.message.dto;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * BPM 发送任务审批超时 Request DTO
  9 + */
  10 +@Data
  11 +public class BpmMessageSendWhenTaskTimeoutReqDTO {
  12 +
  13 + /**
  14 + * 流程实例的编号
  15 + */
  16 + @NotEmpty(message = "流程实例的编号不能为空")
  17 + private String processInstanceId;
  18 + /**
  19 + * 流程实例的名字
  20 + */
  21 + @NotEmpty(message = "流程实例的名字不能为空")
  22 + private String processInstanceName;
  23 +
  24 + /**
  25 + * 流程任务的编号
  26 + */
  27 + @NotEmpty(message = "流程任务的编号不能为空")
  28 + private String taskId;
  29 + /**
  30 + * 流程任务的名字
  31 + */
  32 + @NotEmpty(message = "流程任务的名字不能为空")
  33 + private String taskName;
  34 +
  35 + /**
  36 + * 审批人的用户编号
  37 + */
  38 + @NotNull(message = "审批人的用户编号不能为空")
  39 + private Long assigneeUserId;
  40 +
  41 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1 -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import 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 @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.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); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 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. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); } }  
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.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.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import 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 @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.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); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 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. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } }
3 \ 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/BpmTaskService.java
@@ -207,4 +207,13 @@ public interface BpmTaskService { @@ -207,4 +207,13 @@ public interface BpmTaskService {
207 */ 207 */
208 void processTaskAssigned(Task task); 208 void processTaskAssigned(Task task);
209 209
  210 + /**
  211 + * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
  212 + *
  213 + * @param processInstanceId 流程示例编号
  214 + * @param taskDefineKey 任务 Key
  215 + * @param taskAction 处理类型
  216 + */
  217 + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction);
  218 +
210 } 219 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.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.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.*; 5 import cn.hutool.core.util.*;
  6 +import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 8 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  10 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
8 import cn.iocoder.yudao.framework.common.util.object.PageUtils; 11 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
9 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 12 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; 13 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
11 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; 14 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
  15 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; 16 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
  17 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType;
13 import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; 18 import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
14 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; 19 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
15 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; 20 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
@@ -19,6 +24,9 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; @@ -19,6 +24,9 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
19 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 24 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
20 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; 25 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
21 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; 26 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
  27 +import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
  28 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  29 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
22 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 30 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 31 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
24 import jakarta.annotation.Resource; 32 import jakarta.annotation.Resource;
@@ -44,7 +52,6 @@ import org.springframework.stereotype.Service; @@ -44,7 +52,6 @@ import org.springframework.stereotype.Service;
44 import org.springframework.transaction.annotation.Transactional; 52 import org.springframework.transaction.annotation.Transactional;
45 import org.springframework.transaction.support.TransactionSynchronization; 53 import org.springframework.transaction.support.TransactionSynchronization;
46 import org.springframework.transaction.support.TransactionSynchronizationManager; 54 import org.springframework.transaction.support.TransactionSynchronizationManager;
47 -import org.springframework.util.Assert;  
48 55
49 import java.util.*; 56 import java.util.*;
50 import java.util.stream.Stream; 57 import java.util.stream.Stream;
@@ -77,12 +84,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -77,12 +84,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
77 @Resource 84 @Resource
78 private BpmProcessInstanceCopyService processInstanceCopyService; 85 private BpmProcessInstanceCopyService processInstanceCopyService;
79 @Resource 86 @Resource
80 - private BpmModelService bpmModelService; 87 + private BpmModelService modelService;
81 @Resource 88 @Resource
82 private BpmMessageService messageService; 89 private BpmMessageService messageService;
83 90
84 @Resource 91 @Resource
85 private AdminUserApi adminUserApi; 92 private AdminUserApi adminUserApi;
  93 + @Resource
  94 + private DeptApi deptApi;
86 95
87 // ========== Query 查询相关方法 ========== 96 // ========== Query 查询相关方法 ==========
88 97
@@ -226,7 +235,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -226,7 +235,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
226 // 1.1 校验当前任务 task 存在 235 // 1.1 校验当前任务 task 存在
227 Task task = validateTaskExist(id); 236 Task task = validateTaskExist(id);
228 // 1.2 根据流程定义获取流程模型信息 237 // 1.2 根据流程定义获取流程模型信息
229 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); 238 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
230 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); 239 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
231 if (source == null) { 240 if (source == null) {
232 throw exception(TASK_NOT_EXISTS); 241 throw exception(TASK_NOT_EXISTS);
@@ -496,12 +505,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -496,12 +505,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
496 } 505 }
497 506
498 // 3. 根据不同的 RejectHandler 处理策略 507 // 3. 根据不同的 RejectHandler 处理策略
499 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());  
500 - FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); 508 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
  509 + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
501 // 3.1 情况一:驳回到指定的任务节点 510 // 3.1 情况一:驳回到指定的任务节点
502 - BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); 511 + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement);
503 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { 512 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
504 - String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); 513 + String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement);
505 Assert.notNull(returnTaskId, "回退的节点不能为空"); 514 Assert.notNull(returnTaskId, "回退的节点不能为空");
506 returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) 515 returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
507 .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); 516 .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
@@ -560,7 +569,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -560,7 +569,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
560 */ 569 */
561 private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { 570 private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
562 // 1.1 获取流程模型信息 571 // 1.1 获取流程模型信息
563 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); 572 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
564 // 1.3 获取当前任务节点元素 573 // 1.3 获取当前任务节点元素
565 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); 574 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
566 // 1.3 获取跳转的节点元素 575 // 1.3 获取跳转的节点元素
@@ -688,7 +697,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -688,7 +697,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
688 }); 697 });
689 698
690 // 2. 终止流程 699 // 2. 终止流程
691 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); 700 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId());
692 List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); 701 List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey));
693 EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); 702 EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
694 Assert.notNull(endEvent, "结束节点不能未空"); 703 Assert.notNull(endEvent, "结束节点不能未空");
@@ -913,16 +922,110 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -913,16 +922,110 @@ public class BpmTaskServiceImpl implements BpmTaskService {
913 @Override 922 @Override
914 public void afterCommit() { 923 public void afterCommit() {
915 if (StrUtil.isEmpty(task.getAssignee())) { 924 if (StrUtil.isEmpty(task.getAssignee())) {
  925 + log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId());
916 return; 926 return;
917 } 927 }
918 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); 928 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
919 - if (processInstance != null) {  
920 - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));  
921 - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); 929 + if (processInstance == null) {
  930 + log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId());
  931 + return;
  932 + }
  933 +
  934 + // 审批人与提交人为同一人时,根据策略进行处理
  935 + if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
  936 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
  937 + if (bpmnModel == null) {
  938 + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
  939 + return;
  940 + }
  941 + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
  942 + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
  943 +
  944 + // 情况一:自动跳过
  945 + if (ObjectUtils.equalsAny(assignStartUserHandlerType,
  946 + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
  947 + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO()
  948 + .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过"));
  949 + return;
  950 + }
  951 + // 情况二:转交给部门负责人审批
  952 + if (ObjectUtils.equalsAny(assignStartUserHandlerType,
  953 + BpmUserTaskAssignStartUserHandlerTypeEnum.ASSIGN_DEPT_LEADER.getType())) {
  954 + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
  955 + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId());
  956 + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null;
  957 + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId());
  958 + // 找不到部门负责人的情况下,自动审批通过
  959 + // noinspection DataFlowIssue
  960 + if (dept.getLeaderUserId() == null) {
  961 + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO()
  962 + .setId(task.getId()).setReason("审批人与提交人为同一人时,找不到部门负责人,自动通过"));
  963 + return;
  964 + }
  965 + // 找得到部门负责人的情况下,修改负责人
  966 + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) {
  967 + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO()
  968 + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId())
  969 + .setReason("审批人与提交人为同一人时,转交给部门负责人审批"));
  970 + return;
  971 + }
  972 + // 如果部门负责人是自己,还是自己审批吧~
  973 + }
922 } 974 }
  975 +
  976 + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
  977 + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
923 } 978 }
924 979
925 }); 980 });
926 } 981 }
927 982
  983 + @Override
  984 + @Transactional(rollbackFor = Exception.class)
  985 + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) {
  986 + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
  987 + if (processInstance == null) {
  988 + log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId);
  989 + return;
  990 + }
  991 + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey);
  992 + // TODO 优化:未来需要考虑加签的情况
  993 + if (CollUtil.isEmpty(taskList)) {
  994 + log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey);
  995 + return;
  996 + }
  997 +
  998 + taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> {
  999 + // 情况一:自动提醒
  1000 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) {
  1001 + messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO()
  1002 + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
  1003 + .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee())));
  1004 + return;
  1005 + }
  1006 +
  1007 + // 情况二:自动同意
  1008 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) {
  1009 + approveTask(Long.parseLong(task.getAssignee()),
  1010 + new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意"));
  1011 + return;
  1012 + }
  1013 +
  1014 + // 情况三:自动拒绝
  1015 + if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) {
  1016 + rejectTask(Long.parseLong(task.getAssignee()),
  1017 + new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"));
  1018 + }
  1019 + }));
  1020 + }
  1021 +
  1022 + /**
  1023 + * 获得自身的代理对象,解决 AOP 生效问题
  1024 + *
  1025 + * @return 自己
  1026 + */
  1027 + private BpmTaskServiceImpl getSelf() {
  1028 + return SpringUtil.getBean(getClass());
  1029 + }
  1030 +
928 } 1031 }