diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java
index 2ab6f372c..73bad9241 100644
--- a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java
+++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java
@@ -60,6 +60,36 @@ public class ProcessInstanceConstant {
*/
public static final String REJECT_TO_STARTER_VAR = "rejectToStarter";
+ /**
+ * 流程结束原因:拒绝
+ */
+ public static final String FINISH_REASON_REFUSE = "0";
+
+ /**
+ * 流程结束原因:通过
+ */
+ public static final String FINISH_REASON_PASS = "1";
+
+ /**
+ * 流程结束原因:撤回
+ */
+ public static final String FINISH_REASON_WITHDRAW = "3";
+
+ /**
+ * 流程结束原因:终止
+ */
+ public static final String FINISH_REASON_TERMINATE = "9";
+
+ /**
+ * 流程节点执行人状态:撤回
+ */
+ public static final int ASSIGN_USER_STATUS_WITHDRAW = 3;
+
+ /**
+ * 流程节点执行人状态:终止
+ */
+ public static final int ASSIGN_USER_STATUS_TERMINATE = 5;
+
/**
* 多人审批模式:会签 所有审批人都必须同意才能通过,任何一人拒绝则不通过
*/
diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java
index 2d7057c62..361bc7a9c 100644
--- a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java
+++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java
@@ -44,4 +44,14 @@ public class ProcessInstanceRecordVo extends ProcessInstanceRecord {
*/
private Boolean hasPrintTemplate;
+ /**
+ * 是否允许撤回
+ */
+ private Boolean canWithdraw;
+
+ /**
+ * 不可撤回原因
+ */
+ private String withdrawDisabledReason;
+
}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java
index 94f0116d7..efc88cf45 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java
@@ -144,6 +144,17 @@ public class ProcessTaskController {
}
+ /**
+ * 撤回流程实例
+ *
+ * @param taskParamDto 撤回参数,包含流程实例ID
+ * @return R 统一响应对象,操作成功返回撤回信息
+ */
+ @PostMapping("withdrawProcessInstance")
+ public R withdrawProcessInstance(@RequestBody TaskParamDto taskParamDto) {
+ return taskService.withdrawProcessInstance(taskParamDto);
+ }
+
/**
* 退回任务
*
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java
index b15de35a5..982b7f308 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java
@@ -87,6 +87,14 @@ public interface ITaskService {
*/
R stopProcessInstance(TaskParamDto taskParamDto);
+ /**
+ * 撤回流程实例
+ *
+ * @param taskParamDto 任务参数,包含流程实例ID
+ * @return 操作结果
+ */
+ R withdrawProcessInstance(TaskParamDto taskParamDto);
+
/**
* 退回任务
*
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java
index 1b8331bf0..d9a3c917d 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java
@@ -18,11 +18,10 @@ import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import com.pig4cloud.pigx.flow.constant.NodeStatusEnum;
import com.pig4cloud.pigx.flow.constant.NodeUserTypeEnum;
+import com.pig4cloud.pigx.flow.constant.ProcessInstanceConstant;
import com.pig4cloud.pigx.flow.dto.*;
+import com.pig4cloud.pigx.flow.entity.*;
import com.pig4cloud.pigx.flow.entity.Process;
-import com.pig4cloud.pigx.flow.entity.ProcessCopy;
-import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord;
-import com.pig4cloud.pigx.flow.entity.ProcessNodeRecord;
import com.pig4cloud.pigx.flow.service.*;
import com.pig4cloud.pigx.flow.support.utils.NodeFormatUtil;
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
@@ -42,6 +41,7 @@ import org.flowable.engine.history.HistoricActivityInstanceQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
+import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.stereotype.Service;
import java.time.ZoneId;
@@ -82,6 +82,8 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
private final IProcessNodeRecordService processNodeRecordService;
+ private final IProcessNodeRecordAssignUserService processNodeRecordAssignUserService;
+
private final ObjectMapper objectMapper;
/**
@@ -374,36 +376,59 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
.collect(Collectors.toSet());
List processList = CollUtil.isEmpty(flowIds) ? Collections.emptyList()
: processService.lambdaQuery().in(Process::getFlowId, flowIds).list();
+ Set processInstanceIds = voList.stream()
+ .map(ProcessInstanceRecordVo::getProcessInstanceId)
+ .filter(StrUtil::isNotBlank)
+ .collect(Collectors.toSet());
+ Map> assignUserMap = queryAssignUserMap(processInstanceIds);
+
+ // 批量查询进行中流程的 rejectToStarter 变量,避免 N+1
+ Set runningInstanceIds = voList.stream()
+ .filter(vo -> Objects.equals(vo.getStatus(), NodeStatusEnum.JXZ.getCode()))
+ .map(ProcessInstanceRecordVo::getProcessInstanceId)
+ .filter(StrUtil::isNotBlank)
+ .collect(Collectors.toSet());
+
+ Set rejectToStarterIds = batchQueryRejectToStarter(runningInstanceIds);
+
+ // 批量查询驳回待提交的 root 任务,避免 N+1
+ Map resubmitTaskMap = Collections.emptyMap();
+ if (!rejectToStarterIds.isEmpty()) {
+ resubmitTaskMap = taskService.createTaskQuery()
+ .processInstanceIdIn(rejectToStarterIds)
+ .taskDefinitionKey("root")
+ .list()
+ .stream()
+ .collect(Collectors.toMap(Task::getProcessInstanceId, Task::getId, (a, b) -> a));
+ }
+
+ // 构建打印模板 Map,避免循环内 stream 遍历
+ Map processMap = processList.stream()
+ .collect(Collectors.toMap(p -> p.getFlowId().toLowerCase(), p -> p, (a, b) -> a));
for (ProcessInstanceRecordVo vo : voList) {
- try {
- // 从 Flowable 变量中获取 rejectToStarter 状态
- Object rejectVar = runtimeService.getVariable(
- vo.getProcessInstanceId(),
- "rejectToStarter"
- );
- vo.setRejectToStarter(Boolean.TRUE.equals(rejectVar));
+ boolean rejectToStarter = false;
+ if (Objects.equals(vo.getStatus(), NodeStatusEnum.JXZ.getCode())) {
+ rejectToStarter = rejectToStarterIds.contains(vo.getProcessInstanceId());
+ vo.setRejectToStarter(rejectToStarter);
- // 如果是驳回状态,获取 root 任务 ID 用于重新提交
- if (Boolean.TRUE.equals(vo.getRejectToStarter())) {
- Task task = taskService.createTaskQuery()
- .processInstanceId(vo.getProcessInstanceId())
- .taskDefinitionKey("root")
- .singleResult();
- if (task != null) {
- vo.setResubmitTaskId(task.getId());
- }
+ if (rejectToStarter) {
+ vo.setResubmitTaskId(resubmitTaskMap.get(vo.getProcessInstanceId()));
}
- } catch (Exception e) {
- // 流程可能已结束,设置为 false
+ } else {
vo.setRejectToStarter(false);
}
- // 检查是否配置了打印模板(不依赖运行时变量,无需放在 try-catch 内)
- processList.stream()
- .filter(process -> process.getFlowId().equalsIgnoreCase(vo.getFlowId()))
- .findFirst()
- .ifPresent(process -> vo.setHasPrintTemplate(StrUtil.isNotBlank(process.getPrintTemplate())));
+ WithdrawState withdrawState = buildWithdrawState(vo, vo.getRejectToStarter(),
+ assignUserMap.getOrDefault(vo.getProcessInstanceId(), Collections.emptyList()));
+ vo.setCanWithdraw(withdrawState.canWithdraw);
+ vo.setWithdrawDisabledReason(withdrawState.disabledReason);
+
+ // 检查是否配置了打印模板
+ Process matchedProcess = processMap.get(vo.getFlowId().toLowerCase());
+ if (matchedProcess != null) {
+ vo.setHasPrintTemplate(StrUtil.isNotBlank(matchedProcess.getPrintTemplate()));
+ }
}
// 构建返回的分页对象
@@ -549,10 +574,128 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
Node nodeDto = objectMapper.readValue(process, Node.class);
Map formPerms1 = nodeDto.getFormPerms();
+ boolean rejectToStarter = Objects.equals(processInstanceRecord.getStatus(), NodeStatusEnum.JXZ.getCode())
+ && isRejectToStarter(processInstanceId);
+ WithdrawState withdrawState = buildWithdrawState(processInstanceRecord, rejectToStarter,
+ queryAssignUserMap(Collections.singleton(processInstanceId))
+ .getOrDefault(processInstanceId, Collections.emptyList()));
- Dict set = Dict.create().set(ProcessInstanceRecord.Fields.processInstanceId, processInstanceId).set(ProcessInstanceRecord.Fields.status, processInstanceRecord.getStatus()).set("process", oaForms.getProcess()).set("formItems", oaForms.getFormItems()).set("formData", formData).set("formConfig", oaForms.getFormConfig()).set("formPerms", formPerms1);
+ Dict set = Dict.create()
+ .set(ProcessInstanceRecord.Fields.processInstanceId, processInstanceId)
+ .set(ProcessInstanceRecord.Fields.status, processInstanceRecord.getStatus())
+ .set("finishReason", processInstanceRecord.getFinishReason())
+ .set("rejectToStarter", rejectToStarter)
+ .set("canWithdraw", withdrawState.canWithdraw)
+ .set("withdrawDisabledReason", withdrawState.disabledReason)
+ .set("process", oaForms.getProcess())
+ .set("formItems", oaForms.getFormItems())
+ .set("formData", formData)
+ .set("formConfig", oaForms.getFormConfig())
+ .set("formPerms", formPerms1);
return R.ok(set);
}
+ private Map> queryAssignUserMap(Set processInstanceIds) {
+ if (CollUtil.isEmpty(processInstanceIds)) {
+ return Collections.emptyMap();
+ }
+
+ return processNodeRecordAssignUserService.lambdaQuery()
+ .in(ProcessNodeRecordAssignUser::getProcessInstanceId, processInstanceIds)
+ .ne(ProcessNodeRecordAssignUser::getNodeId, "root")
+ .list()
+ .stream()
+ .collect(Collectors.groupingBy(ProcessNodeRecordAssignUser::getProcessInstanceId));
+ }
+
+ private boolean isRejectToStarter(String processInstanceId) {
+ try {
+ Object rejectVar = runtimeService.getVariable(processInstanceId,
+ ProcessInstanceConstant.REJECT_TO_STARTER_VAR);
+ return Boolean.TRUE.equals(rejectVar);
+ } catch (Exception e) {
+ log.debug("查询驳回待提交状态失败: processInstanceId={}, error={}", processInstanceId, e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * 批量查询流程实例的驳回到发起人状态
+ *
+ * @param processInstanceIds 流程实例ID集合
+ * @return 处于驳回待提交状态的流程实例ID集合
+ */
+ private Set batchQueryRejectToStarter(Set processInstanceIds) {
+ if (CollUtil.isEmpty(processInstanceIds)) {
+ return Collections.emptySet();
+ }
+ try {
+ return historyService.createHistoricVariableInstanceQuery()
+ .processInstanceIds(processInstanceIds)
+ .variableName(ProcessInstanceConstant.REJECT_TO_STARTER_VAR)
+ .list()
+ .stream()
+ .filter(v -> Boolean.TRUE.equals(v.getValue()))
+ .map(HistoricVariableInstance::getProcessInstanceId)
+ .collect(Collectors.toSet());
+ } catch (Exception e) {
+ log.warn("批量查询驳回状态失败,降级为逐条查询: {}", e.getMessage());
+ return processInstanceIds.stream()
+ .filter(this::isRejectToStarter)
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private WithdrawState buildWithdrawState(ProcessInstanceRecord processInstanceRecord, Boolean rejectToStarter,
+ List assignUsers) {
+ if (!Objects.equals(processInstanceRecord.getStatus(), NodeStatusEnum.JXZ.getCode())) {
+ return new WithdrawState(false, getFinishedProcessReason(processInstanceRecord.getFinishReason()));
+ }
+
+ if (Boolean.TRUE.equals(rejectToStarter)) {
+ return new WithdrawState(false, "驳回待提交状态不可撤回");
+ }
+
+ boolean hasProcessedApproval = assignUsers.stream().anyMatch(this::isProcessedAssignUser);
+ if (hasProcessedApproval) {
+ return new WithdrawState(false, "首个审批节点已处理,无法撤回");
+ }
+
+ return new WithdrawState(true, null);
+ }
+
+ private boolean isProcessedAssignUser(ProcessNodeRecordAssignUser assignUser) {
+ return assignUser.getEndTime() != null
+ || !Objects.equals(assignUser.getStatus(), NodeStatusEnum.JXZ.getCode());
+ }
+
+ private String getFinishedProcessReason(String finishReason) {
+ if (StrUtil.equals(finishReason, ProcessInstanceConstant.FINISH_REASON_WITHDRAW)) {
+ return "流程已撤回";
+ }
+ if (StrUtil.equals(finishReason, ProcessInstanceConstant.FINISH_REASON_TERMINATE)) {
+ return "流程已终止";
+ }
+ if (StrUtil.equals(finishReason, ProcessInstanceConstant.FINISH_REASON_PASS)) {
+ return "流程已通过";
+ }
+ if (StrUtil.equals(finishReason, ProcessInstanceConstant.FINISH_REASON_REFUSE)) {
+ return "流程已拒绝";
+ }
+ return "流程已结束";
+ }
+
+ private static final class WithdrawState {
+
+ private final boolean canWithdraw;
+
+ private final String disabledReason;
+
+ private WithdrawState(boolean canWithdraw, String disabledReason) {
+ this.canWithdraw = canWithdraw;
+ this.disabledReason = disabledReason;
+ }
+ }
+
}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java
index 6886c467f..ee7a251d9 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java
@@ -10,14 +10,13 @@ import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import com.pig4cloud.pigx.flow.constant.NodeStatusEnum;
+import com.pig4cloud.pigx.flow.constant.ProcessInstanceConstant;
import com.pig4cloud.pigx.flow.dto.IndexPageStatistics;
import com.pig4cloud.pigx.flow.dto.Node;
import com.pig4cloud.pigx.flow.dto.TaskParamDto;
import com.pig4cloud.pigx.flow.dto.TaskResultDto;
+import com.pig4cloud.pigx.flow.entity.*;
import com.pig4cloud.pigx.flow.entity.Process;
-import com.pig4cloud.pigx.flow.entity.ProcessCopy;
-import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord;
-import com.pig4cloud.pigx.flow.entity.ProcessNodeRecordAssignUser;
import com.pig4cloud.pigx.flow.service.*;
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
import lombok.RequiredArgsConstructor;
@@ -67,6 +66,8 @@ public class TaskServiceImpl implements ITaskService {
private final IProcessInstanceRecordService processInstanceRecordService;
+ private final IProcessNodeRecordService processNodeRecordService;
+
private final IFlowableEngineService flowableEngineService;
private final TaskService taskService;
@@ -476,39 +477,60 @@ public class TaskServiceImpl implements ITaskService {
*/
@Override
public R stopProcessInstance(TaskParamDto taskParamDto) {
- String reqProcessInstanceId = taskParamDto.getProcessInstanceId();
- List allStopProcessInstanceIdList = getAllStopProcessInstanceIdList(reqProcessInstanceId);
- CollUtil.reverse(allStopProcessInstanceIdList);
- allStopProcessInstanceIdList.add(reqProcessInstanceId);
- allStopProcessInstanceIdList.forEach(processInstanceId -> {
- // 查询流程实例
- ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
- .processInstanceId(processInstanceId)
- .processInstanceTenantId(TenantContextHolder.getTenantId().toString())
- .singleResult();
- if (Optional.ofNullable(processInstance).isPresent()) {
- // 查询执行实例
- List executionIds = runtimeService.createExecutionQuery()
- .parentId(processInstanceId)
- .list()
- .stream()
- .map(Execution::getId)
- .toList();
- // 更改活动状态为结束
- runtimeService.createChangeActivityStateBuilder()
- .moveExecutionsToSingleActivityId(executionIds, "end")
- .changeState();
- }
- });
+ return terminateProcessInstance(taskParamDto.getProcessInstanceId(),
+ ProcessInstanceConstant.FINISH_REASON_TERMINATE);
+ }
- // 停止流程实例后,更新流程实例记录状态
- processInstanceRecordService.lambdaUpdate()
- .set(ProcessInstanceRecord::getStatus, NodeStatusEnum.YJS.getCode())
- .set(ProcessInstanceRecord::getFinishReason, String.valueOf(NodeStatusEnum.YZZ.getCode()))
- .set(ProcessInstanceRecord::getEndTime, new Date())
- .eq(ProcessInstanceRecord::getProcessInstanceId, reqProcessInstanceId)
- .update(new ProcessInstanceRecord());
- return R.ok();
+ /**
+ * 撤回流程
+ *
+ * @param taskParamDto 任务参数,包含流程实例ID
+ * @return R 操作结果
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public R withdrawProcessInstance(TaskParamDto taskParamDto) {
+ String processInstanceId = taskParamDto.getProcessInstanceId();
+ if (StrUtil.isBlank(processInstanceId)) {
+ return R.failed("流程实例ID不能为空");
+ }
+
+ long userId = SecurityUtils.getUser().getId();
+ ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery()
+ .eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId)
+ .one();
+ if (processInstanceRecord == null
+ || !Objects.equals(processInstanceRecord.getStatus(), NodeStatusEnum.JXZ.getCode())) {
+ return R.failed("流程不存在或已结束");
+ }
+
+ if (!Objects.equals(processInstanceRecord.getUserId(), userId)) {
+ return R.failed("仅发起人可撤回流程");
+ }
+
+ try {
+ Object rejectToStarter = runtimeService.getVariable(processInstanceId,
+ ProcessInstanceConstant.REJECT_TO_STARTER_VAR);
+ if (Boolean.TRUE.equals(rejectToStarter)) {
+ return R.failed("驳回待提交状态不可撤回");
+ }
+ } catch (Exception e) {
+ log.warn("查询撤回状态变量失败: processInstanceId={}, error={}", processInstanceId, e.getMessage());
+ }
+
+ boolean hasProcessedApproval = processNodeRecordAssignUserService.lambdaQuery()
+ .eq(ProcessNodeRecordAssignUser::getProcessInstanceId, processInstanceId)
+ .ne(ProcessNodeRecordAssignUser::getNodeId, "root")
+ .and(wrapper -> wrapper
+ .isNotNull(ProcessNodeRecordAssignUser::getEndTime)
+ .or()
+ .ne(ProcessNodeRecordAssignUser::getStatus, NodeStatusEnum.JXZ.getCode()))
+ .exists();
+ if (hasProcessedApproval) {
+ return R.failed("首个审批节点已处理,无法撤回");
+ }
+
+ return terminateProcessInstance(processInstanceId, ProcessInstanceConstant.FINISH_REASON_WITHDRAW);
}
/**
@@ -626,4 +648,72 @@ public class TaskServiceImpl implements ITaskService {
return collect;
}
+ private R terminateProcessInstance(String reqProcessInstanceId, String finishReason) {
+ if (StrUtil.isBlank(reqProcessInstanceId)) {
+ return R.failed("流程实例ID不能为空");
+ }
+
+ List allStopProcessInstanceIdList = getAllStopProcessInstanceIdList(reqProcessInstanceId);
+ CollUtil.reverse(allStopProcessInstanceIdList);
+ allStopProcessInstanceIdList.add(reqProcessInstanceId);
+
+ Date now = new Date();
+ int assignUserStatus = Objects.equals(finishReason, ProcessInstanceConstant.FINISH_REASON_WITHDRAW)
+ ? ProcessInstanceConstant.ASSIGN_USER_STATUS_WITHDRAW
+ : ProcessInstanceConstant.ASSIGN_USER_STATUS_TERMINATE;
+
+ // 先更新本地记录,确保流程结束通知能拿到准确的 finishReason
+ processInstanceRecordService.lambdaUpdate()
+ .set(ProcessInstanceRecord::getStatus, NodeStatusEnum.YJS.getCode())
+ .set(ProcessInstanceRecord::getFinishReason, finishReason)
+ .set(ProcessInstanceRecord::getEndTime, now)
+ .in(ProcessInstanceRecord::getProcessInstanceId, allStopProcessInstanceIdList)
+ .update(new ProcessInstanceRecord());
+
+ processNodeRecordService.lambdaUpdate()
+ .set(ProcessNodeRecord::getStatus, NodeStatusEnum.YJS.getCode())
+ .set(ProcessNodeRecord::getEndTime, now)
+ .in(ProcessNodeRecord::getProcessInstanceId, allStopProcessInstanceIdList)
+ .eq(ProcessNodeRecord::getStatus, NodeStatusEnum.JXZ.getCode())
+ .update(new ProcessNodeRecord());
+
+ processNodeRecordAssignUserService.lambdaUpdate()
+ .set(ProcessNodeRecordAssignUser::getStatus, assignUserStatus)
+ .set(ProcessNodeRecordAssignUser::getEndTime, Convert.toLocalDateTime(now))
+ .set(ProcessNodeRecordAssignUser::getTaskType,
+ Objects.equals(finishReason, ProcessInstanceConstant.FINISH_REASON_WITHDRAW)
+ ? "WITHDRAW"
+ : "TERMINATE")
+ .in(ProcessNodeRecordAssignUser::getProcessInstanceId, allStopProcessInstanceIdList)
+ .eq(ProcessNodeRecordAssignUser::getStatus, NodeStatusEnum.JXZ.getCode())
+ .update(new ProcessNodeRecordAssignUser());
+
+ allStopProcessInstanceIdList.forEach(processInstanceId -> {
+ ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .processInstanceTenantId(TenantContextHolder.getTenantId().toString())
+ .singleResult();
+ if (processInstance == null) {
+ return;
+ }
+
+ List executionIds = runtimeService.createExecutionQuery()
+ .parentId(processInstanceId)
+ .list()
+ .stream()
+ .map(Execution::getId)
+ .toList();
+ if (CollUtil.isEmpty(executionIds)) {
+ log.warn("流程实例缺少可迁移执行流,跳过状态迁移: processInstanceId={}", processInstanceId);
+ return;
+ }
+
+ runtimeService.createChangeActivityStateBuilder()
+ .moveExecutionsToSingleActivityId(executionIds, "end")
+ .changeState();
+ });
+
+ return R.ok();
+ }
+
}