diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java
index aa41fdc95..e017d0e09 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java
@@ -1,17 +1,25 @@
package com.pig4cloud.pigx.flow.support.tasks;
+import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pigx.flow.constant.NodeStatusEnum;
import com.pig4cloud.pigx.flow.constant.ProcessInstanceConstant;
import com.pig4cloud.pigx.flow.dto.Node;
import com.pig4cloud.pigx.flow.dto.Refuse;
+import com.pig4cloud.pigx.flow.entity.ProcessNodeRecord;
+import com.pig4cloud.pigx.flow.entity.ProcessNodeRecordAssignUser;
import com.pig4cloud.pigx.flow.service.IProcessNodeDataService;
+import com.pig4cloud.pigx.flow.service.IProcessNodeRecordService;
+import com.pig4cloud.pigx.flow.service.IProcessNodeRecordAssignUserService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.Execution;
+import java.util.Date;
import java.util.List;
/**
@@ -68,7 +76,8 @@ public class ApproveServiceTask implements JavaDelegate {
/**
* 处理任务被拒绝时的流程重定向逻辑
*
- * 根据节点配置的拒绝策略,将流程重定向到指定节点或结束节点。 支持的拒绝策略: 1. TO_NODE:跳转到指定节点 2. 其他:直接结束流程
+ * 根据节点配置跳转到指定节点、发起人或结束节点。TO_START 会收回实例全部执行流,并结束被收回节点的本轮审批记录;
+ * 与引擎状态迁移共用当前审批事务,不覆盖已经完成的审批历史。
* @param processDefinitionId 流程定义 ID
* @param nodeId 当前节点标识符
* @param currentActivityId 当前活动标识符
@@ -97,17 +106,36 @@ public class ApproveServiceTask implements JavaDelegate {
// 根据拒绝设置重定向到特定节点
stateChangeBuilder.moveActivityIdTo(currentActivityId, refuseConfig.getNodeId()).changeState();
}
- else if (StrUtil.equals(rejectionHandler, "TO_START")) {
- // 驳回到发起人:设置驳回标识变量,跳转到 root 节点
- // 并行、多选分支内驳回时,兄弟分支执行流需一并收回,否则重新提交后汇聚网关会多出令牌
- runtimeService.setVariable(processInstanceId, "rejectToStarter", true);
- List executionIds = runtimeService.createExecutionQuery()
- .parentId(processInstanceId)
- .list()
+ else if (StrUtil.equals(rejectionHandler, ProcessInstanceConstant.USER_TASK_REFUSE_TYPE_TO_START)) {
+ // 多实例结束后当前执行流尚未落库,必须使用包含命令缓存的执行树,并排除已删除的旧 scope。
+ // 按活动迁移会合并为一个 root;按执行流迁移会为每条分支各建一个 root 待办。
+ List activityIds = CommandContextUtil.getExecutionEntityManager()
+ .findChildExecutionsByProcessInstanceId(processInstanceId)
.stream()
- .map(Execution::getId)
+ .filter(child -> !child.isDeleted() && StrUtil.equals(child.getParentId(), processInstanceId))
+ .map(Execution::getActivityId)
+ .distinct()
.toList();
- stateChangeBuilder.moveExecutionsToSingleActivityId(executionIds, "root").changeState();
+ runtimeService.setVariable(processInstanceId, ProcessInstanceConstant.REJECT_TO_STARTER_VAR, true);
+
+ // 先结束本轮活动记录,避免重提复用被收回的节点及参与人;迁移新建的 root 不应被关闭。
+ Date now = new Date();
+ SpringUtil.getBean(IProcessNodeRecordService.class)
+ .lambdaUpdate()
+ .set(ProcessNodeRecord::getStatus, NodeStatusEnum.YJS.getCode())
+ .set(ProcessNodeRecord::getEndTime, now)
+ .eq(ProcessNodeRecord::getProcessInstanceId, processInstanceId)
+ .eq(ProcessNodeRecord::getStatus, NodeStatusEnum.JXZ.getCode())
+ .update(new ProcessNodeRecord());
+ SpringUtil.getBean(IProcessNodeRecordAssignUserService.class)
+ .lambdaUpdate()
+ .set(ProcessNodeRecordAssignUser::getStatus, ProcessInstanceConstant.ASSIGN_USER_STATUS_WITHDRAW)
+ .set(ProcessNodeRecordAssignUser::getEndTime, Convert.toLocalDateTime(now))
+ .set(ProcessNodeRecordAssignUser::getTaskType, "WITHDRAW")
+ .eq(ProcessNodeRecordAssignUser::getProcessInstanceId, processInstanceId)
+ .eq(ProcessNodeRecordAssignUser::getStatus, NodeStatusEnum.JXZ.getCode())
+ .update(new ProcessNodeRecordAssignUser());
+ stateChangeBuilder.moveActivityIdsToSingleActivityId(activityIds, "root").changeState();
}
else {
// 默认行为:重定向到结束节点
diff --git a/pigx-flow/pigx-flow-biz/src/test/java/com/pig4cloud/pigx/flow/support/utils/ModelUtilMultiMatchTests.java b/pigx-flow/pigx-flow-biz/src/test/java/com/pig4cloud/pigx/flow/support/utils/ModelUtilMultiMatchTests.java
index 8c101f98f..633594297 100644
--- a/pigx-flow/pigx-flow-biz/src/test/java/com/pig4cloud/pigx/flow/support/utils/ModelUtilMultiMatchTests.java
+++ b/pigx-flow/pigx-flow-biz/src/test/java/com/pig4cloud/pigx/flow/support/utils/ModelUtilMultiMatchTests.java
@@ -1,10 +1,15 @@
package com.pig4cloud.pigx.flow.support.utils;
import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
+import com.pig4cloud.pigx.flow.entity.ProcessNodeRecord;
+import com.pig4cloud.pigx.flow.entity.ProcessNodeRecordAssignUser;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.flow.dto.Node;
import com.pig4cloud.pigx.flow.dto.Refuse;
import com.pig4cloud.pigx.flow.service.IProcessNodeDataService;
+import com.pig4cloud.pigx.flow.service.IProcessNodeRecordService;
+import com.pig4cloud.pigx.flow.service.IProcessNodeRecordAssignUserService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExclusiveGateway;
import org.flowable.bpmn.model.InclusiveGateway;
@@ -17,6 +22,7 @@ import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@@ -25,6 +31,7 @@ import org.flowable.spring.SpringProcessEngineConfiguration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -65,6 +72,18 @@ class ModelUtilMultiMatchTests {
context = new GenericApplicationContext();
context.getBeanFactory().registerSingleton("runtimeService", engine.getRuntimeService());
context.getBeanFactory().registerSingleton("processNodeDataService", nodes);
+ // set/eq 泛型返回值擦除为 Object,RETURNS_SELF 不会返回自身;链式调用返回 mock,终结的 update 返回 true
+ Answer