wisdomisite-java/src/main/java/com/zhgd/xmgl/util/FlowSeviceUtil.java

486 lines
25 KiB
Java
Raw Normal View History

package com.zhgd.xmgl.util;
import cn.hutool.core.collection.CollUtil;
2024-10-16 18:25:15 +08:00
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
2024-10-16 18:25:15 +08:00
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
2024-10-22 19:22:09 +08:00
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
2024-10-16 18:25:15 +08:00
import com.gexin.fastjson.JSON;
import com.gexin.fastjson.JSONArray;
import com.gexin.fastjson.JSONObject;
import com.wflow.bean.do_.UserDeptDo;
import com.wflow.bean.entity.WflowModelHistorys;
2024-10-22 19:22:09 +08:00
import com.wflow.bean.entity.WflowModels;
2024-10-16 18:25:15 +08:00
import com.wflow.bean.entity.WflowSubProcess;
import com.wflow.mapper.WflowModelHistorysMapper;
2024-10-22 19:22:09 +08:00
import com.wflow.mapper.WflowModelsMapper;
2024-10-16 18:25:15 +08:00
import com.wflow.mapper.WflowSubProcessMapper;
import com.wflow.service.OrgRepositoryService;
import com.wflow.workflow.UELTools;
import com.wflow.workflow.bean.dto.ProcessInstanceOwnerDto;
import com.wflow.workflow.bean.process.OrgUser;
import com.wflow.workflow.bean.process.ProcessNode;
import com.wflow.workflow.bean.process.enums.ApprovalModeEnum;
import com.wflow.workflow.bean.process.enums.NodeTypeEnum;
import com.wflow.workflow.bean.process.props.ApprovalProps;
import com.wflow.workflow.bean.process.props.CcProps;
import com.wflow.workflow.bean.vo.ProcessConditionResolveParamsVo;
import com.wflow.workflow.bean.vo.ProcessProgressVo;
import com.wflow.workflow.bean.vo.ProcessTaskVo;
2024-10-16 18:25:15 +08:00
import com.wflow.workflow.config.WflowGlobalVarDef;
import com.wflow.workflow.service.*;
import com.wflow.workflow.service.impl.ProcessTaskServiceImpl;
2025-01-10 22:32:26 +08:00
import com.wflow.workflow.utils.Executor;
2024-10-22 19:22:09 +08:00
import com.wflow.workflow.utils.FlowableUtils;
import com.zhgd.xmgl.modules.baotou.entity.vo.CountFlowVO;
import com.zhgd.xmgl.security.util.SecurityUtils;
2025-01-10 22:32:26 +08:00
import com.zhgd.xmgl.tenant.TenantContextHolder;
2024-10-16 18:25:15 +08:00
import lombok.extern.slf4j.Slf4j;
2024-10-22 19:22:09 +08:00
import org.apache.commons.collections.MapUtils;
2024-10-16 18:25:15 +08:00
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
2024-10-16 18:25:15 +08:00
import org.flowable.engine.history.HistoricProcessInstance;
2025-01-10 22:32:26 +08:00
import org.flowable.engine.history.HistoricProcessInstanceQuery;
2024-10-22 19:22:09 +08:00
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.task.api.Task;
2024-10-16 18:25:15 +08:00
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.TaskQuery;
2024-10-16 18:25:15 +08:00
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
2024-10-16 18:25:15 +08:00
import java.util.*;
2024-10-22 19:22:09 +08:00
import java.util.function.Function;
import java.util.stream.Collectors;
@Component
2024-10-16 18:25:15 +08:00
@Slf4j
public class FlowSeviceUtil {
2024-10-16 18:25:15 +08:00
private final static OrgUser UNKNOW_USER = OrgUser.builder().id("5201314").name("人员待定").build();
2024-10-22 19:22:09 +08:00
@Autowired
private WflowModelsMapper wflowModelsMapper;
@Lazy
@Autowired
private TaskService taskService;
@Lazy
@Autowired
private RuntimeService runtimeService;
2024-10-16 18:25:15 +08:00
@Lazy
@Autowired
private ProcessInstanceService processService;
@Lazy
@Autowired
private WflowModelHistorysMapper modelHistorysMapper;
@Lazy
@Autowired
private HistoryService historyService;
@Lazy
@Autowired
private RepositoryService repositoryService;
@Lazy
@Autowired
private UserDeptOrLeaderService userDeptOrLeaderService;
@Lazy
@Autowired
private ProcessStepRenderService stepRenderService;
@Lazy
@Autowired
private ProcessTaskServiceImpl processTaskService;
@Lazy
@Autowired
private OrgRepositoryService orgRepositoryService;
@Lazy
@Autowired
private BusinessDataStorageService businessDataService;
@Lazy
@Autowired
private UELTools uelTools;
@Lazy
@Autowired
private WflowSubProcessMapper subProcessMapper;
@Lazy
@Autowired
private ProcessNodeCatchService nodeCatchService;
2024-10-22 19:22:09 +08:00
@Autowired
private WflowModelHistorysMapper wflowModelHistorysMapper;
@Autowired
private ProcessModelService modelService;
2024-10-16 18:25:15 +08:00
/**
* 获取下一个待处理的审批节点
*
* @param instanceId
* @param dfNodeId
* @return
*/
public ProcessProgressVo.ProgressNode getNextTodoApproval(String instanceId, String dfNodeId) {
//先查实例,然后判断是子流程还是主流程
HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(instanceId).singleResult();
//数据分类 表单配置及数据、审批任务结果、
ProcessInstanceOwnerDto owner = null;
Map<String, Object> formDatas = new HashMap<>();
//是否是子流程
boolean isSub = StrUtil.isNotBlank(instance.getSuperProcessInstanceId());
HistoricProcessInstance mainInst = isSub ? null : instance;
if (isSub) {
//查出主流程表单数据
mainInst = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(instance.getSuperProcessInstanceId()).singleResult();
formDatas = businessDataService.getProcessInstanceFormData(mainInst.getId());
}
//优化查询,把之前查好几次的一次性查出来然后再分类
List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(instanceId).executionId(instanceId).list();
Map<String, Object> vars = Objects.nonNull(instance.getEndTime()) ?
new HashMap<>() : uelTools.getContextVar(instanceId, instance.getProcessDefinitionId());
//遍历所有变量,将数据分类
for (HistoricVariableInstance var : variables) {
vars.put(var.getVariableName(), var.getValue());
if (!isSub && var.getVariableName().startsWith("field")) {
formDatas.put(var.getVariableName(), var.getValue());
} else if (WflowGlobalVarDef.OWNER.equals(var.getVariableName())) {
owner = (ProcessInstanceOwnerDto) var.getValue();
} else if (WflowGlobalVarDef.START_DEPT.equals(var.getVariableName())) {
String key = instance.getStartUserId() + "_" + var.getValue();
Map<String, UserDeptDo> infoMap = orgRepositoryService.getUserDeptInfos(CollectionUtil.newArrayList(key));
UserDeptDo userDeptDo = infoMap.getOrDefault(key, new UserDeptDo());
owner = ProcessInstanceOwnerDto.builder().ownerDeptId(String.valueOf(var.getValue()))
.owner(instance.getStartUserId()).ownerName(userDeptDo.getUserName())
.ownerDeptId(userDeptDo.getDeptId()).ownerDeptName(userDeptDo.getDeptName()).build();
}
}
Map<String, ProcessNode<?>> nodeMap = Collections.emptyMap();
if (isSub) {
WflowSubProcess subProcess = subProcessMapper.selectOne(new LambdaQueryWrapper<>(WflowSubProcess.builder()
.procDefId(instance.getProcessDefinitionId()).build()));
nodeMap = nodeCatchService.reloadProcessByStr(subProcess.getProcess());
HistoricVariableInstance formsVar = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(mainInst.getId()).variableName(WflowGlobalVarDef.WFLOW_FORMS).singleResult();
}
//搜索当前版本流程的配置
WflowModelHistorys modelHistory = modelHistorysMapper.selectOne(new LambdaQueryWrapper<>(WflowModelHistorys.builder()
.processDefId(mainInst.getProcessDefinitionId()).version(mainInst.getProcessDefinitionVersion()).build()));
if (StrUtil.isNotBlank(dfNodeId)) {
nodeMap = nodeCatchService.reloadProcessByStr(modelHistory.getProcess());
}
List<ProcessProgressVo.ProgressNode> progressNodeList = this.getFutureTask(instance, owner.getOwnerDeptId(), vars, nodeMap, modelHistory.getTenantId());
if (CollUtil.isEmpty(progressNodeList)) {
2024-12-21 09:49:16 +08:00
log.warn("获取下一个待处理的审批节点超时失败instanceId=" + instanceId + "nodeId=" + dfNodeId);
return null;
} else {
return progressNodeList.get(0);
2024-10-16 18:25:15 +08:00
}
}
private boolean isProcessDone(String process, String curNodeId, String dfNodeId) {
JSONObject rootJo = JSON.parseObject(process);
int i = 0;
return getDeepFromProcess(rootJo, curNodeId, i) > getDeepFromProcess(rootJo, dfNodeId, i);
}
private Integer getDeepFromProcess(JSONObject jo, String curNodeId, int i) {
String id = jo.getString("id");
if (id.equals(curNodeId)) {
return i;
}
String type = jo.getString("type");
if (type.equals("CONDITIONS")) {
JSONArray array = jo.getJSONArray("branchs");
for (int j = 0; j < array.size(); j++) {
i = getDeepFromProcess(array.getJSONObject(j), curNodeId, i);
}
} else {
JSONObject children = jo.getJSONObject("children");
if (CollUtil.isEmpty(children)) {
return i;
}
i = getDeepFromProcess(children, curNodeId, ++i);
}
return i;
}
/**
* 获取我的待办的instanceIdList
*
* @param code 表单id安全检查wf66f6451c48b718d0aaf27522
* @return
*/
2024-10-16 18:25:15 +08:00
public List<String> getMyTodoInstanceIds(String code, String projectSn) {
if (SecurityUtils.getUser() == null) {
ArrayList<String> list = new ArrayList<>();
list.add("-1");
return list;
}
String userId = String.valueOf(SecurityUtils.getUser().getUserId());
TaskQuery taskQuery = taskService.createTaskQuery();
if (StrUtil.isNotBlank(code)) {
taskQuery.processDefinitionKey(code);
}
ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
Set<String> stringSet = processInstanceQuery.list().stream().map(p -> p.getProcessInstanceId()).collect(Collectors.toSet());
taskQuery.active().taskTenantId(projectSn)
.processInstanceIdIn(stringSet)
.taskCandidateOrAssigned(userId)
.orderByTaskCreateTime().desc();
Page<ProcessTaskVo> page = new Page<>();
List<Task> taskList = new ArrayList<>();
taskList = taskQuery.list();
List<String> list = taskList.stream().map(o -> o.getProcessInstanceId()).collect(Collectors.toList());
if (CollUtil.isEmpty(list)) {
list.add("-1");
}
return list;
}
2024-10-16 18:25:15 +08:00
private Set<String> getCcTaskUsers(String instanceId, String nodeId) {
//获取设置项
HistoricVariableInstance variableInstance = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(instanceId)
.variableName(WflowGlobalVarDef.WFLOW_NODE_PROPS).singleResult();
Map nodeProps;
if (Objects.nonNull(variableInstance)) {
nodeProps = (Map) variableInstance.getValue();
} else {
//流程首个节点需要从执行实例中取数据
nodeProps = runtimeService.getVariable(instanceId, WflowGlobalVarDef.WFLOW_NODE_PROPS, Map.class);
}
CcProps ccProps = (CcProps) nodeProps.get(nodeId);
//获取变量里面自选的抄送人
Set<String> ccUsers = new HashSet<>();
List<OrgUser> orgs = new ArrayList<>(ccProps.getAssignedUser());
if (ccProps.getShouldAdd()) {
//获取发起流程时添加的抄送人
HistoricVariableInstance result = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(instanceId)
.variableName(nodeId).singleResult();
if (Objects.nonNull(result)) {
Optional.ofNullable(result.getValue()).ifPresent(us -> orgs.addAll((List<OrgUser>) us));
} else {
Optional.ofNullable(runtimeService.getVariable(instanceId, nodeId, List.class)).ifPresent(orgs::addAll);
}
}
//解析部门与人员选项
ccUsers.addAll(orgs.stream().filter(org -> "user".equals(org.getType()))
.map(OrgUser::getId).collect(Collectors.toSet()));
ccUsers.addAll(userDeptOrLeaderService.getUsersByDept(orgs.stream()
.filter(org -> "dept".equals(org.getType()))
.map(OrgUser::getId).collect(Collectors.toSet())));
return ccUsers;
}
private List<ProcessProgressVo.ProgressNode> getFutureTask(HistoricProcessInstance instance, String
startDept, Map<String, Object> context, Map<String, ProcessNode<?>> nodes, String tenantId) {
//根据流程遍历后续节点,期间要穿越后续包含并行网关和条件网关的节点,先找到所有激活的任务节点开始递归
//节点如果处于并行/包容分支内,可能会有多个活动的节点
List<ProcessProgressVo.ProgressNode> progressNodes = new LinkedList<>();
try {
Set<String> idSet = new LinkedHashSet<>();
context.put("root", instance.getStartUserId());
taskService.createTaskQuery().processInstanceId(instance.getId()).active().list()
.stream().map(TaskInfo::getTaskDefinitionKey)
.collect(Collectors.toSet()).forEach(nodeId -> {
//根据每个活动的节点进行遍历,最终它们会在合流点汇聚
idSet.add(nodeId);
Optional.ofNullable(nodes.get(nodeId)).ifPresent(node -> {
//已激活的当前节点,需要进行去重
if (node.getProps() instanceof ApprovalProps) {
ApprovalProps props = (ApprovalProps) node.getProps();
List<String> users = processTaskService.getApprovalUsers(instance.getId(), node.getId(), props, tenantId);
//取已下发的任务
List<HistoricTaskInstance> ingTask = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(instance.getId()).taskDefinitionKey(nodeId).list();
//转交的也要过滤掉,但是只能过滤一次转交的
Set<String> ingTaskUser = ingTask.stream().map(TaskInfo::getAssignee).collect(Collectors.toSet());
ingTaskUser.addAll(ingTask.stream().filter(v -> Objects.nonNull(v.getOwner()))
.map(HistoricTaskInstance::getOwner).collect(Collectors.toList()));
Map<String, OrgUser> userMaps = userDeptOrLeaderService.getUserMapByIds(users);
users.stream().filter(v -> !ingTaskUser.contains(v))
.forEach(us -> {
Date createTime = ingTask.get(ingTask.size() - 1).getCreateTime();
//把时间往后延长10ms使其排列到后方
createTime.setTime(createTime.getTime() + 10);
progressNodes.add(ProcessProgressVo.ProgressNode.builder()
.nodeId(node.getId())
// .isFuture(true)
.name(node.getName())
.user(userMaps.getOrDefault(us, UNKNOW_USER))
.nodeType(node.getType())
.comment(Collections.emptyList())
.approvalMode(props.getMode())
.startTime(createTime)
.build());
});
}
//遍历后续节点
foreachNode(node.getChildren(), idSet, progressNodes, instance, context, tenantId);
});
});
} catch (Exception e) {
log.error("获取[{}]未开始任务异常: {}", instance.getId(), e.getMessage());
}
return progressNodes;
}
private void foreachNode(ProcessNode<?> node, Set<String> idSet, List<ProcessProgressVo.ProgressNode> progressNodes,
HistoricProcessInstance instance, Map<String, Object> vars, String tenantId) {
if (Objects.isNull(node) || StrUtil.isBlank(node.getId()) || idSet.contains(node.getId())) return;
idSet.add(node.getId());
if ((NodeTypeEnum.TASK.equals(node.getType()) || NodeTypeEnum.APPROVAL.equals(node.getType())
|| NodeTypeEnum.CC.equals(node.getType()))) {
//没有遍历过的节点,且是(审批、抄送、办理)把对应的人员解析出来
Collection<String> users;
ApprovalModeEnum modeEnum;
if (NodeTypeEnum.CC.equals(node.getType())) {
modeEnum = null;
users = getCcTaskUsers(instance.getId(), node.getId());
} else {
ApprovalProps props = (ApprovalProps) node.getProps();
modeEnum = props.getMode();
users = processTaskService.getApprovalUsers(instance.getId(), node.getId(), props, tenantId);
}
Map<String, OrgUser> userMaps = userDeptOrLeaderService.getUserMapByIds(users);
users.forEach(us -> progressNodes.add(ProcessProgressVo.ProgressNode.builder()
.nodeId(node.getId())
// .isFuture(true)
.name(node.getName())
.user(userMaps.getOrDefault(us, UNKNOW_USER))
.nodeType(node.getType())
.comment(Collections.emptyList())
.approvalMode(modeEnum)
.startTime(GregorianCalendar.getInstance().getTime())
.build()));
} else if (NodeTypeEnum.CONCURRENTS.equals(node.getType())) {
//并行网关,全部需要递归遍历
node.getBranchs().forEach(branch -> {
foreachNode(branch, idSet, progressNodes, instance, vars, tenantId);
});
//从分支出来继续往下遍历
} else if (NodeTypeEnum.INCLUSIVES.equals(node.getType()) || NodeTypeEnum.CONDITIONS.equals(node.getType())) {
//包容网关/条件网关,满足条件的分支都要执行
List<String> trueConditions = stepRenderService.getIsTrueConditions(ProcessConditionResolveParamsVo.builder()
.processDfId(instance.getProcessDefinitionId())
.conditionNodeId(node.getId()).context(vars)
.multiple(NodeTypeEnum.INCLUSIVES.equals(node.getType()))
.build());
//遍历满足条件的分支
node.getBranchs().forEach(bNode -> {
if (trueConditions.contains(bNode.getId())) {
foreachNode(bNode, idSet, progressNodes, instance, vars, tenantId);
}
});
} else if (NodeTypeEnum.EMPTY.equals(node.getType())) {
//空节点的话是合流点,说明前面有网关,那么要回头再向上去找节点,太复杂了,难搞
//nodes.get(node.getParentId())
}
foreachNode(node.getChildren(), idSet, progressNodes, instance, vars, tenantId);
}
2024-10-22 19:22:09 +08:00
public CountFlowVO countFlow(Map<String, Object> map) {
String projectSn = MapUtils.getString(map, "projectSn");
String userId = SecurityUtils.getUser().getUserId() + "";
2025-01-10 22:32:26 +08:00
//查询我的待办、质量待办、安全待办、抄送我的
2024-10-22 19:22:09 +08:00
TaskQuery taskQuery = taskService.createTaskQuery();
ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
Set<String> stringSet = processInstanceQuery.list().stream().map(p -> p.getProcessInstanceId()).collect(Collectors.toSet());
taskQuery.active().taskTenantId(projectSn)
.processInstanceIdIn(stringSet)
.taskCandidateOrAssigned(userId)
.orderByTaskCreateTime().desc();
List<Task> taskList = taskQuery.list();
Set<String> staterUsers = new HashSet<>();
Set<String> instanceIds = taskList.stream().map(Task::getProcessInstanceId).collect(Collectors.toSet());
Map<String, Object> startDept = FlowableUtils.getProcessVars(instanceIds, "startDept");
//把待办任务流程实例一次性取出来,减少查询次数
Map<String, ProcessInstance> instanceMap = CollectionUtil.isNotEmpty(taskList) ?
runtimeService.createProcessInstanceQuery().processInstanceIds(taskList.stream()
.map(Task::getProcessInstanceId).collect(Collectors.toSet()))
.list().stream().collect(Collectors.toMap(ProcessInstance::getId, v -> v)) : new HashMap<>();
List<String> formIds = taskList.stream().map(task -> {
ProcessInstance instance = instanceMap.get(task.getProcessInstanceId());
//构造用户id -> 部门id
staterUsers.add(instance.getStartUserId() + "_" + startDept.getOrDefault(task.getProcessInstanceId(),
//如果没有就从流程变量 owner 里取之前是存owner变量
FlowableUtils.getOwnerDept(task.getProcessInstanceId(), true)));
return instance.getProcessDefinitionKey();
}).filter(Objects::nonNull).collect(Collectors.toList());
//取用户信息,减少数据库查询,一次构建
if (CollectionUtil.isEmpty(staterUsers)) {
formIds = new ArrayList<>();
}
Map<String, WflowModels> safeMap = wflowModelsMapper.selectList(Wrappers.<WflowModels>lambdaQuery()
.eq(WflowModels::getGroupId, 135)).stream().collect(Collectors.toMap(WflowModels::getFormId, Function.identity()));
Map<String, WflowModels> qualityMap = wflowModelsMapper.selectList(Wrappers.<WflowModels>lambdaQuery()
.eq(WflowModels::getGroupId, 136)).stream().collect(Collectors.toMap(WflowModels::getFormId, Function.identity()));
Integer safe = 0;
Integer quality = 0;
for (String formId : formIds) {
if (safeMap.containsKey(formId)) {
safe++;
} else if (qualityMap.containsKey(formId)) {
quality++;
}
}
CountFlowVO vo = new CountFlowVO();
vo.setTotal(formIds.size());
vo.setSafe(safe);
vo.setQuality(quality);
2024-10-24 19:17:00 +08:00
vo.setCsMe((int) processService.getCcMeInstance(1, 1, null, null, null, null, null).getTotal());
2025-01-10 22:32:26 +08:00
//查询待催办、已办结
vo.setNeedCall((int) getTaskNum(userId, false).count());
vo.setDone((int) getTaskNum(userId, true).count());
2024-10-22 19:22:09 +08:00
return vo;
}
2024-10-16 18:25:15 +08:00
2025-01-10 22:32:26 +08:00
/**
* 查询任务数量
*
* @param userId
* @param finished
* @return
*/
private HistoricProcessInstanceQuery getTaskNum(String userId, boolean finished) {
HistoricProcessInstanceQuery instanceQuery = historyService.createHistoricProcessInstanceQuery();
instanceQuery.processInstanceTenantId(TenantContextHolder.getTenantId());
Executor.builder()
.ifNotBlankNext(userId, instanceQuery::involvedUser)
// .ifNotBlankNext(startUser, instanceQuery::startedBy)
// .ifNotBlankNext(code, instanceQuery::processDefinitionKey)
// .ifTrueNext(null != startTimes && startTimes.length > 1, () -> {
// instanceQuery.startedAfter(DateUtil.parse(startTimes[0]));
// instanceQuery.startedBefore(DateUtil.parse(startTimes[1]));
// })
// .ifNotBlankNext(keyword, v -> instanceQuery.processInstanceNameLike(StrUtil.format("%{}%", keyword)))
.ifTrueNext(Boolean.TRUE.equals(finished), instanceQuery::finished)
.ifTrueNext(Boolean.FALSE.equals(finished), instanceQuery::unfinished)
// .ifNotBlankNext(fieldId, id -> {
// if (StrUtil.isBlank(code)){
// throw new BusinessException("搜索表单值必须先指定表单流程类型");
// }
// if (StrUtil.isNotBlank(fieldVal)){
// instanceQuery.variableValueLike(fieldId, "%" + fieldVal + "%");
// }
// })
;
instanceQuery.count();
return instanceQuery;
}
2024-10-24 19:17:00 +08:00
public String getFlowIdByInstanceId(String instanceId) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
if (processInstance == null) {
return null;
}
return processInstance.getProcessDefinitionKey();
}
}