소스 검색

仿钉钉流程设计- 审批节点添加拒绝处理方式

jason 1 년 전
부모
커밋
d2750f08ce
10개의 변경된 파일254개의 추가작업 그리고 48개의 파일을 삭제
  1. 5 4
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java
  2. 25 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
  3. 13 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
  4. 72 4
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
  5. 9 14
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java
  6. 18 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java
  7. 29 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
  8. 38 7
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  9. 5 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  10. 40 14
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

+ 5 - 4
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java → yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java

@@ -5,20 +5,21 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 /**
- * 定时器边界事件类型枚举
+ * BPM 边界事件 (boundary event) 自定义类型枚举
  *
  * @author jason
  */
 @Getter
 @AllArgsConstructor
-public enum BpmTimerBoundaryEventType {
+public enum BpmBoundaryEventType {
 
-    USER_TASK_TIMEOUT(1,"用户任务超时");
+    USER_TASK_TIMEOUT(1,"用户任务超时"),
+    USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理");
 
     private final Integer type;
     private final String name;
 
-    public static BpmTimerBoundaryEventType typeOf(Integer type) {
+    public static BpmBoundaryEventType typeOf(Integer type) {
         return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
     }
 }

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

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BPM 用户任务拒绝处理类型枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmUserTaskRejectHandlerType {
+
+    TERMINATION(1, "终止流程"),
+    RETURN_PRE_USER_TASK(2, "驳回到用户任务");
+
+    private final Integer type;
+    private final String name;
+
+    public static BpmUserTaskRejectHandlerType typeOf(Integer type) {
+        return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+    }
+}

+ 13 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java

@@ -30,15 +30,25 @@ public interface BpmnModelConstants {
      */
     String USER_TASK_CANDIDATE_PARAM = "candidateParam";
 
+    /**
+     * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型
+     */
+    String BOUNDARY_EVENT_TYPE = "boundaryEventType";
+
     /**
      * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
      */
     String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction";
 
     /**
-     * BPMN ExtensionElement 的扩展属性,用于标记定时边界事件类型
+     * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型
+     */
+    String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
+
+    /**
+     * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id
      */
-    String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType";
+    String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
 
     /**
      * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
@@ -66,4 +76,5 @@ public interface BpmnModelConstants {
      */
     Set<Class<? extends FlowNode>> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class);
 
+    String REJECT_POST_PROCESS_MESSAGE_NAME = "message_reject_post_process";
 }

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

@@ -2,23 +2,41 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+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.framework.flowable.core.util.FlowableUtils;
+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.BpmProcessInstanceService;
 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.delegate.event.FlowableMessageEvent;
 import org.flowable.engine.history.HistoricActivityInstance;
 import org.flowable.task.api.Task;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
-import jakarta.annotation.Resource;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS;
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseBoundaryEventExtensionElement;
+
 /**
  * 监听 {@link Task} 的开始与完成
  *
@@ -34,15 +52,22 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
     @Resource
     @Lazy // 解决循环依赖
     private BpmActivityService activityService;
+    @Resource
+    @Lazy // 解决循环依赖
+    private BpmProcessInstanceService processInstanceService;
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private BpmModelService bpmModelService;
 
     public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
             .add(FlowableEngineEventType.TASK_CREATED)
             .add(FlowableEngineEventType.TASK_ASSIGNED)
-//            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
+            //.add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
+            .add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED)
             .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
             .build();
 
-    public BpmTaskEventListener(){
+    public BpmTaskEventListener() {
         super(TASK_EVENTS);
     }
 
@@ -53,7 +78,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
 
     @Override
     protected void taskAssigned(FlowableEngineEntityEvent event) {
-        taskService.updateTaskExtAssign((Task)event.getEntity());
+        taskService.updateTaskExtAssign((Task) event.getEntity());
     }
 
     @Override
@@ -72,4 +97,47 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
         });
     }
 
+    @Override
+    protected void activityMessageReceived(FlowableMessageEvent event) {
+        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId());
+        FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId());
+        if (element instanceof BoundaryEvent) {
+            BoundaryEvent boundaryEvent = (BoundaryEvent) element;
+            String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
+            // 如果自定义类型为拒绝后处理,进行拒绝处理
+            if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) {
+                String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE);
+                rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType));
+            }
+        }
+    }
+
+    private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) {
+        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
+        if (userTaskRejectHandlerType != null) {
+            List<Task> taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey);
+            taskList.forEach(task -> {
+                Integer taskStatus = FlowableUtils.getTaskStatus(task);
+                // 只有处于拒绝状态下才处理
+                if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) {
+                    // 终止流程
+                    if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) {
+                        processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task));
+                        return;
+                    }
+                    // 驳回
+                    if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
+                        String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID);
+                        if (returnTaskId != null) {
+                            BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId())
+                                    .setTargetTaskDefinitionKey(returnTaskId)
+                                    .setReason("任务拒绝回退");
+                            taskService.returnTask(getLoginUserId(), reqVO);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
 }

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

@@ -1,11 +1,10 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
 
-import cn.hutool.core.collection.CollUtil;
 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.BpmTimerBoundaryEventType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;
@@ -18,7 +17,6 @@ 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.ExtensionElement;
 import org.flowable.bpmn.model.FlowElement;
 import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
 import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
@@ -29,7 +27,6 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -69,23 +66,21 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe
         // 如果是定时器边界事件
         if (element instanceof BoundaryEvent) {
             BoundaryEvent boundaryEvent = (BoundaryEvent) element;
-            ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE));
-            Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null));
-            BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType);
+            String  boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
+            BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
             // 类型为用户任务超时未处理的情况
-            if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) {
-                ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION));
-                Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null));
-                processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction);
+            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 processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) {
+    private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) {
         BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction);
         if (userTaskTimeoutAction != null) {
-            // 查询超时未处理的任务
-            List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey);
+            // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ???
+            List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefKey);
             taskList.forEach(task -> {
                 // 自动提醒
                 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) {

+ 18 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple;
 
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
 import lombok.Data;
 
 import java.util.List;
@@ -33,12 +34,15 @@ public class SimpleModelUserTaskConfig {
      */
     private  Integer approveMethod;
 
-
     /**
      * 超时处理
      */
     private TimeoutHandler timeoutHandler;
 
+    /**
+     * 用户任务拒绝处理
+     */
+    private RejectHandler rejectHandler;
 
     @Data
     public static class TimeoutHandler {
@@ -62,7 +66,20 @@ public class SimpleModelUserTaskConfig {
          * 如果执行动作是自动提醒, 最大提醒次数
          */
         private Integer maxRemindCount;
+    }
+
+    @Data
+    public static class RejectHandler {
 
+        /**
+         * 用户任务拒绝处理类型 {@link BpmUserTaskRejectHandlerType}
+         */
+        private Integer type;
+
+        /**
+         * 用户任务拒绝后驳回的节点 Id
+         */
+        private String  returnNodeId;
     }
 
 }

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

@@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
 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 org.flowable.bpmn.converter.BpmnXMLConverter;
 import org.flowable.bpmn.model.Process;
@@ -360,4 +361,32 @@ public class BpmnModelUtils {
         return userTaskList;
     }
 
+    /**
+     * 在用户任务中查找自定义的边界事件
+     *
+     * @param userTask 用户任务
+     * @param bpmBoundaryEventType 自定义的边界事件类型
+     */
+    public static BoundaryEvent findCustomBoundaryEventOfUserTask(UserTask userTask, BpmBoundaryEventType bpmBoundaryEventType) {
+        if (userTask == null) {
+            return null;
+        }
+        BoundaryEvent result = null;
+        for (BoundaryEvent item : userTask.getBoundaryEvents()) {
+            String boundaryEventType = parseBoundaryEventExtensionElement(item, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
+            if (Objects.equals(bpmBoundaryEventType.getType(), NumberUtils.parseInt(boundaryEventType))) {
+                result = item;
+                break;
+            }
+        }
+        return result;
+    }
+
+    public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
+        if (boundaryEvent == null) {
+            return null;
+        }
+        ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
+        return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
+    }
 }

+ 38 - 7
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -14,6 +14,7 @@ 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.simple.SimpleModelConditionGroups;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig.RejectHandler;
 import org.flowable.bpmn.BpmnAutoLayout;
 import org.flowable.bpmn.model.Process;
 import org.flowable.bpmn.model.*;
@@ -22,13 +23,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT;
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT;
-import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK;
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
-import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
-import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
+import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
 
 /**
  * 仿钉钉快搭模型相关的工具方法
@@ -42,12 +44,12 @@ public class SimpleModelUtils {
     /**
      * 所有审批人同意的表达式
      */
-    public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= 0 }";
+    public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }";
 
     /**
      * 任一一名审批人同意的表达式
      */
-    public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }";
+    public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
 
     /**
      * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
@@ -59,6 +61,12 @@ public class SimpleModelUtils {
      */
     public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
         BpmnModel bpmnModel = new BpmnModel();
+        // 不加这个 解析 Message 会报 NPE 异常
+        bpmnModel.setTargetNamespace(BPMN2_NAMESPACE);
+        Message rejectPostProcessMsg = new Message();
+        rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME);
+        bpmnModel.addMessage(rejectPostProcessMsg);
+
         Process mainProcess = new Process();
         mainProcess.setId(processId);
         mainProcess.setName(processName);
@@ -214,6 +222,12 @@ public class SimpleModelUtils {
                     BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
                     mainProcess.addFlowElement(boundaryEvent);
                 }
+                if (userTaskConfig.getRejectHandler() != null) {
+                    // 添加用户任务拒绝 Message Boundary Event, 用于任务的拒绝处理
+                    BoundaryEvent boundaryEvent = buildUserTaskRejectBoundaryEvent(userTask, userTaskConfig.getRejectHandler());
+                    mainProcess.addFlowElement(boundaryEvent);
+                }
+
                 break;
             }
             case COPY_TASK: {
@@ -270,10 +284,27 @@ public class SimpleModelUtils {
         }
     }
 
+    private static BoundaryEvent buildUserTaskRejectBoundaryEvent(UserTask userTask, RejectHandler rejectHandler) {
+        BoundaryEvent messageBoundaryEvent = new BoundaryEvent();
+        messageBoundaryEvent.setId("Event-" + IdUtil.fastUUID());
+        // 设置关联的任务为不会被中断
+        messageBoundaryEvent.setCancelActivity(false);
+        messageBoundaryEvent.setAttachedToRef(userTask);
+        MessageEventDefinition messageEventDefinition = new MessageEventDefinition();
+        messageEventDefinition.setMessageRef(REJECT_POST_PROCESS_MESSAGE_NAME);
+        messageBoundaryEvent.addEventDefinition(messageEventDefinition);
+        addExtensionElement(messageBoundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_REJECT_POST_PROCESS.getType().toString());
+        addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
+        if (Objects.equals(rejectHandler.getType(), RETURN_PRE_USER_TASK.getType())) {
+            addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
+        }
+        return messageBoundaryEvent;
+    }
+
     private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
         // 定时器边界事件
         BoundaryEvent boundaryEvent = new BoundaryEvent();
-        boundaryEvent.setId(IdUtil.fastUUID());
+        boundaryEvent.setId("Event-" + IdUtil.fastUUID());
         // 设置关联的任务为不会被中断
         boundaryEvent.setCancelActivity(false);
         boundaryEvent.setAttachedToRef(userTask);
@@ -286,7 +317,7 @@ public class SimpleModelUtils {
         }
         boundaryEvent.addEventDefinition(eventDefinition);
         // 添加定时器边界事件类型
-        addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
+        addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
         // 添加超时执行动作元素
         addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction()));
         return boundaryEvent;

+ 5 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java

@@ -90,6 +90,8 @@ public interface BpmTaskService {
      */
     void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO);
 
+
+
     /**
      * 将流程任务分配给指定用户
      *
@@ -129,10 +131,11 @@ public interface BpmTaskService {
 
     /**
      * 根据条件查询已经分配的用户任务列表
-     * @param processInstanceId 流程实例编号
+     * @param processInstanceId 流程实例编号,不允许为空
+     * @param executionId execution Id
      * @param taskDefineKey 任务定义 Key
      */
-    List<Task> getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey);
+    List<Task> getAssignedTaskListByConditions(String processInstanceId, String executionId, String taskDefineKey);
 
     /**
      * 获取当前任务的可回退的 UserTask 集合

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

@@ -9,16 +9,18 @@ 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.PageUtils;
-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.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.BpmBoundaryEventType;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
+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.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.system.api.user.AdminUserApi;
@@ -26,6 +28,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.BoundaryEvent;
 import org.flowable.bpmn.model.BpmnModel;
 import org.flowable.bpmn.model.FlowElement;
 import org.flowable.bpmn.model.UserTask;
@@ -33,6 +36,7 @@ import org.flowable.engine.HistoryService;
 import org.flowable.engine.ManagementService;
 import org.flowable.engine.RuntimeService;
 import org.flowable.engine.TaskService;
+import org.flowable.engine.runtime.Execution;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.flowable.task.api.DelegationState;
 import org.flowable.task.api.Task;
@@ -245,7 +249,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
     /**
      * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务:
-     *
+     * <p>
      * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批
      * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批
      *
@@ -278,7 +282,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             taskService.resolveTask(parentTaskId);
             // 3.1.2 更新流程任务 status
             updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus());
-        // 3.2 情况二:处理向【向后】加签
+            // 3.2 情况二:处理向【向后】加签
         } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) {
             // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成
             // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题
@@ -333,14 +337,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
                 BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
 
-        // 3. 更新流程实例,审批不通过!
+        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
+        FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+        // 寻找用户任务的自定义拒绝后处理边界事件
+        BoundaryEvent rejectBoundaryEvent = BpmnModelUtils.findCustomBoundaryEventOfUserTask((UserTask) flowElement,
+                BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS);
+
+        if (rejectBoundaryEvent != null) {
+            Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId())
+                    .activityId(rejectBoundaryEvent.getId()).singleResult();
+            if (execution != null) {
+                // 3.1 触发消息边界事件. 进一步的处理交给 BpmTaskEventListener
+                runtimeService.messageEventReceived(BpmnModelConstants.REJECT_POST_PROCESS_MESSAGE_NAME, execution.getId());
+                return;
+            }
+        }
+        // 3.2 没有找到拒绝后处理边界事件, 更新流程实例,审批不通过!
         processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason());
     }
 
     /**
      * 更新流程任务的 status 状态
      *
-     * @param id    任务编号
+     * @param id     任务编号
      * @param status 状态
      */
     private void updateTaskStatus(String id, Integer status) {
@@ -350,7 +369,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     /**
      * 更新流程任务的 status 状态、reason 理由
      *
-     * @param id 任务编号
+     * @param id     任务编号
      * @param status 状态
      * @param reason 理由(审批通过、审批不通过的理由)
      */
@@ -434,9 +453,16 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     }
 
     @Override
-    public List<Task> getAssignedTaskListByConditions(String processInstanceId, String defineKey) {
-        TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId)
-                .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables();
+    public List<Task> getAssignedTaskListByConditions(String processInstanceId, String executionId, String defineKey) {
+        Assert.notNull(processInstanceId, "processInstanceId 不能为空");
+        TaskQuery taskQuery = taskService.createTaskQuery().taskAssigned().processInstanceId(processInstanceId).active()
+                .includeTaskLocalVariables();
+        if (StrUtil.isNotEmpty(executionId)) {
+            taskQuery.executionId(executionId);
+        }
+        if (StrUtil.isNotEmpty(defineKey)) {
+            taskQuery.taskDefinitionKey(defineKey);
+        }
         return taskQuery.list();
     }
 
@@ -664,7 +690,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner
                 Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
         if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) {
-            List<AdminUserRespDTO> userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds()));
+            List<AdminUserRespDTO> userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds()));
             throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname)));
         }
         return taskEntity;
@@ -673,8 +699,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     /**
      * 创建加签子任务
      *
-     * @param userIds 被加签的用户 ID
-     * @param taskEntity        被加签的任务
+     * @param userIds    被加签的用户 ID
+     * @param taskEntity 被加签的任务
      */
     private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) {
         if (CollUtil.isEmpty(userIds)) {
@@ -703,7 +729,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         // 2.1 向前加签,设置审批人
         if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) {
             task.setAssignee(assignee);
-        // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
+            // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
         } else {
             task.setOwner(assignee);
         }