mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm
This commit is contained in:
		| @ -13,8 +13,7 @@ import lombok.Getter; | |||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| public enum BpmBoundaryEventType { | public enum BpmBoundaryEventType { | ||||||
|  |  | ||||||
|     USER_TASK_TIMEOUT(1,"用户任务超时"), |     USER_TASK_TIMEOUT(1,"用户任务超时"); | ||||||
|     USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理"); |  | ||||||
|  |  | ||||||
|     private final Integer type; |     private final Integer type; | ||||||
|     private final String name; |     private final String name; | ||||||
| @ -22,4 +21,5 @@ public enum BpmBoundaryEventType { | |||||||
|     public static BpmBoundaryEventType typeOf(Integer type) { |     public static BpmBoundaryEventType typeOf(Integer type) { | ||||||
|         return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); |         return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,27 @@ | |||||||
|  | package cn.iocoder.yudao.module.bpm.enums.definition; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; | ||||||
|  | import lombok.Getter; | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * BPM 用户任务的审批人与发起人相同时,处理类型枚举 | ||||||
|  |  * | ||||||
|  |  * @author 芋道源码 | ||||||
|  |  */ | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | @Getter | ||||||
|  | public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable { | ||||||
|  |  | ||||||
|  |     START_USER_AUDIT(1), // 由发起人对自己审批 | ||||||
|  |     SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 | ||||||
|  |     ASSIGN_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 | ||||||
|  |  | ||||||
|  |     private final Integer type; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int[] array() { | ||||||
|  |         return new int[0]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -20,6 +20,7 @@ public enum BpmUserTaskTimeoutHandlerType implements IntArrayValuable { | |||||||
|     APPROVE(2, "自动同意"), |     APPROVE(2, "自动同意"), | ||||||
|     REJECT(3, "自动拒绝"); |     REJECT(3, "自动拒绝"); | ||||||
|  |  | ||||||
|  |     // TODO @jason:type 是不是更合适哈; | ||||||
|     private final Integer action; |     private final Integer action; | ||||||
|     private final String name; |     private final String name; | ||||||
|  |  | ||||||
|  | |||||||
| @ -14,7 +14,8 @@ public enum BpmMessageEnum { | |||||||
|  |  | ||||||
|     PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 |     PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 | ||||||
|     PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 |     PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 | ||||||
|     TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 |     TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 | ||||||
|  |     TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 短信模板的标识 |      * 短信模板的标识 | ||||||
|  | |||||||
| @ -1,10 +1,7 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; | package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.common.validation.InEnum; | import cn.iocoder.yudao.framework.common.validation.InEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; | import cn.iocoder.yudao.module.bpm.enums.definition.*; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; |  | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; |  | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
| import com.fasterxml.jackson.annotation.JsonInclude; | import com.fasterxml.jackson.annotation.JsonInclude; | ||||||
| @ -84,6 +81,10 @@ public class BpmSimpleModelNodeVO { | |||||||
|      */ |      */ | ||||||
|     private TimeoutHandler timeoutHandler; |     private TimeoutHandler timeoutHandler; | ||||||
|  |  | ||||||
|  |     @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") | ||||||
|  |     @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) | ||||||
|  |     private Integer assignStartUserHandlerType; | ||||||
|  |  | ||||||
|     @Data |     @Data | ||||||
|     @Schema(description = "审批节点拒绝处理策略") |     @Schema(description = "审批节点拒绝处理策略") | ||||||
|     public static class RejectHandler { |     public static class RejectHandler { | ||||||
| @ -96,6 +97,7 @@ public class BpmSimpleModelNodeVO { | |||||||
|         private String returnNodeId; |         private String returnNodeId; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // TODO @芋艿:参数校验 | ||||||
|     @Data |     @Data | ||||||
|     @Schema(description = "审批节点超时处理策略") |     @Schema(description = "审批节点超时处理策略") | ||||||
|     public static class TimeoutHandler { |     public static class TimeoutHandler { | ||||||
| @ -103,6 +105,7 @@ public class BpmSimpleModelNodeVO { | |||||||
|         @Schema(description = "是否开启超时处理", example = "false") |         @Schema(description = "是否开启超时处理", example = "false") | ||||||
|         private Boolean enable; |         private Boolean enable; | ||||||
|  |  | ||||||
|  |         // TODO @jason:type 是不是更合适哈; | ||||||
|         @Schema(description = "任务超时未处理的行为", example = "1") |         @Schema(description = "任务超时未处理的行为", example = "1") | ||||||
|         @InEnum(BpmUserTaskTimeoutHandlerType.class) |         @InEnum(BpmUserTaskTimeoutHandlerType.class) | ||||||
|         private Integer action; |         private Integer action; | ||||||
| @ -112,6 +115,7 @@ public class BpmSimpleModelNodeVO { | |||||||
|  |  | ||||||
|         @Schema(description = "最大提醒次数", example = "1") |         @Schema(description = "最大提醒次数", example = "1") | ||||||
|         private Integer maxRemindCount; |         private Integer maxRemindCount; | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Data |     @Data | ||||||
| @ -129,14 +133,7 @@ public class BpmSimpleModelNodeVO { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用 |     // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用 | ||||||
|     // Integer approveMethod; 审批方式;仅审批节点会使用 |  | ||||||
|     // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级 |  | ||||||
|     // TODO @芋艿:① 审批人的选择; |  | ||||||
|     // TODO @芋艿:⑥ 没有人的策略? |     // TODO @芋艿:⑥ 没有人的策略? | ||||||
|     // TODO @芋艿:② 审批拒绝的策略? |  | ||||||
|     // TODO @芋艿:③ 配置的可操作列表?(操作权限) |  | ||||||
|     // TODO @芋艿:④ 表单的权限列表? |  | ||||||
|     // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔; |  | ||||||
|     // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 |     // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -176,7 +176,6 @@ public interface BpmTaskConvert { | |||||||
|         childTask.setParentTaskId(parentTask.getId()); |         childTask.setParentTaskId(parentTask.getId()); | ||||||
|         childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); |         childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); | ||||||
|         childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); |         childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); | ||||||
| //        childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错) |  | ||||||
|         childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); |         childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); | ||||||
|         childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); |         childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); | ||||||
|         childTask.setPriority(parentTask.getPriority()); |         childTask.setPriority(parentTask.getPriority()); | ||||||
|  | |||||||
| @ -2,11 +2,15 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; | |||||||
|  |  | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.lang.Assert; | import cn.hutool.core.lang.Assert; | ||||||
|  | import cn.hutool.core.util.ObjectUtil; | ||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.hutool.extra.spring.SpringUtil; | ||||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||||
| import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; | ||||||
|  | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | ||||||
|  | import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; | ||||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||||
| import com.google.common.annotations.VisibleForTesting; | import com.google.common.annotations.VisibleForTesting; | ||||||
| @ -14,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; | |||||||
| import org.flowable.bpmn.model.BpmnModel; | import org.flowable.bpmn.model.BpmnModel; | ||||||
| import org.flowable.bpmn.model.UserTask; | import org.flowable.bpmn.model.UserTask; | ||||||
| import org.flowable.engine.delegate.DelegateExecution; | import org.flowable.engine.delegate.DelegateExecution; | ||||||
|  | import org.flowable.engine.runtime.ProcessInstance; | ||||||
|  |  | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker { | |||||||
|         Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); |         Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); | ||||||
|         // 1.2 移除被禁用的用户 |         // 1.2 移除被禁用的用户 | ||||||
|         removeDisableUsers(userIds); |         removeDisableUsers(userIds); | ||||||
|  |         // 1.3 移除发起人的用户 | ||||||
|  |         removeStartUserIfSkip(execution, userIds); | ||||||
|  |  | ||||||
|         // 2. 校验是否有候选人 |         // 2. 校验是否有候选人 | ||||||
|         if (CollUtil.isEmpty(userIds)) { |         if (CollUtil.isEmpty(userIds)) { | ||||||
| @ -108,6 +115,29 @@ public class BpmTaskCandidateInvoker { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 | ||||||
|  |      * | ||||||
|  |      * 注意:如果只有一个候选人,则不处理,避免无法审批 | ||||||
|  |      * | ||||||
|  |      * @param execution 执行中的任务 | ||||||
|  |      * @param assigneeUserIds 当前分配的候选人 | ||||||
|  |      */ | ||||||
|  |     @VisibleForTesting | ||||||
|  |     void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) { | ||||||
|  |         if (CollUtil.size(assigneeUserIds) <= 1) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement()); | ||||||
|  |         if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) | ||||||
|  |                 .getProcessInstance(execution.getProcessInstanceId()); | ||||||
|  |         Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); | ||||||
|  |         assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { |     private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { | ||||||
|         BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); |         BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); | ||||||
|         Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); |         Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); | ||||||
|  | |||||||
| @ -29,12 +29,17 @@ public interface BpmnModelConstants { | |||||||
|     String BOUNDARY_EVENT_TYPE = "boundaryEventType"; |     String BOUNDARY_EVENT_TYPE = "boundaryEventType"; | ||||||
|  |  | ||||||
|     // TODO @jason:这个命名,应该也要改哈 |     // TODO @jason:这个命名,应该也要改哈 | ||||||
|  |     // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler; | ||||||
|     /** |     /** | ||||||
|      * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 |      * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 | ||||||
|      */ |      */ | ||||||
|     String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; |     String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; | ||||||
|  |  | ||||||
|     // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;2)rejectHandlerType 改成 rejectHandler 哇? |     /** | ||||||
|  |      * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 | ||||||
|  |      */ | ||||||
|  |     String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 |      * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -1,17 +1,27 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; | package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; | ||||||
|  |  | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.util.ObjectUtil; | ||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
|  | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||||
|  | import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | ||||||
|  | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | ||||||
|  | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | ||||||
|  | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | ||||||
| import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; | import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; | ||||||
| import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; | import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; | ||||||
| import com.google.common.collect.ImmutableSet; | import com.google.common.collect.ImmutableSet; | ||||||
| import jakarta.annotation.Resource; | import jakarta.annotation.Resource; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.flowable.bpmn.model.BoundaryEvent; | ||||||
|  | import org.flowable.bpmn.model.BpmnModel; | ||||||
|  | import org.flowable.bpmn.model.FlowElement; | ||||||
| import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; | import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; | ||||||
| import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; | import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; | ||||||
| import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; | import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; | ||||||
| import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; | import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; | ||||||
| import org.flowable.engine.history.HistoricActivityInstance; | import org.flowable.engine.history.HistoricActivityInstance; | ||||||
|  | import org.flowable.job.api.Job; | ||||||
| import org.flowable.task.api.Task; | import org.flowable.task.api.Task; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
| @ -28,6 +38,9 @@ import java.util.Set; | |||||||
| @Slf4j | @Slf4j | ||||||
| public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { | public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     @Lazy // 延迟加载,避免循环依赖 | ||||||
|  |     private BpmModelService modelService; | ||||||
|     @Resource |     @Resource | ||||||
|     @Lazy // 解决循环依赖 |     @Lazy // 解决循环依赖 | ||||||
|     private BpmTaskService taskService; |     private BpmTaskService taskService; | ||||||
| @ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { | |||||||
|             .add(FlowableEngineEventType.TASK_ASSIGNED) |             .add(FlowableEngineEventType.TASK_ASSIGNED) | ||||||
| //            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 | //            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 | ||||||
|             .add(FlowableEngineEventType.ACTIVITY_CANCELLED) |             .add(FlowableEngineEventType.ACTIVITY_CANCELLED) | ||||||
|  |             .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 | ||||||
|             .build(); |             .build(); | ||||||
|  |  | ||||||
|     public BpmTaskEventListener() { |     public BpmTaskEventListener() { | ||||||
| @ -72,4 +86,30 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected void timerFired(FlowableEngineEntityEvent event) { | ||||||
|  |         // 1.1 只处理 BoundaryEvent 边界计时时间 | ||||||
|  |         String processDefinitionId = event.getProcessDefinitionId(); | ||||||
|  |         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); | ||||||
|  |         Job entity = (Job) event.getEntity(); | ||||||
|  |         FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); | ||||||
|  |         if (!(element instanceof BoundaryEvent)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         // 1.2 判断是否为超时处理 | ||||||
|  |         BoundaryEvent boundaryEvent = (BoundaryEvent) element; | ||||||
|  |         String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, | ||||||
|  |                 BpmnModelConstants.BOUNDARY_EVENT_TYPE); | ||||||
|  |         BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); | ||||||
|  |         if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 2. 处理超时 | ||||||
|  |         String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, | ||||||
|  |                 BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); | ||||||
|  |         String taskKey = boundaryEvent.getAttachedToRefId(); | ||||||
|  |         taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutAction)); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,111 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |  | ||||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |  | ||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; |  | ||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; |  | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; |  | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer; |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |  | ||||||
| import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; |  | ||||||
| import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; |  | ||||||
| import com.google.common.collect.ImmutableSet; |  | ||||||
| import jakarta.annotation.Resource; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.flowable.bpmn.model.BoundaryEvent; |  | ||||||
| import org.flowable.bpmn.model.BpmnModel; |  | ||||||
| import org.flowable.bpmn.model.FlowElement; |  | ||||||
| import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; |  | ||||||
| import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; |  | ||||||
| import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; |  | ||||||
| import org.flowable.job.api.Job; |  | ||||||
| import org.flowable.task.api.Task; |  | ||||||
| import org.springframework.context.annotation.Lazy; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Set; |  | ||||||
|  |  | ||||||
| // TODO @芋艿:这块需要仔细再瞅瞅 |  | ||||||
| /** |  | ||||||
|  * 监听定时器触发事件 |  | ||||||
|  * |  | ||||||
|  * @author jason |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     @Lazy // 延迟加载,避免循环依赖 |  | ||||||
|     private BpmModelService bpmModelService; |  | ||||||
|     @Resource |  | ||||||
|     @Lazy // 延迟加载,避免循环依赖 |  | ||||||
|     private BpmTaskService bpmTaskService; |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private TodoTaskReminderProducer todoTaskReminderProducer; |  | ||||||
|  |  | ||||||
|     public static final Set<FlowableEngineEventType> TIME_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() |  | ||||||
|             .add(FlowableEngineEventType.TIMER_FIRED) |  | ||||||
|             .build(); |  | ||||||
|  |  | ||||||
|     public BpmTimerFiredEventListener() { |  | ||||||
|         super(TIME_EVENTS); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     protected void timerFired(FlowableEngineEntityEvent event) { |  | ||||||
|         String processDefinitionId = event.getProcessDefinitionId(); |  | ||||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); |  | ||||||
|         Job entity = (Job) event.getEntity(); |  | ||||||
|         FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); |  | ||||||
|         // 如果是定时器边界事件 |  | ||||||
|         if (element instanceof BoundaryEvent) { |  | ||||||
|             BoundaryEvent boundaryEvent = (BoundaryEvent) element; |  | ||||||
|             String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); |  | ||||||
|             BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); |  | ||||||
|             // 类型为用户任务超时未处理的情况 |  | ||||||
|             if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) { |  | ||||||
|                 String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); |  | ||||||
|                 userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) { |  | ||||||
|         BpmUserTaskTimeoutHandlerType userTaskTimeoutAction = BpmUserTaskTimeoutHandlerType.typeOf(timeoutAction); |  | ||||||
|         if (userTaskTimeoutAction != null) { |  | ||||||
|             // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? |  | ||||||
|             List<Task> taskList = bpmTaskService.getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefKey); |  | ||||||
|             taskList.forEach(task -> { |  | ||||||
|                 // 自动提醒 |  | ||||||
|                 if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REMINDER) { |  | ||||||
|                     TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) |  | ||||||
|                             .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); |  | ||||||
|                     todoTaskReminderProducer.sendReminderMessage(message); |  | ||||||
|                 } |  | ||||||
|                 // 自动同意 |  | ||||||
|                 if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.APPROVE) { |  | ||||||
|                     // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 |  | ||||||
|                     TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); |  | ||||||
|                     TenantContextHolder.setIgnore(false); |  | ||||||
|                     BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId()) |  | ||||||
|                             .setReason("超时系统自动同意"); |  | ||||||
|                     bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); |  | ||||||
|                 } |  | ||||||
|                 // 自动拒绝 |  | ||||||
|                 if (userTaskTimeoutAction == BpmUserTaskTimeoutHandlerType.REJECT) { |  | ||||||
|                     // TODO  @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 |  | ||||||
|                     TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); |  | ||||||
|                     TenantContextHolder.setIgnore(false); |  | ||||||
|                     BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"); |  | ||||||
|                     bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,42 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task; |  | ||||||
|  |  | ||||||
| import cn.hutool.core.map.MapUtil; |  | ||||||
| import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; |  | ||||||
| import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; |  | ||||||
| import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; |  | ||||||
| import jakarta.annotation.Resource; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.springframework.context.event.EventListener; |  | ||||||
| import org.springframework.scheduling.annotation.Async; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import java.util.Map; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  *  待办任务提醒 - 站内信的消费者 |  | ||||||
|  * |  | ||||||
|  * @author jason |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Slf4j |  | ||||||
| public class SysNotifyTodoTaskReminderConsumer { |  | ||||||
|  |  | ||||||
|     private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind"; |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private NotifyMessageSendApi notifyMessageSendApi; |  | ||||||
|  |  | ||||||
|     @EventListener |  | ||||||
|     @Async |  | ||||||
|     public void onMessage(TodoTaskReminderMessage message) { |  | ||||||
|         log.info("站内信消费者接收到消息 [消息内容({})] ", message); |  | ||||||
|         TenantUtils.execute(message.getTenantId(), ()-> { |  | ||||||
|             Map<String,Object> templateParams = MapUtil.newHashMap(); |  | ||||||
|             templateParams.put("name", message.getTaskName()); |  | ||||||
|             NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId()) |  | ||||||
|                     .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams); |  | ||||||
|             notifyMessageSendApi.sendSingleMessageToAdmin(req); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,34 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task; |  | ||||||
|  |  | ||||||
| import jakarta.validation.constraints.NotEmpty; |  | ||||||
| import jakarta.validation.constraints.NotNull; |  | ||||||
| import lombok.Data; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 待办任务提醒消息 |  | ||||||
|  * |  | ||||||
|  * @author jason |  | ||||||
|  */ |  | ||||||
| @Data |  | ||||||
| public class TodoTaskReminderMessage { |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 租户 Id |  | ||||||
|      */ |  | ||||||
|     @NotNull(message = "租户 Id 不能未空") |  | ||||||
|     private Long tenantId; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 用户Id |  | ||||||
|      */ |  | ||||||
|     @NotNull(message = "用户 Id 不能未空") |  | ||||||
|     private Long userId; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 任务名称 |  | ||||||
|      */ |  | ||||||
|     @NotEmpty(message = "任务名称不能未空") |  | ||||||
|     private String taskName; |  | ||||||
|  |  | ||||||
|     // TODO 暂时只有站内信通知. 后面可以增加 |  | ||||||
| } |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; |  | ||||||
| import jakarta.annotation.Resource; |  | ||||||
| import jakarta.validation.Valid; |  | ||||||
| import org.springframework.context.ApplicationContext; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
| import org.springframework.validation.annotation.Validated; |  | ||||||
|  |  | ||||||
| // TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~ |  | ||||||
| /** |  | ||||||
|  * 待办任务提醒 Producer |  | ||||||
|  * |  | ||||||
|  * @author jason |  | ||||||
|  */ |  | ||||||
| @Component |  | ||||||
| @Validated |  | ||||||
| public class TodoTaskReminderProducer { |  | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private ApplicationContext applicationContext; |  | ||||||
|  |  | ||||||
|     public void sendReminderMessage(@Valid TodoTaskReminderMessage message) { |  | ||||||
|         applicationContext.publishEvent(message); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @ -53,6 +53,10 @@ public class BpmnModelUtils { | |||||||
|         return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); |         return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { | ||||||
|  |         return NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public static String parseExtensionElement(FlowElement flowElement, String elementName) { |     public static String parseExtensionElement(FlowElement flowElement, String elementName) { | ||||||
|         if (flowElement == null) { |         if (flowElement == null) { | ||||||
|             return null; |             return null; | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; | package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.ObjectUtil; | ||||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||||
|  | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; | ||||||
| import org.flowable.common.engine.api.delegate.Expression; | import org.flowable.common.engine.api.delegate.Expression; | ||||||
| import org.flowable.common.engine.api.variable.VariableContainer; | import org.flowable.common.engine.api.variable.VariableContainer; | ||||||
| @ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo; | |||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Flowable 相关的工具方法 |  * Flowable 相关的工具方法 | ||||||
| @ -39,6 +42,16 @@ public class FlowableUtils { | |||||||
|         return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; |         return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static void execute(String tenantIdStr, Runnable runnable) { | ||||||
|  |         if (ObjectUtil.isEmpty(tenantIdStr) | ||||||
|  |                 || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { | ||||||
|  |             runnable.run(); | ||||||
|  |         } else { | ||||||
|  |             Long tenantId = Long.valueOf(tenantIdStr); | ||||||
|  |             TenantUtils.execute(tenantId, runnable); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // ========== Execution 相关的工具方法 ========== |     // ========== Execution 相关的工具方法 ========== | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | ||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; | import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; | ||||||
|  | import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; | ||||||
| import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | ||||||
| @ -25,7 +26,6 @@ import java.util.Objects; | |||||||
|  |  | ||||||
| import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; | import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; | ||||||
| import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; | import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; | ||||||
| import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT; |  | ||||||
| import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; | import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; | ||||||
| import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; | import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType.REMINDER; | ||||||
| import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; | ||||||
| @ -59,7 +59,6 @@ public class SimpleModelUtils { | |||||||
|      */ |      */ | ||||||
|     public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; |     public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}"; | ||||||
|  |  | ||||||
|     // TODO-DONE @jason:建议方法名,改成 buildBpmnModel |  | ||||||
|     // TODO @yunai:注释需要完善下; |     // TODO @yunai:注释需要完善下; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @ -339,32 +338,41 @@ public class SimpleModelUtils { | |||||||
|         List<FlowElement> flowElements = new ArrayList<>(); |         List<FlowElement> flowElements = new ArrayList<>(); | ||||||
|         UserTask userTask = buildBpmnUserTask(node); |         UserTask userTask = buildBpmnUserTask(node); | ||||||
|         flowElements.add(userTask); |         flowElements.add(userTask); | ||||||
|  |  | ||||||
|  |         // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 | ||||||
|         if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { |         if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { | ||||||
|             // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 |  | ||||||
|             BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); |             BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler()); | ||||||
|             flowElements.add(boundaryEvent); |             flowElements.add(boundaryEvent); | ||||||
|         } |         } | ||||||
|         return flowElements; |         return flowElements; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 | ||||||
|  |      * | ||||||
|  |      * @param userTask 审批任务 | ||||||
|  |      * @param timeoutHandler 超时处理器 | ||||||
|  |      * @return BoundaryEvent 超时事件 | ||||||
|  |      */ | ||||||
|     private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { |     private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) { | ||||||
|         // 定时器边界事件 |         // 1.1 定时器边界事件 | ||||||
|         BoundaryEvent boundaryEvent = new BoundaryEvent(); |         BoundaryEvent boundaryEvent = new BoundaryEvent(); | ||||||
|         boundaryEvent.setId("Event-" + IdUtil.fastUUID()); |         boundaryEvent.setId("Event-" + IdUtil.fastUUID()); | ||||||
|         // 设置关联的任务为不会被中断 |         boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 | ||||||
|         boundaryEvent.setCancelActivity(false); |  | ||||||
|         boundaryEvent.setAttachedToRef(userTask); |         boundaryEvent.setAttachedToRef(userTask); | ||||||
|  |         // 1.2 定义超时时间、最大提醒次数 | ||||||
|         TimerEventDefinition eventDefinition = new TimerEventDefinition(); |         TimerEventDefinition eventDefinition = new TimerEventDefinition(); | ||||||
|         eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); |         eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); | ||||||
|         if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && |         if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) && | ||||||
|                 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { |                 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { | ||||||
|             // 最大提醒次数 |             eventDefinition.setTimeCycle(String.format("R%d/%s", | ||||||
|             eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); |                     timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); | ||||||
|         } |         } | ||||||
|         boundaryEvent.addEventDefinition(eventDefinition); |         boundaryEvent.addEventDefinition(eventDefinition); | ||||||
|         // 添加定时器边界事件类型 |  | ||||||
|         addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); |         // 2.1 添加定时器边界事件类型 | ||||||
|         // 添加超时执行动作元素 |         addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString()); | ||||||
|  |         // 2.2 添加超时执行动作元素 | ||||||
|         addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); |         addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); | ||||||
|         return boundaryEvent; |         return boundaryEvent; | ||||||
|     } |     } | ||||||
| @ -448,8 +456,6 @@ public class SimpleModelUtils { | |||||||
|             userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); |             userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批 |  | ||||||
|  |  | ||||||
|         // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? |         // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈? | ||||||
|         // 添加候选人元素 |         // 添加候选人元素 | ||||||
|         addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); |         addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); | ||||||
| @ -461,10 +467,11 @@ public class SimpleModelUtils { | |||||||
|         processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); |         processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); | ||||||
|         // 添加任务被拒绝的处理元素 |         // 添加任务被拒绝的处理元素 | ||||||
|         addTaskRejectElements(node.getRejectHandler(), userTask); |         addTaskRejectElements(node.getRejectHandler(), userTask); | ||||||
|  |         // 添加用户任务的审批人与发起人相同时的处理元素 | ||||||
|  |         addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); | ||||||
|         return userTask; |         return userTask; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { |     private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) { | ||||||
|         if (rejectHandler == null) { |         if (rejectHandler == null) { | ||||||
|             return; |             return; | ||||||
| @ -473,6 +480,13 @@ public class SimpleModelUtils { | |||||||
|         addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); |         addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { | ||||||
|  |         if (assignStartUserHandlerType == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { |     private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { | ||||||
|         BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); |         BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); | ||||||
|         if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { |         if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.message; | |||||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; | ||||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; | ||||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | ||||||
|  | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | ||||||
| import jakarta.validation.Valid; | import jakarta.validation.Valid; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -36,4 +36,11 @@ public interface BpmMessageService { | |||||||
|      */ |      */ | ||||||
|     void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); |     void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 发送任务审批超时的消息 | ||||||
|  |      * | ||||||
|  |      * @param reqDTO 发送信息 | ||||||
|  |      */ | ||||||
|  |     void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum; | |||||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; | ||||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; | ||||||
| import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | ||||||
|  | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | ||||||
| import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; | import cn.iocoder.yudao.module.system.api.sms.SmsSendApi; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| @ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService { | |||||||
|                 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); |                 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { | ||||||
|  |         Map<String, Object> templateParams = new HashMap<>(); | ||||||
|  |         templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); | ||||||
|  |         templateParams.put("taskName", reqDTO.getTaskName()); | ||||||
|  |         templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); | ||||||
|  |         smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), | ||||||
|  |                 BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private String getProcessInstanceDetailUrl(String taskId) { |     private String getProcessInstanceDetailUrl(String taskId) { | ||||||
|         return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; |         return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -0,0 +1,41 @@ | |||||||
|  | package cn.iocoder.yudao.module.bpm.service.message.dto; | ||||||
|  |  | ||||||
|  | import jakarta.validation.constraints.NotEmpty; | ||||||
|  | import jakarta.validation.constraints.NotNull; | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * BPM 发送任务审批超时 Request DTO | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | public class BpmMessageSendWhenTaskTimeoutReqDTO { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 流程实例的编号 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "流程实例的编号不能为空") | ||||||
|  |     private String processInstanceId; | ||||||
|  |     /** | ||||||
|  |      * 流程实例的名字 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "流程实例的名字不能为空") | ||||||
|  |     private String processInstanceName; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 流程任务的编号 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "流程任务的编号不能为空") | ||||||
|  |     private String taskId; | ||||||
|  |     /** | ||||||
|  |      * 流程任务的名字 | ||||||
|  |      */ | ||||||
|  |     @NotEmpty(message = "流程任务的名字不能为空") | ||||||
|  |     private String taskName; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 审批人的用户编号 | ||||||
|  |      */ | ||||||
|  |     @NotNull(message = "审批人的用户编号不能为空") | ||||||
|  |     private Long assigneeUserId; | ||||||
|  |  | ||||||
|  | } | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -207,4 +207,13 @@ public interface BpmTaskService { | |||||||
|      */ |      */ | ||||||
|     void processTaskAssigned(Task task); |     void processTaskAssigned(Task task); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 | ||||||
|  |      * | ||||||
|  |      * @param processInstanceId 流程示例编号 | ||||||
|  |      * @param taskDefineKey 任务 Key | ||||||
|  |      * @param taskAction 处理类型 | ||||||
|  |      */ | ||||||
|  |     void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction); | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +1,20 @@ | |||||||
| package cn.iocoder.yudao.module.bpm.service.task; | package cn.iocoder.yudao.module.bpm.service.task; | ||||||
|  |  | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.lang.Assert; | ||||||
| import cn.hutool.core.util.*; | import cn.hutool.core.util.*; | ||||||
|  | import cn.hutool.extra.spring.SpringUtil; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.date.DateUtils; | import cn.iocoder.yudao.framework.common.util.date.DateUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||||
|  | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.object.PageUtils; | import cn.iocoder.yudao.framework.common.util.object.PageUtils; | ||||||
| import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; | ||||||
| import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | ||||||
| import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | ||||||
|  | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; | ||||||
|  | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerType; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; | import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; | ||||||
| import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; | ||||||
| @ -19,6 +24,9 @@ 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.framework.flowable.core.util.FlowableUtils; | ||||||
| import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | ||||||
| import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; | import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; | ||||||
|  | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; | ||||||
|  | import cn.iocoder.yudao.module.system.api.dept.DeptApi; | ||||||
|  | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | ||||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||||
| import jakarta.annotation.Resource; | import jakarta.annotation.Resource; | ||||||
| @ -44,7 +52,6 @@ import org.springframework.stereotype.Service; | |||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import org.springframework.transaction.support.TransactionSynchronization; | import org.springframework.transaction.support.TransactionSynchronization; | ||||||
| import org.springframework.transaction.support.TransactionSynchronizationManager; | import org.springframework.transaction.support.TransactionSynchronizationManager; | ||||||
| import org.springframework.util.Assert; |  | ||||||
|  |  | ||||||
| import java.util.*; | import java.util.*; | ||||||
| import java.util.stream.Stream; | import java.util.stream.Stream; | ||||||
| @ -77,12 +84,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|     @Resource |     @Resource | ||||||
|     private BpmProcessInstanceCopyService processInstanceCopyService; |     private BpmProcessInstanceCopyService processInstanceCopyService; | ||||||
|     @Resource |     @Resource | ||||||
|     private BpmModelService bpmModelService; |     private BpmModelService modelService; | ||||||
|     @Resource |     @Resource | ||||||
|     private BpmMessageService messageService; |     private BpmMessageService messageService; | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private AdminUserApi adminUserApi; |     private AdminUserApi adminUserApi; | ||||||
|  |     @Resource | ||||||
|  |     private DeptApi deptApi; | ||||||
|  |  | ||||||
|     // ========== Query 查询相关方法 ========== |     // ========== Query 查询相关方法 ========== | ||||||
|  |  | ||||||
| @ -226,7 +235,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         // 1.1 校验当前任务 task 存在 |         // 1.1 校验当前任务 task 存在 | ||||||
|         Task task = validateTaskExist(id); |         Task task = validateTaskExist(id); | ||||||
|         // 1.2 根据流程定义获取流程模型信息 |         // 1.2 根据流程定义获取流程模型信息 | ||||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | ||||||
|         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | ||||||
|         if (source == null) { |         if (source == null) { | ||||||
|             throw exception(TASK_NOT_EXISTS); |             throw exception(TASK_NOT_EXISTS); | ||||||
| @ -496,12 +505,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 3. 根据不同的 RejectHandler 处理策略 |         // 3. 根据不同的 RejectHandler 处理策略 | ||||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); |         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); | ||||||
|         FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |         FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | ||||||
|         // 3.1 情况一:驳回到指定的任务节点 |         // 3.1 情况一:驳回到指定的任务节点 | ||||||
|         BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement); |         BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); | ||||||
|         if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { |         if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { | ||||||
|             String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement); |             String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); | ||||||
|             Assert.notNull(returnTaskId, "回退的节点不能为空"); |             Assert.notNull(returnTaskId, "回退的节点不能为空"); | ||||||
|             returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) |             returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) | ||||||
|                     .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); |                     .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); | ||||||
| @ -560,7 +569,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|      */ |      */ | ||||||
|     private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { |     private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { | ||||||
|         // 1.1 获取流程模型信息 |         // 1.1 获取流程模型信息 | ||||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); |         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); | ||||||
|         // 1.3 获取当前任务节点元素 |         // 1.3 获取当前任务节点元素 | ||||||
|         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); |         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); | ||||||
|         // 1.3 获取跳转的节点元素 |         // 1.3 获取跳转的节点元素 | ||||||
| @ -688,7 +697,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // 2. 终止流程 |         // 2. 终止流程 | ||||||
|         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); |         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); | ||||||
|         List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); |         List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); | ||||||
|         EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); |         EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); | ||||||
|         Assert.notNull(endEvent, "结束节点不能未空"); |         Assert.notNull(endEvent, "结束节点不能未空"); | ||||||
| @ -913,16 +922,110 @@ public class BpmTaskServiceImpl implements BpmTaskService { | |||||||
|             @Override |             @Override | ||||||
|             public void afterCommit() { |             public void afterCommit() { | ||||||
|                 if (StrUtil.isEmpty(task.getAssignee())) { |                 if (StrUtil.isEmpty(task.getAssignee())) { | ||||||
|  |                     log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId()); | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|                 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); |                 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); | ||||||
|                 if (processInstance != null) { |                 if (processInstance == null) { | ||||||
|  |                     log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // 审批人与提交人为同一人时,根据策略进行处理 | ||||||
|  |                 if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { | ||||||
|  |                     BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); | ||||||
|  |                     if (bpmnModel == null) { | ||||||
|  |                         log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | ||||||
|  |                     Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); | ||||||
|  |  | ||||||
|  |                     // 情况一:自动跳过 | ||||||
|  |                     if (ObjectUtils.equalsAny(assignStartUserHandlerType, | ||||||
|  |                             BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { | ||||||
|  |                         getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | ||||||
|  |                                 .setId(task.getId()).setReason("审批人与提交人为同一人时,自动通过")); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     // 情况二:转交给部门负责人审批 | ||||||
|  |                     if (ObjectUtils.equalsAny(assignStartUserHandlerType, | ||||||
|  |                             BpmUserTaskAssignStartUserHandlerTypeEnum.ASSIGN_DEPT_LEADER.getType())) { | ||||||
|  |                         AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | ||||||
|  |                         Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); | ||||||
|  |                         DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; | ||||||
|  |                         Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); | ||||||
|  |                         // 找不到部门负责人的情况下,自动审批通过 | ||||||
|  |                         // noinspection DataFlowIssue | ||||||
|  |                         if (dept.getLeaderUserId() == null) { | ||||||
|  |                             getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO() | ||||||
|  |                                     .setId(task.getId()).setReason("审批人与提交人为同一人时,找不到部门负责人,自动通过")); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                         // 找得到部门负责人的情况下,修改负责人 | ||||||
|  |                         if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { | ||||||
|  |                             getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() | ||||||
|  |                                     .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) | ||||||
|  |                                     .setReason("审批人与提交人为同一人时,转交给部门负责人审批")); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                         // 如果部门负责人是自己,还是自己审批吧~ | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); |                 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); | ||||||
|                 messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); |                 messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); | ||||||
|             } |             } | ||||||
|             } |  | ||||||
|  |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @Transactional(rollbackFor = Exception.class) | ||||||
|  |     public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction) { | ||||||
|  |         ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); | ||||||
|  |         if (processInstance == null) { | ||||||
|  |             log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey); | ||||||
|  |         // TODO 优化:未来需要考虑加签的情况 | ||||||
|  |         if (CollUtil.isEmpty(taskList)) { | ||||||
|  |             log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { | ||||||
|  |             // 情况一:自动提醒 | ||||||
|  |             if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REMINDER.getAction())) { | ||||||
|  |                 messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() | ||||||
|  |                         .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) | ||||||
|  |                         .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 情况二:自动同意 | ||||||
|  |             if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.APPROVE.getAction())) { | ||||||
|  |                 approveTask(Long.parseLong(task.getAssignee()), | ||||||
|  |                         new BpmTaskApproveReqVO().setId(task.getId()).setReason("超时系统自动同意")); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 情况三:自动拒绝 | ||||||
|  |             if (Objects.equals(taskAction, BpmUserTaskTimeoutHandlerType.REJECT.getAction())) { | ||||||
|  |                 rejectTask(Long.parseLong(task.getAssignee()), | ||||||
|  |                         new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝")); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获得自身的代理对象,解决 AOP 生效问题 | ||||||
|  |      * | ||||||
|  |      * @return 自己 | ||||||
|  |      */ | ||||||
|  |     private BpmTaskServiceImpl getSelf() { | ||||||
|  |         return SpringUtil.getBean(getClass()); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 jason
					jason