Browse Source

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

jason 11 months ago
parent
commit
bc46c67a99
22 changed files with 345 additions and 257 deletions
  1. 2 2
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
  2. 27 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java
  3. 1 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerType.java
  4. 2 1
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
  5. 8 11
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  6. 0 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
  7. 30 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
  8. 6 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
  9. 40 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
  10. 0 111
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java
  11. 0 42
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java
  12. 0 34
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java
  13. 0 27
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java
  14. 4 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
  15. 13 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
  16. 28 14
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  17. 8 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
  18. 11 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
  19. 41 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java
  20. 0 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
  21. 9 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  22. 115 12
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

+ 2 - 2
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;
 @AllArgsConstructor
 public enum BpmBoundaryEventType {
 
-    USER_TASK_TIMEOUT(1,"用户任务超时"),
-    USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理");
+    USER_TASK_TIMEOUT(1,"用户任务超时");
 
     private final Integer type;
     private final String name;
@@ -22,4 +21,5 @@ public enum BpmBoundaryEventType {
     public static BpmBoundaryEventType typeOf(Integer type) {
         return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
     }
+
 }

+ 27 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java

@@ -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];
+    }
+
+}

+ 1 - 0
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 {
     APPROVE(2, "自动同意"),
     REJECT(3, "自动拒绝");
 
+    // TODO @jason:type 是不是更合适哈;
     private final Integer action;
     private final String name;
 

+ 2 - 1
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 {
 
     PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
     PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
-    TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人
+    TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人
+    TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人
 
     /**
      * 短信模板的标识

+ 8 - 11
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java

@@ -1,10 +1,7 @@
 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
 
 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.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.enums.definition.*;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -84,6 +81,10 @@ public class BpmSimpleModelNodeVO {
      */
     private TimeoutHandler timeoutHandler;
 
+    @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
+    @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
+    private Integer assignStartUserHandlerType;
+
     @Data
     @Schema(description = "审批节点拒绝处理策略")
     public static class RejectHandler {
@@ -96,6 +97,7 @@ public class BpmSimpleModelNodeVO {
         private String returnNodeId;
     }
 
+    // TODO @芋艿:参数校验
     @Data
     @Schema(description = "审批节点超时处理策略")
     public static class TimeoutHandler {
@@ -103,6 +105,7 @@ public class BpmSimpleModelNodeVO {
         @Schema(description = "是否开启超时处理", example = "false")
         private Boolean enable;
 
+        // TODO @jason:type 是不是更合适哈;
         @Schema(description = "任务超时未处理的行为", example = "1")
         @InEnum(BpmUserTaskTimeoutHandlerType.class)
         private Integer action;
@@ -112,6 +115,7 @@ public class BpmSimpleModelNodeVO {
 
         @Schema(description = "最大提醒次数", example = "1")
         private Integer maxRemindCount;
+
     }
 
     @Data
@@ -129,14 +133,7 @@ public class BpmSimpleModelNodeVO {
     }
 
     // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用
-    // Integer approveMethod; 审批方式;仅审批节点会使用
-    // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级
-    // TODO @芋艿:① 审批人的选择;
     // TODO @芋艿:⑥ 没有人的策略?
-    // TODO @芋艿:② 审批拒绝的策略?
-    // TODO @芋艿:③ 配置的可操作列表?(操作权限)
-    // TODO @芋艿:④ 表单的权限列表?
-    // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔;
     // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持
 
 }

+ 0 - 1
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 {
         childTask.setParentTaskId(parentTask.getId());
         childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
         childTask.setProcessInstanceId(parentTask.getProcessInstanceId());
-//        childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错)
         childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
         childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId());
         childTask.setPriority(parentTask.getPriority());

+ 30 - 0
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;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
 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.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.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.dto.AdminUserRespDTO;
 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.UserTask;
 import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.runtime.ProcessInstance;
 
 import java.util.HashMap;
 import java.util.List;
@@ -86,6 +91,8 @@ public class BpmTaskCandidateInvoker {
         Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
         // 1.2 移除被禁用的用户
         removeDisableUsers(userIds);
+        // 1.3 移除发起人的用户
+        removeStartUserIfSkip(execution, userIds);
 
         // 2. 校验是否有候选人
         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) {
         BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
         Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);

+ 6 - 1
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 {
     String BOUNDARY_EVENT_TYPE = "boundaryEventType";
 
     // TODO @jason:这个命名,应该也要改哈
+    // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;
     /**
      * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
      */
     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 的扩展属性,用于标记用户任务拒绝处理类型
      */

+ 40 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java

@@ -1,17 +1,27 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 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.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.engine.delegate.event.FlowableActivityCancelledEvent;
 import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.job.api.Job;
 import org.flowable.task.api.Task;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
@@ -28,6 +38,9 @@ import java.util.Set;
 @Slf4j
 public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
 
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private BpmModelService modelService;
     @Resource
     @Lazy // 解决循环依赖
     private BpmTaskService taskService;
@@ -40,6 +53,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
             .add(FlowableEngineEventType.TASK_ASSIGNED)
 //            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
             .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
+            .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
             .build();
 
     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));
+    }
+
 }

+ 0 - 111
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java

@@ -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);
-                }
-            });
-        }
-    }
-}

+ 0 - 42
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java

@@ -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);
-        });
-    }
-}

+ 0 - 34
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java

@@ -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 暂时只有站内信通知. 后面可以增加
-}

+ 0 - 27
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java

@@ -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);
-    }
-
-}

+ 4 - 0
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 {
         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) {
         if (flowElement == null) {
             return null;

+ 13 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java

@@ -1,6 +1,8 @@
 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.util.TenantUtils;
 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.variable.VariableContainer;
@@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Flowable 相关的工具方法
@@ -39,6 +42,16 @@ public class FlowableUtils {
         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 相关的工具方法 ==========
 
     /**

+ 28 - 14
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;
 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.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.BpmSimpleModelNodeType;
 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.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.BpmUserTaskTimeoutHandlerType.REMINDER;
 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}";
 
-    // TODO-DONE @jason:建议方法名,改成 buildBpmnModel
     // TODO @yunai:注释需要完善下;
 
     /**
@@ -339,32 +338,41 @@ public class SimpleModelUtils {
         List<FlowElement> flowElements = new ArrayList<>();
         UserTask userTask = buildBpmnUserTask(node);
         flowElements.add(userTask);
+
+        // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
         if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
-            // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理
             BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, node.getTimeoutHandler());
             flowElements.add(boundaryEvent);
         }
         return flowElements;
     }
 
+    /**
+     * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
+     *
+     * @param userTask 审批任务
+     * @param timeoutHandler 超时处理器
+     * @return BoundaryEvent 超时事件
+     */
     private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
-        // 定时器边界事件
+        // 1.1 定时器边界事件
         BoundaryEvent boundaryEvent = new BoundaryEvent();
         boundaryEvent.setId("Event-" + IdUtil.fastUUID());
-        // 设置关联的任务为不会被中断
-        boundaryEvent.setCancelActivity(false);
+        boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
         boundaryEvent.setAttachedToRef(userTask);
+        // 1.2 定义超时时间、最大提醒次数
         TimerEventDefinition eventDefinition = new TimerEventDefinition();
         eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
         if (Objects.equals(REMINDER.getAction(), timeoutHandler.getAction()) &&
                 timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
-            // 最大提醒次数
-            eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
+            eventDefinition.setTimeCycle(String.format("R%d/%s",
+                    timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
         }
         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()));
         return boundaryEvent;
     }
@@ -448,8 +456,6 @@ public class SimpleModelUtils {
             userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
         }
 
-        // TODO 芋艿 + jason:要不要基于服务任务,实现或签下的审批不通过?或者说,按比例审批
-
         // TODO @jason:addCandidateElements、processMultiInstanceLoopCharacteristics 建议一起搞哈?
         // 添加候选人元素
         addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
@@ -461,10 +467,11 @@ public class SimpleModelUtils {
         processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
         // 添加任务被拒绝的处理元素
         addTaskRejectElements(node.getRejectHandler(), userTask);
+        // 添加用户任务的审批人与发起人相同时的处理元素
+        addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
         return userTask;
     }
 
-
     private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) {
         if (rejectHandler == null) {
             return;
@@ -473,6 +480,13 @@ public class SimpleModelUtils {
         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) {
         BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod);
         if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.RANDOM) {

+ 8 - 1
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;
 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.BpmMessageSendWhenTaskCreatedReqDTO;
-
+import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
 import jakarta.validation.Valid;
 
 /**
@@ -36,4 +36,11 @@ public interface BpmMessageService {
      */
     void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO);
 
+    /**
+     * 发送任务审批超时的消息
+     *
+     * @param reqDTO 发送信息
+     */
+    void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO);
+
 }

+ 11 - 0
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;
 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.BpmMessageSendWhenTaskCreatedReqDTO;
+import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
 import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -61,6 +62,16 @@ public class BpmMessageServiceImpl implements BpmMessageService {
                 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) {
         return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId;
     }

+ 41 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java

@@ -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 it is too large
+ 0 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java


+ 9 - 0
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 {
      */
     void processTaskAssigned(Task task);
 
+    /**
+     * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
+     *
+     * @param processInstanceId 流程示例编号
+     * @param taskDefineKey 任务 Key
+     * @param taskAction 处理类型
+     */
+    void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer taskAction);
+
 }

+ 115 - 12
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -1,15 +1,20 @@
 package cn.iocoder.yudao.module.bpm.service.task;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.*;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 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.object.ObjectUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 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.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.BpmUserTaskTimeoutHandlerType;
 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.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.service.definition.BpmModelService;
 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.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
@@ -44,7 +52,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.util.Assert;
 
 import java.util.*;
 import java.util.stream.Stream;
@@ -77,12 +84,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     @Resource
     private BpmProcessInstanceCopyService processInstanceCopyService;
     @Resource
-    private BpmModelService bpmModelService;
+    private BpmModelService modelService;
     @Resource
     private BpmMessageService messageService;
 
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
 
     // ========== Query 查询相关方法 ==========
 
@@ -226,7 +235,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         // 1.1 校验当前任务 task 存在
         Task task = validateTaskExist(id);
         // 1.2 根据流程定义获取流程模型信息
-        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
+        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
         if (source == null) {
             throw exception(TASK_NOT_EXISTS);
@@ -496,12 +505,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         }
 
         // 3. 根据不同的 RejectHandler 处理策略
-        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
-        FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
+        FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
         // 3.1 情况一:驳回到指定的任务节点
-        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement);
+        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement);
         if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
-            String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement);
+            String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement);
             Assert.notNull(returnTaskId, "回退的节点不能为空");
             returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
                     .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
@@ -560,7 +569,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
      */
     private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
         // 1.1 获取流程模型信息
-        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
+        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
         // 1.3 获取当前任务节点元素
         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
         // 1.3 获取跳转的节点元素
@@ -688,7 +697,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         });
 
         // 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));
         EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
         Assert.notNull(endEvent, "结束节点不能未空");
@@ -913,16 +922,110 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             @Override
             public void afterCommit() {
                 if (StrUtil.isEmpty(task.getAssignee())) {
+                    log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId());
                     return;
                 }
                 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
-                if (processInstance != null) {
-                    AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
-                    messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
+                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()));
+                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());
+    }
+
 }

Some files were not shown because too many files changed in this diff