mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 20:49:00 +08:00
feat(pigx-flow-biz): 支持分组ID为空时查询所有未隐藏流程
This commit is contained in:
1 parent
26efa7acbf
commit
5f3bff90e1
11 files changed
+411
-118
No files matched your search
+39
-39
@@ -27,46 +27,46 @@ import java.util.List;
|
||||
@FeignClient(contextId = "remoteFlowApiFlowService", value = ServiceNameConstants.FLOW_SERVER)
|
||||
public interface RemoteFlowApiFlowService {
|
||||
|
||||
/**
|
||||
* 查询当前用户可发起的流程列表
|
||||
* <p>
|
||||
* 根据当前登录用户的权限,查询该用户有权发起的所有流程。
|
||||
* 返回结果按分组组织,方便前端展示。
|
||||
* </p>
|
||||
*
|
||||
* @return 分组后的流程列表,包含分组信息和各分组下的流程
|
||||
*/
|
||||
@GetMapping("/combination/group/listCurrentUserStartGroup")
|
||||
R<List<FormGroupVo>> listCurrentUserStartGroup();
|
||||
/**
|
||||
* 查询当前用户可发起的流程列表
|
||||
* <p>
|
||||
* 根据当前登录用户的权限,查询该用户有权发起的所有流程。
|
||||
* 返回结果按分组组织,方便前端展示。
|
||||
* </p>
|
||||
*
|
||||
* @return 分组后的流程列表,包含分组信息和各分组下的流程
|
||||
*/
|
||||
@GetMapping("/combination/group/listCurrentUserStartGroup")
|
||||
R<List<FormGroupVo>> listCurrentUserStartGroup();
|
||||
|
||||
/**
|
||||
* 获取流程详细信息
|
||||
* <p>
|
||||
* 根据流程ID获取流程的完整配置信息,包括:
|
||||
* - 流程基本信息(名称、图标、描述等)
|
||||
* - 表单配置(表单项定义、验证规则等)
|
||||
* - 流程配置(节点定义、流转规则等)
|
||||
* </p>
|
||||
*
|
||||
* @param flowId 流程定义ID
|
||||
* @return 流程详细信息视图对象
|
||||
*/
|
||||
@GetMapping("/process/getDetail")
|
||||
R<ProcessVO> getDetail(@RequestParam("flowId") String flowId);
|
||||
/**
|
||||
* 获取流程详细信息
|
||||
* <p>
|
||||
* 根据流程ID获取流程的完整配置信息,包括:
|
||||
* - 流程基本信息(名称、图标、描述等)
|
||||
* - 表单配置(表单项定义、验证规则等)
|
||||
* - 流程配置(节点定义、流转规则等)
|
||||
* </p>
|
||||
*
|
||||
* @param flowId 流程定义ID
|
||||
* @return 流程详细信息视图对象
|
||||
*/
|
||||
@GetMapping("/process/getDetail")
|
||||
R<ProcessVO> getDetail(@RequestParam("flowId") String flowId);
|
||||
|
||||
/**
|
||||
* 启动流程实例
|
||||
* <p>
|
||||
* 根据流程定义创建新的流程实例。启动流程时需要提供:
|
||||
* - 流程ID
|
||||
* - 表单数据
|
||||
* - 需要自选的节点执行人信息
|
||||
* </p>
|
||||
*
|
||||
* @param paramDto 流程实例启动参数,包含流程ID、表单数据等
|
||||
* @return 启动成功后的流程实例ID
|
||||
*/
|
||||
@PostMapping("/process-instance/startProcessInstance")
|
||||
R<String> startProcessInstance(@RequestBody ProcessInstanceParamDto paramDto);
|
||||
/**
|
||||
* 启动流程实例
|
||||
* <p>
|
||||
* 根据流程定义创建新的流程实例。启动流程时需要提供:
|
||||
* - 流程ID
|
||||
* - 表单数据
|
||||
* - 需要自选的节点执行人信息
|
||||
* </p>
|
||||
*
|
||||
* @param paramDto 流程实例启动参数,包含流程ID、表单数据等
|
||||
* @return 启动成功后的流程实例ID
|
||||
*/
|
||||
@PostMapping("/process-instance/startProcessInstance")
|
||||
R<String> startProcessInstance(@RequestBody ProcessInstanceParamDto paramDto);
|
||||
|
||||
}
|
||||
+30
@@ -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;
|
||||
|
||||
/**
|
||||
* 多人审批模式:会签 所有审批人都必须同意才能通过,任何一人拒绝则不通过
|
||||
*/
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.pig4cloud.pigx.flow.dto;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnore;
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnore;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
+10
@@ -44,4 +44,14 @@ public class ProcessInstanceRecordVo extends ProcessInstanceRecord {
|
||||
*/
|
||||
private Boolean hasPrintTemplate;
|
||||
|
||||
/**
|
||||
* 是否允许撤回
|
||||
*/
|
||||
private Boolean canWithdraw;
|
||||
|
||||
/**
|
||||
* 不可撤回原因
|
||||
*/
|
||||
private String withdrawDisabledReason;
|
||||
|
||||
}
|
||||
+2
-1
@@ -6,6 +6,7 @@ import com.pig4cloud.pigx.flow.service.ICombinationGroupService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ public class ProcessCombinationController {
|
||||
* @return R 统一响应对象,包含分页后的流程组及流程列表数据
|
||||
*/
|
||||
@GetMapping("listGroupWithProcess")
|
||||
public R listGroupWithProcess(Page page, Long groupId) {
|
||||
public R listGroupWithProcess(Page page, @RequestParam(required = false) Long groupId) {
|
||||
return combinationGroupService.listGroupWithProcess(page, groupId);
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -144,6 +144,17 @@ public class ProcessTaskController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回流程实例
|
||||
*
|
||||
* @param taskParamDto 撤回参数,包含流程实例ID
|
||||
* @return R 统一响应对象,操作成功返回撤回信息
|
||||
*/
|
||||
@PostMapping("withdrawProcessInstance")
|
||||
public R withdrawProcessInstance(@RequestBody TaskParamDto taskParamDto) {
|
||||
return taskService.withdrawProcessInstance(taskParamDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退回任务
|
||||
* <p>
|
||||
|
||||
+8
@@ -87,6 +87,14 @@ public interface ITaskService {
|
||||
*/
|
||||
R stopProcessInstance(TaskParamDto taskParamDto);
|
||||
|
||||
/**
|
||||
* 撤回流程实例
|
||||
*
|
||||
* @param taskParamDto 任务参数,包含流程实例ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
R withdrawProcessInstance(TaskParamDto taskParamDto);
|
||||
|
||||
/**
|
||||
* 退回任务
|
||||
* <p>
|
||||
|
||||
+2
-2
@@ -48,13 +48,13 @@ public class CombinationGroupServiceImpl implements ICombinationGroupService {
|
||||
* 根据流程分组ID查询该分组下所有未隐藏的流程,按照排序值升序和创建时间降序排列
|
||||
* </p>
|
||||
* @param page 分页参数,用于控制查询结果的分页
|
||||
* @param groupId 流程分组ID
|
||||
* @param groupId 流程分组ID,可为空;为空时返回所有分组下的未隐藏流程
|
||||
* @return R 包含分页后的流程列表
|
||||
*/
|
||||
@Override
|
||||
public R listGroupWithProcess(Page page, Long groupId) {
|
||||
return R.ok(processService.lambdaQuery()
|
||||
.eq(Process::getGroupId, groupId)
|
||||
.eq(Objects.nonNull(groupId), Process::getGroupId, groupId)
|
||||
.eq(Process::getHidden, YesNoEnum.NO.getCode())
|
||||
.orderByAsc(Process::getSort)
|
||||
.orderByDesc(Process::getCreateTime)
|
||||
|
||||
+170
-27
@@ -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<Process> processList = CollUtil.isEmpty(flowIds) ? Collections.emptyList()
|
||||
: processService.lambdaQuery().in(Process::getFlowId, flowIds).list();
|
||||
Set<String> processInstanceIds = voList.stream()
|
||||
.map(ProcessInstanceRecordVo::getProcessInstanceId)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
Map<String, List<ProcessNodeRecordAssignUser>> assignUserMap = queryAssignUserMap(processInstanceIds);
|
||||
|
||||
// 批量查询进行中流程的 rejectToStarter 变量,避免 N+1
|
||||
Set<String> runningInstanceIds = voList.stream()
|
||||
.filter(vo -> Objects.equals(vo.getStatus(), NodeStatusEnum.JXZ.getCode()))
|
||||
.map(ProcessInstanceRecordVo::getProcessInstanceId)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> rejectToStarterIds = batchQueryRejectToStarter(runningInstanceIds);
|
||||
|
||||
// 批量查询驳回待提交的 root 任务,避免 N+1
|
||||
Map<String, String> 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<String, Process> 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<String, String> 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<String, List<ProcessNodeRecordAssignUser>> queryAssignUserMap(Set<String> 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<String> batchQueryRejectToStarter(Set<String> 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<ProcessNodeRecordAssignUser> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+127
-35
@@ -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;
|
||||
@@ -279,6 +280,8 @@ public class TaskServiceImpl implements ITaskService {
|
||||
}
|
||||
|
||||
Dict set = Dict.create()
|
||||
.set("taskId", taskId)
|
||||
.set("nodeId", taskResultDto.getNodeId())
|
||||
.set("processInstanceId", taskResultDto.getProcessInstanceId())
|
||||
.set("node", node)
|
||||
.set("process", oaForms.getProcess())
|
||||
@@ -476,39 +479,60 @@ public class TaskServiceImpl implements ITaskService {
|
||||
*/
|
||||
@Override
|
||||
public R stopProcessInstance(TaskParamDto taskParamDto) {
|
||||
String reqProcessInstanceId = taskParamDto.getProcessInstanceId();
|
||||
List<String> 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<String> 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 +650,72 @@ public class TaskServiceImpl implements ITaskService {
|
||||
return collect;
|
||||
}
|
||||
|
||||
private R terminateProcessInstance(String reqProcessInstanceId, String finishReason) {
|
||||
if (StrUtil.isBlank(reqProcessInstanceId)) {
|
||||
return R.failed("流程实例ID不能为空");
|
||||
}
|
||||
|
||||
List<String> 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<String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
+11
-13
@@ -225,8 +225,6 @@ public class ModelUtil {
|
||||
if (nodeDto.getType() == NodeTypeEnum.EXCLUSIVE_GATEWAY.getValue().intValue()) {
|
||||
if (ord == size) {
|
||||
expression = NodeExpressionStrategyFactory.handleDefaultBranch(branchs, ord - 1);
|
||||
} else if (ord > 1) {
|
||||
expression = NodeExpressionStrategyFactory.handleDefaultBranch(branchs, ord - 1);
|
||||
} else {
|
||||
expression = NodeExpressionStrategyFactory.handle(branch);
|
||||
}
|
||||
@@ -337,7 +335,7 @@ public class ModelUtil {
|
||||
}
|
||||
// 条件分支
|
||||
if (node.getType() == NodeTypeEnum.EXCLUSIVE_GATEWAY.getValue().intValue()) {
|
||||
flowElementList.addAll(buildInclusiveGatewayNode(node));
|
||||
flowElementList.addAll(buildExclusiveGatewayNode(node));
|
||||
}
|
||||
// 并行分支
|
||||
if (node.getType() == NodeTypeEnum.PARALLEL_GATEWAY.getValue().intValue()) {
|
||||
@@ -560,27 +558,27 @@ public class ModelUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建包容网关
|
||||
* 构建排他网关
|
||||
* <p>
|
||||
* 创建包容网关(条件分支)节点和对应的合并网关。
|
||||
* 包容网关用于根据条件创建不同的执行路径。
|
||||
* 创建排他网关(条件分支)节点和对应的合并网关。
|
||||
* 排他网关用于根据条件只选择一条执行路径,避免多个条件同时为真时并行进入多个分支。
|
||||
*
|
||||
* @param node 包容网关节点数据
|
||||
* @param node 排他网关节点数据
|
||||
* @return 包含分叉网关和合并网关的元素列表
|
||||
*/
|
||||
private static List<FlowElement> buildInclusiveGatewayNode(Node node) {
|
||||
private static List<FlowElement> buildExclusiveGatewayNode(Node node) {
|
||||
|
||||
node.setTailId(StrUtil.format("{}_merge_gateway", node.getId()));
|
||||
|
||||
List<FlowElement> flowElementList = new ArrayList<>();
|
||||
|
||||
InclusiveGateway inclusiveGateway = new InclusiveGateway();
|
||||
inclusiveGateway.setId(node.getId());
|
||||
inclusiveGateway.setName(node.getName());
|
||||
flowElementList.add(inclusiveGateway);
|
||||
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
|
||||
exclusiveGateway.setId(node.getId());
|
||||
exclusiveGateway.setName(node.getName());
|
||||
flowElementList.add(exclusiveGateway);
|
||||
|
||||
// 合并网关
|
||||
InclusiveGateway gateway = new InclusiveGateway();
|
||||
ExclusiveGateway gateway = new ExclusiveGateway();
|
||||
gateway.setId(StrUtil.format("{}_merge_gateway", node.getId()));
|
||||
gateway.setName(StrUtil.format("{}_合并网关", node.getName()));
|
||||
flowElementList.add(gateway);
|
||||
|
||||
Reference in new issue
Block a user