package cn.axzo.workflow.core.service.impl;

import cn.axzo.basics.common.BeanMapper;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.AttachmentTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessTaskResultEnum;
import cn.axzo.workflow.common.enums.BusinessTypeEnum;
import cn.axzo.workflow.common.enums.ButtonVisibleScopeEnum;
import cn.axzo.workflow.common.enums.WorkspaceType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.SignFileDTO;
import cn.axzo.workflow.common.model.dto.SimpleDocDTO;
import cn.axzo.workflow.common.model.request.BpmnApproveConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.request.bpmn.process.BeforeProcessInstanceCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceBatchQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO;
import cn.axzo.workflow.common.model.request.category.CategorySearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationItemResultVO;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnNodeConfigVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.process.NodesByModelVO;
import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO;
import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.core.common.utils.BpmnCollectionUtils;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd;
import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd;
import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd;
import cn.axzo.workflow.core.engine.cmd.CustomOverrideFormVariablesByLatestInstanceCmd;
import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign;
import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.core.service.ExtAxProcessSignService;
import cn.axzo.workflow.core.service.ExtAxReModelService;
import cn.axzo.workflow.core.service.ExtAxReadRecordService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricProcessInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceAdminPageItemConverter;
import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnProcessInstancePageItemConverter;
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
import cn.axzo.workflow.core.service.support.ProcessGraphicService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.history.NativeHistoricProcessInstanceQuery;
import org.flowable.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.flowable.engine.impl.persistence.entity.ActivityInstanceEntity;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.NativeActivityInstanceQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.engine.task.Attachment;
import org.flowable.form.api.FormInfo;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.task.api.Task;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CREATE_PARAM_ERROR;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.RUNNING_INSTANCE_ONLY_FORECAST;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_IS_INVALID;
import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_IS_SUSPENDED;
import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_KEY_NOT_EXISTS;
import static cn.axzo.workflow.common.code.OtherRespCode.ASSIGNEE_NODE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPMN_FILE_SUFFIX;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CANCEL_PROCESS_NODE_DEF_KEY_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.CREATE_INSTANCE_PARAMS;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INITIATOR_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_AGENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_BIZ_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_WORKSPACE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.PENDING_TEMPLATE_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATORIES;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_PROCESS_ENABLE_DOC_IDS;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_PROCESS;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoPassed_empty;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoRejection_empty;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.human;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.transferToAdmin;
import static cn.axzo.workflow.common.enums.BpmnButtonEnum.BPMN_UPGRADE;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.EXCEPTIONAL;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_COMMENT;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_ROBOT;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_SIGN;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_TASK;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED;
import static cn.axzo.workflow.common.enums.WorkspaceType.GOVERNMENT;
import static cn.axzo.workflow.common.enums.WorkspaceType.PROJECT;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverScope;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCarbonCopyConfigs;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCategoryVersion;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getExcludeCooperateShipTypes;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getExcludeIdentityTypes;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getInitiatorSpecifiedFilter;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getInitiatorSpecifyRange;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getProcessApproveConf;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSignApproverLimit;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSupportInitiatorSpecified;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getUpgradeApprovalConf;

@Service
@Slf4j
public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceService {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private BpmnProcessDefinitionService processDefinitionService;
    @Resource
    private HistoryService historyService;
    @Resource
    private BpmnProcessInstanceAdminPageItemConverter instanceAdminPageItemConverter;
    @Resource
    private BpmnProcessInstancePageItemConverter instancePageItemConverter;
    @Resource
    private BpmnProcessInstanceConverter instanceConverter;
    @Resource
    private BpmnHistoricProcessInstanceConverter historicProcessInstanceConverter;
    @Resource
    private ManagementService managementService;
    @Resource
    private ProcessGraphicService graphicService;
    @Resource
    private FlowNodeForecastService forecastService;
    @Resource
    private EngineExecutionStartListener engineExecutionStartListener;
    @Resource
    private SpringProcessEngineConfiguration springProcessEngineConfiguration;
    @Resource
    private ExtAxHiTaskInstService extAxHiTaskInstService;
    @Resource
    private CategoryService categoryService;
    @Resource
    private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter;
    @Resource
    private String serviceVersion;
    @Resource
    @Lazy
    private BpmnProcessInstanceService bpmnProcessInstanceService;
    @Resource
    private ExtAxProcessLogService processLogService;
    @Resource
    private ExtAxBpmnFormRelationService bpmnFormRelationService;
    @Resource
    private ExtAxReModelService extAxReModelService;
    @Resource
    private ExtAxModelDocMapper extAxModelDocMapper;
    @Resource
    private ExtAxProcessSignService extAxProcessSignService;
    @Resource
    private ExtAxReadRecordService extAxReadRecordService;

    @Override
    public HistoricProcessInstance getProcessInstanceByBusinessKey(String businessKey, @Nullable String tenantId,
                                                                   @Nullable Boolean hasVariable) {
        HistoricProcessInstanceQuery query =
                historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey);
        if (Boolean.TRUE.equals(hasVariable)) {
            query.includeProcessVariables();
        }
        if (Objects.nonNull(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }

    @Override
    public BpmPageResult<BpmnProcessInstancePageItemVO> getMyProcessInstancePage(@Valid BpmnProcessInstanceMyPageReqVO dto) {
        HistoricProcessInstanceQuery query =
                historyService.createHistoricProcessInstanceQuery().processInstanceTenantId(dto.getTenantId());
        if (StringUtils.hasText(dto.getName())) {
            query.processInstanceName(dto.getName());
        }
        if (StringUtils.hasText(dto.getProcessDefinitionId())) {
            query.processDefinitionId(dto.getProcessDefinitionId());
        }
        if (StringUtils.hasText(dto.getKey())) {
            query.processDefinitionKey(dto.getKey());
        }
        if (StringUtils.hasText(dto.getBusinessKey())) {
            query.processInstanceBusinessKey(dto.getBusinessKey());
        }
        if (StringUtils.hasText(dto.getUserId())) {
            query.startedBy(dto.getUserId());
        }
        if (Objects.nonNull(dto.getBeginCreateTime())) {
            query.startedAfter(dto.getBeginCreateTime());
        }
        if (Objects.nonNull(dto.getEndCreateTime())) {
            query.startedBefore(dto.getEndCreateTime());
        }
        if (Objects.nonNull(dto.getResult())) {
            query.processInstanceBusinessStatus(dto.getResult().getStatus());
        }
        if (Objects.nonNull(dto.getTenantId())) {
            query.processInstanceTenantId(dto.getTenantId());
        }
        List<HistoricProcessInstance> instances = query.orderByProcessInstanceStartTime().desc()
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());

        if (CollectionUtils.isEmpty(instances)) {
            return BpmPageResult.empty();
        }

        List<BpmnProcessInstancePageItemVO> vos = new ArrayList<>();
        instances.forEach(i -> {
            BpmnProcessInstancePageItemVO vo = instancePageItemConverter.toVo(i);
            // 暂不对外提供发起人姓名
            //            List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
            //                    .processInstanceId(i.getId()).variableName(INTERNAL_START_USER_NAME).list();
            //            vo.setStartUserName((String) list.get(0).getValue());
            vos.add(vo);
        });
        return new BpmPageResult<>(vos, query.count());
    }

    @Override
    public HistoricProcessInstance getProcessInstance(String id, @Nullable String tenantId,
                                                      @Nullable Boolean hasVariable) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceId(id);
        if (Boolean.TRUE.equals(hasVariable)) {
            query.includeProcessVariables();
        }
        if (Objects.nonNull(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        HistoricProcessInstance processInstance = query.singleResult();
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, id);
        }
        return processInstance;
    }


    public List<ProcessInstance> getProcessInstances(Set<String> ids) {
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
    }


    @Override
    public Boolean updateProcessStatus(String processDefinitionId, Integer status) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), status)) {
            repositoryService.activateProcessDefinitionById(processDefinitionId, false, null);
            return true;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), status)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(processDefinitionId, false, null);
            return true;
        }
        log.warn("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, status);
        return false;
    }

    @Override
    public List<NodesByModelVO> nodesBeforeCreateProcessInstance(BeforeProcessInstanceCreateDTO dto) {
        BpmnProcessDefinitionVO definition = processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), dto.getTenantId());
        if (Objects.isNull(definition)) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey());
        }
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey());
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(definition.getId());
        List<FlowElement> orderedNodes = new ArrayList<>();
        forecastService.doForecasting(null, null, null, orderedNodes, bpmnModel);

        List<NodesByModelVO> nodes = new ArrayList<>();
        orderedNodes.forEach(fe -> {
            if (!(fe instanceof SequenceFlow) && !Objects.equals(fe.getId(), "startEventNode") && !Objects.equals(fe.getId(), "endEventNode")) {
                BpmnFlowNodeMode nodeModel = GENERAL;
                if (fe instanceof UserTask && ((UserTask) fe).getBehavior() instanceof MultiInstanceActivityBehavior) {
                    MultiInstanceActivityBehavior behavior = (MultiInstanceActivityBehavior) ((UserTask) fe).getBehavior();
                    nodeModel = (Objects.equals(AND_SIGN_EXPRESSION,
                            behavior.getCompletionCondition()) ? AND : OR);
                }
                ApproverSpecifyEnum approverSpecifyEnum = getApproverSpecify(fe).orElse(null);
                nodes.add(new NodesByModelVO()
                        .setActivityId(fe.getId())
                        .setActivityName(fe.getName())
                        .setAssigners(Objects.equals(NODE_STARTER.getType(), fe.getId()) ? Lists.newArrayList(dto.getInitiator()) : calcSpecifyAssigners(approverSpecifyEnum, fe))
                        .setSupportSpecify(getSupportInitiatorSpecified(fe))
                        .setNodeModel(nodeModel)
                        .setNodeType(getNodeType(fe).orElse(null))
                        .setNodeConfig(BpmnNodeConfigVO.builder()
                                .approvalMethodEnum(getApprovalMethod(fe).orElse(null))
                                .approverScopeEnum(getApproverScope(fe).orElse(null))
                                .approverSpecifyEnum(approverSpecifyEnum)
                                .initiatorSpecifyRange(getInitiatorSpecifyRange(fe).orElse(null))
                                .initiatorSpecifiedFilter(getInitiatorSpecifiedFilter(fe).orElse(null))
                                .excludeIdentityTypes(getExcludeIdentityTypes(fe).orElse(null))
                                .excludeCooperateShipTypes(getExcludeCooperateShipTypes(fe).orElse(null))
                                .approverEmptyHandleTypeEnum(getApproverEmptyHandleType(fe).orElse(null))
                                .signature(getActivitySignature(fe))
                                .signApproverLimit(getSignApproverLimit(fe).orElse(null))
                                .build())
                );
            }
        });
        if (CollectionUtils.isEmpty(dto.getFilter())) {
            return nodes;
        }
        return nodes.stream().filter(i -> dto.getFilter().contains(i.getNodeType())).collect(Collectors.toList());
    }

    private List<BpmnTaskDelegateAssigner> calcSpecifyAssigners(ApproverSpecifyEnum approverSpecifyEnum, FlowElement fe) {
        List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
        if (Objects.nonNull(approverSpecifyEnum) && Objects.equals(approverSpecifyEnum, ApproverSpecifyEnum.fixedPerson)) {
            BpmnMetaParserHelper.getApproverSpecifyValue(fe).ifPresent(s ->
                    assigners.addAll(JSON.parseArray(s, BpmnTaskDelegateAssigner.class)));
        }
        return assigners;
    }

    // TODO 该接口有个问题，如果模型没有关联表单，但是传入参数中含有表单变量，则不能正确创建流程
    @Override
    public String createProcessInstance(BpmnProcessInstanceCreateDTO dto) {
        BpmnProcessDefinitionVO definition = null;
        if (!StringUtils.hasText(dto.getProcessDefinitionId())) {
            definition = processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), dto.getTenantId());
        } else {
            definition = processDefinitionService.getProcessDefinition(dto.getProcessDefinitionId());
        }
        // 校验流程定义
        if (definition == null) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey());
        }
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey());
        }
        Optional<CategoryItemVO> categoryItemVO = categoryService.get(BPM_MODEL_CATEGORY, definition.getKey());
        //政务只能使用代运营的流程定义
        if (categoryItemVO.isPresent() && categoryItemVO.get().getWorkspaceTypeCode().equals(String.valueOf(GOVERNMENT.getCode()))) {
            if (!StringUtils.hasText(definition.getTenantId())) {
                throw new WorkflowEngineException(PROCESS_DEFINITION_IS_INVALID, dto.getProcessDefinitionKey());
            }
        }
        //新版入参 nodeId 校验
        if (categoryItemVO.isPresent() && categoryItemVO.get().getWorkspaceTypeCode().equals(String.valueOf(PROJECT.getCode())) && categoryItemVO.get().getVersion() > 0) {
            if (Objects.isNull(dto.getCooperationOrg().getNodeId())) {
                throw new WorkflowEngineException(PROCESS_INSTANCE_CREATE_PARAM_ERROR);
            }
            if (!StringUtils.hasText(dto.getInitiator().getNodeId())) {
                throw new WorkflowEngineException(ASSIGNEE_NODE_ID_NOT_EXISTS, "发起人");
            }
        }
        categoryItemVO.ifPresent(itemVO -> {
            dto.getVariables().put(INTERNAL_PROCESS_WORKSPACE_TYPE, WorkspaceType.getType(Integer.valueOf(itemVO.getWorkspaceTypeCode())).getCode());
            dto.getVariables().put(INTERNAL_PROCESS_BIZ_TYPE, itemVO.getBusinessType().getType());
            if (Objects.equals(BusinessTypeEnum.SIGN, itemVO.getBusinessType())) {
                // 签署业务
                dto.getVariables().put(SIGN_PROCESS_ENABLE_DOC_IDS, dto.getDocIds());
                dto.getVariables().put(SIGN_VARIABLE, dto.getBizCustomVariables());
                dto.getVariables().put(SIGNATORIES, dto.getSignatories());
            }
        });
        dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator().toJson());
        dto.getVariables().put(BIZ_ORG_RELATION, dto.getCooperationOrg());
        dto.getVariables().put(WORKFLOW_ENGINE_VERSION, serviceVersion);
        dto.getVariables().put(PENDING_TEMPLATE_VARIABLE, dto.getPendingVariables());
        dto.getVariables().put(INTERNAL_PROCESS_AGENT, StringUtils.hasText(definition.getTenantId()));

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        dto.getVariables().put(PROCESS_OWNERSHIP_APPLICATION, request.getHeader(HEADER_SERVER_NAME));
        // 发起人指定节点的审批人集合，此处不校验集合，如果人员为空，就走节点的审批人为空的逻辑
        dto.getVariables().put(INITIATOR_SPECIFY, dto.getSpecifyAssignerMap());

        // 创建流程实例
        // 设置流程实例的开始人，参考 https://wenku.baidu.com/view/5538062e7a563c1ec5da50e2524de518964bd3f9.html
        Authentication.setAuthenticatedUserId(dto.getInitiator().buildAssigneeId());
        String name = StringUtils.hasText(dto.getCustomProcessInstanceName()) ? dto.getCustomProcessInstanceName() : definition.getName();

        // 设置流程名字
        ProcessInstanceBuilder instanceBuilder = runtimeService.createProcessInstanceBuilder()
                .processDefinitionId(definition.getId())
                .businessKey(dto.getBusinessKey())
                .name(name)
                .tenantId(definition.getTenantId())
                .overrideProcessDefinitionTenantId(dto.getTenantId());

        ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(definition.getId());
        if (Objects.isNull(relation)) {
            // 如果模型没有绑定表单，则强制清空表单相关属性，避免异常
            instanceBuilder.outcome(null);
        } else {
            if (!CollectionUtils.isEmpty(dto.getStartFormVariables())) {
                instanceBuilder.startFormVariables(dto.getStartFormVariables());
            }
            if (StringUtils.hasText(dto.getOutcome())) {
                instanceBuilder.outcome(dto.getOutcome());
            }
        }
        dto.getVariables().put(CREATE_INSTANCE_PARAMS, JSONUtil.toJsonStr(dto));
        instanceBuilder.variables(dto.getVariables());
        ProcessInstance instance;
        if (Boolean.TRUE.equals(dto.getAsync())) {
            // 异步开始
            instance = instanceBuilder.startAsync();
        } else {
            // 同步开始
            instance = instanceBuilder.start();
        }
        Authentication.setAuthenticatedUserId(null);
        return instance.getProcessInstanceId();
    }

    @Override
    public FormInfo getStartFormModel(String processDefinitionId, String processInstanceId) {
        return runtimeService.getStartFormModel(processDefinitionId, processInstanceId);
    }

    @Override
    public Boolean cancelProcessInstance(SuperBpmnProcessInstanceCancelDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            commandExecutor.execute(new CustomCancelProcessInstanceAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomCancelProcessInstanceCmd(dto, extAxHiTaskInstService));
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public Boolean abortProcessInstance(BpmnProcessInstanceAbortDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            commandExecutor.execute(new CustomAbortProcessInstanceAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomAbortProcessInstanceCmd(dto, extAxHiTaskInstService));
        }
        return true;
    }

    @Override
    public BatchOperationResultVO batchAbortProcessInstance(List<BpmnProcessInstanceAbortDTO> dtos) {
        BatchOperationResultVO result = new BatchOperationResultVO();
        result.setRequestCount(dtos.size());
        if (CollectionUtils.isEmpty(dtos)) {
            return result;
        }
        List<BatchOperationItemResultVO> details = new ArrayList<>();
        result.setDetails(details);
        dtos.forEach(dto -> {
            BatchOperationItemResultVO itemVO = new BatchOperationItemResultVO();
            details.add(itemVO);
            itemVO.setType("流程");
            try {
                itemVO.setId(dto.getProcessInstanceId());
                bpmnProcessInstanceService.abortProcessInstance(dto);
            } catch (Exception e) {
                itemVO.setHasError(true);
                itemVO.setErrorMessage(e.getMessage());
            }
        });
        result.setFailCount((int) result.getDetails().stream().filter(BatchOperationItemResultVO::getHasError).count());
        return result;
    }

    @Override
    public Boolean carbonCopyProcessInstance(BpmnProcessInstanceCarbonCopyDTO dto) {
        // TODO 暴露给二方 API 的抄送功能, 需求被砍
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        //        commandExecutor.execute(new );
        return null;
    }

    @Override
    public BpmPageResult<BpmnProcessInstanceAdminPageItemVO> getAdminProcessInstancePage
            (BpmnProcessInstanceAdminPageReqVO dto) {
        List<String> dictValues = new ArrayList<>();
        if (StringUtils.hasText(dto.getKey())) {
            CategorySearchDTO searchDTO = new CategorySearchDTO();
            searchDTO.setDictType(BpmnConstants.BPM_MODEL_CATEGORY);
            searchDTO.setLabel(dto.getKey());
            dictValues.addAll(categoryService.list(searchDTO).stream().map(CategoryItemVO::getValue).distinct().collect(Collectors.toList()));
        }
        NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
        String tableName = managementService.getTableName(HistoricProcessInstance.class);
        StringBuilder baseQuerySql = new StringBuilder("SELECT RES.*, ")
                .append("DEF.KEY_           as PROC_DEF_KEY_,")
                .append("DEF.NAME_          as PROC_DEF_NAME_,")
                .append("DEF.VERSION_       as PROC_DEF_VERSION_,")
                .append("DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_")
                .append(" FROM ").append(tableName)
                .append(" RES LEFT OUTER JOIN ACT_RE_PROCDEF DEF ON RES.PROC_DEF_ID_ = DEF.ID_")
                .append(" WHERE 1 = 1");
        if (StringUtils.hasText(dto.getProcessInstanceId())) {
            List<String> ids = Lists.newArrayList(dto.getProcessInstanceId().replaceAll(" ", "").split(","));
            baseQuerySql.append(" AND RES.PROC_INST_ID_ IN (");
            for (int i = 0; i < ids.size(); i++) {
                baseQuerySql.append("#{instanceId").append(i).append("}");
                if (i < ids.size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("instanceId" + i, ids.get(i));
            }
            baseQuerySql.append(")");
        }
        if (StringUtils.hasText(dto.getName())) {
            baseQuerySql.append(" AND RES.NAME_ LIKE #{name}");
            query.parameter("name", "%" + dto.getName() + "%");
        }
        if (!CollectionUtils.isEmpty(dictValues)) {
            baseQuerySql.append(" AND DEF.KEY_ IN (");
            for (int i = 0; i < dictValues.size(); i++) {
                baseQuerySql.append("#{key").append(i).append("}");
                if (i < dictValues.size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("key" + i, dictValues.get(i));
            }
            baseQuerySql.append(")");
        }
        if (!CollectionUtils.isEmpty(dto.getTenantIds())) {
            baseQuerySql.append(" AND RES.TENANT_ID_ IN (");
            for (int i = 0; i < dto.getTenantIds().size(); i++) {
                baseQuerySql.append("#{tenantId").append(i).append("}");
                if (i < dto.getTenantIds().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("tenantId" + i, dto.getTenantIds().get(i));
            }
            baseQuerySql.append(")");
        }
        if (StringUtils.hasText(dto.getBusinessStatus())) {
            baseQuerySql.append(" AND RES.BUSINESS_STATUS_ = #{businessStatus}");
            query.parameter("businessStatus", dto.getBusinessStatus());
        }
        if (Objects.nonNull(dto.getStartTime()) && Objects.nonNull(dto.getEndTime())) {
            baseQuerySql.append(" AND RES.START_TIME_ >= #{startedAfter}");
            query.parameter("startedAfter", dto.getStartTime());
            baseQuerySql.append(" AND RES.START_TIME_ <= #{startedBefore}");
            query.parameter("startedBefore", dto.getEndTime());
        }
        baseQuerySql.append(" ORDER BY RES.START_TIME_ DESC ");

        List<HistoricProcessInstance> instances = query.sql(baseQuerySql.toString())
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(),
                        dto.getPageSize());

        baseQuerySql.replace(7, 163, "count(1)");
        NativeHistoricProcessInstanceQuery countSqlQuery = query.sql(baseQuerySql.toString());
        if (CollectionUtils.isEmpty(instances)) {
            return BpmPageResult.empty();
        }

        List<String> processingInstanceIds =
                instances.stream().filter(i -> Objects.equals(PROCESSING.getStatus(), i.getBusinessStatus()))
                        .map(HistoricProcessInstance::getId).distinct().collect(Collectors.toList());
        Map<String, List<FlowElement>> instanceFlowElementMap = new HashMap<>();
        processingInstanceIds.forEach(i -> {
            // 查询每个流程推测出来的总节点数
            instanceFlowElementMap.put(i, forecastService.performProcessForecasting(i, null));
        });

        // 实例对应的当前运行到的节点
        Map<String, ActivityInstance> liveActivityMap = getInstanceCurrentActivity(processingInstanceIds, false);
        // 实例对应的最后一个已经完成的节点
        Map<String, ActivityInstance> leastEndActivityMap = getInstanceLastActivity(processingInstanceIds);

        List<String> categories = instances.stream().map(HistoricProcessInstance::getProcessDefinitionKey)
                .distinct().collect(Collectors.toList());
        Map<String, CategoryItemVO> categoryLabelMap = categoryService.listByValue(BPM_MODEL_CATEGORY, categories)
                .stream().collect(Collectors.toMap(CategoryItemVO::getValue, Function.identity(), (s, t) -> s));

        List<BpmnProcessInstanceAdminPageItemVO> vos = instanceAdminPageItemConverter.toVos(instances,
                instanceFlowElementMap, liveActivityMap, leastEndActivityMap,
                categoryLabelMap);
        return new BpmPageResult<>(vos, countSqlQuery.count());
    }

    private Map<String, ActivityInstance> getInstanceLastActivity(List<String> processInstanceIds) {
        return getInstanceCurrentActivity(processInstanceIds, true);
    }

    private Map<String, ActivityInstance> getInstanceCurrentActivity(List<String> processInstanceIds,
                                                                     boolean leastEndActivity) {
        if (CollectionUtils.isEmpty(processInstanceIds)) {
            return Collections.emptyMap();
        }

        NativeActivityInstanceQuery nativeQuery = runtimeService.createNativeActivityInstanceQuery();
        String tableName = managementService.getTableName(ActivityInstanceEntity.class);
        StringBuilder querySql = new StringBuilder();
        querySql.append("SELECT * FROM (")
                .append("SELECT *, ROW_NUMBER() OVER (PARTITION BY PROC_INST_ID_ ORDER BY START_TIME_ DESC) AS rn ")
                .append(" FROM ")
                .append(tableName)
                .append(" WHERE PROC_INST_ID_ in (");
        for (int i = 0; i < processInstanceIds.size(); i++) {
            querySql.append("#{processInstanceId_").append(i).append("}");
            if (i < processInstanceIds.size() - 1) {
                querySql.append(",");
            }
            nativeQuery.parameter("processInstanceId_" + i, processInstanceIds.get(i));
        }
        querySql.append(") and TASK_ID_ is not null ");
        if (leastEndActivity) {
            querySql.append(" and END_TIME_ is not null");
        }
        querySql.append(") as t WHERE rn = 1");
        return nativeQuery.sql(querySql.toString()).list().stream()
                .collect(Collectors.toMap(ActivityInstance::getProcessInstanceId, Function.identity(),
                        (s, t) -> s));
    }


    @Override
    public HistoricProcessInstance getHistoricProcessInstance(String id, String tenantId) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceId(id);
        if (StringUtils.hasText(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }


    public HistoricProcessInstance getHistoricProcessInstanceByBusinessKey(String businessKey, String tenantId) {
        HistoricProcessInstanceQuery query =
                historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey);
        if (StringUtils.hasText(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }

    @Override
    public BpmnProcessInstanceVO getProcessInstanceVO(BpmnProcessInstanceQueryDTO dto) {
        HistoricProcessInstance processInstance = null;
        if (StringUtils.hasText(dto.getProcessInstanceId())) {
            processInstance = getProcessInstance(dto.getProcessInstanceId(), dto.getTenantId(), dto.getHasVariable());
        } else if (StringUtils.hasText(dto.getBusinessKey())) {
            processInstance = getProcessInstanceByBusinessKey(dto.getBusinessKey(), dto.getTenantId(),
                    dto.getHasVariable());
        }

        // 获得流程实例
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 获得流程定义
        BpmnProcessDefinitionVO processDefinition = processDefinitionService
                .getProcessDefinition(processInstance.getProcessDefinitionId());
        if (Objects.isNull(processDefinition)) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_ID_NOT_EXISTS,
                    processInstance.getProcessDefinitionId());
        }

        Object tempAssigner = null;
        String version = null;
        if (dto.getHasVariable()) {
            tempAssigner = processInstance.getProcessVariables().get(INTERNAL_INITIATOR);
            if (Objects.isNull(tempAssigner)) {
                tempAssigner = processInstance.getProcessVariables().get(OLD_INTERNAL_INITIATOR);
            }
            version = (String) processInstance.getProcessVariables().get(WORKFLOW_ENGINE_VERSION);

        } else {
            List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(processInstance.getId()).list();
            for (HistoricVariableInstance i : variableInstances) {
                if (Objects.equals(i.getVariableName(), INTERNAL_INITIATOR) || Objects.equals(i.getVariableName(),
                        OLD_INTERNAL_INITIATOR)) {
                    tempAssigner = i.getValue();
                }
                if (Objects.equals(i.getVariableName(), WORKFLOW_ENGINE_VERSION)) {
                    version = (String) i.getValue();
                }
            }
        }

        if (Objects.isNull(version)) {
            version = FLOW_SERVER_VERSION_121;
        }

        List<Task> runningTasks = taskService.createTaskQuery().processInstanceId(processInstance.getId())
                .active()
                .orderByTaskCreateTime()
                .desc()
                .list();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        return instanceConverter.toVo(processInstance, processDefinition,
                getButtonConfig(bpmnModel.getMainProcess()),
                getProcessApproveConf(bpmnModel.getMainProcess()),
                BpmnTaskDelegateAssigner.toObjectCompatible(tempAssigner), version, categoryService.get(BPM_MODEL_CATEGORY,
                        processInstance.getProcessDefinitionKey()), runningTasks);
    }

    @Override
    public List<BpmnProcessInstanceVO> getBatchProcessInstanceVO(BpmnProcessInstanceBatchQueryDTO dto) {
        List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceIds(Sets.newHashSet(dto.getProcessInstanceIds())).list();
        Map<String, Map<String, Object>> variableMaps = historyService.createNativeHistoricVariableInstanceQuery()
                .sql("select * from act_hi_varinst where PROC_INST_ID_ in (#{ids}) and (NAME_ = '" + INTERNAL_INITIATOR + "' or NAME_ = '" + OLD_INTERNAL_INITIATOR + "' or NAME_ = '" + WORKFLOW_ENGINE_VERSION + "')")
                .parameter("ids", dto.getProcessInstanceIds()).list()
                .stream()
                .collect(Collectors.groupingBy(HistoricVariableInstance::getProcessInstanceId, Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue, (s, t) -> s)));
        Map<String, Task> taskMaps = taskService.createTaskQuery().processInstanceIdIn(dto.getProcessInstanceIds())
                .active()
                .list()
                .stream().collect(Collectors.groupingBy(
                        Task::getProcessInstanceId,
                        Collectors.collectingAndThen(
                                Collectors.maxBy(Comparator.comparing(Task::getCreateTime)),
                                Optional::get
                        )
                ));


        Map<String, CategoryItemVO> categoryMaps = categoryService.getByValues(processInstanceList.stream().map(HistoricProcessInstance::getProcessDefinitionKey).filter(StringUtils::hasText).distinct().collect(Collectors.toList()))
                .stream()
                .filter(i -> Objects.equals(i.getType(), BPM_MODEL_CATEGORY))
                .collect(Collectors.toMap(CategoryItemVO::getValue, Function.identity(), (s, t) -> s));
        return processInstanceList.stream().map(e -> {
            BpmnProcessInstanceVO vo = new BpmnProcessInstanceVO();
            vo.setId(e.getId());
            vo.setName(e.getName());
            vo.setCategory(e.getProcessDefinitionKey());
            CategoryItemVO category = categoryMaps.getOrDefault(e.getProcessDefinitionKey(), null);
            if (Objects.nonNull(category)) {
                vo.setCategoryDesc(category.getLabel());
                vo.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(category.getWorkspaceTypeCode())));
            }
            vo.setResult(BpmnProcessInstanceResultEnum.valueOfStatus(e.getBusinessStatus()));
            vo.setCreateAt(e.getStartTime());
            vo.setEndTime(e.getEndTime());
            vo.setProcessDefinitionKey(e.getProcessDefinitionKey());
            vo.setProcessDefinitionId(e.getProcessDefinitionId());
            vo.setBusinessKey(e.getBusinessKey());
            vo.setBusinessStatus(e.getBusinessStatus());
            vo.setTenantId(e.getTenantId());
            Task task = taskMaps.getOrDefault(e.getId(), null);
            if (Objects.nonNull(task)) {
                vo.setCurrentDefinitionKey(task.getTaskDefinitionKey());
            }
            Map<String, Object> variables = variableMaps.getOrDefault(e.getId(), Collections.emptyMap());
            if (!CollectionUtils.isEmpty(variables)) {
                vo.setInitiator(BpmnTaskDelegateAssigner.toObjectCompatible(variables.getOrDefault(INTERNAL_INITIATOR, variables.getOrDefault(OLD_INTERNAL_INITIATOR, null))));
                vo.setWorkflowEngineVersion(variables.getOrDefault(WORKFLOW_ENGINE_VERSION, "").toString());
            }
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 获得流程实例 Map
     *
     * @param ids 流程实例的编号集合
     * @return 流程实例列表 Map
     */
    public Map<String, ProcessInstance> getProcessInstanceMap(Set<String> ids) {
        return BpmnCollectionUtils.convertMap(getProcessInstances(ids),
                ProcessInstance::getProcessInstanceId);
    }


    /**
     * 获得历史的流程实例 Map
     *
     * @param ids 流程实例的编号集合
     * @return 历史的流程实例列表 Map
     */
    public Map<String, HistoricProcessInstance> getHistoricProcessInstanceMap(Set<String> ids) {
        return BpmnCollectionUtils.convertMap(getHistoricProcessInstances(ids),
                HistoricProcessInstance::getId);
    }

    public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery =
                historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids);
        return historicProcessInstanceQuery.list();
    }

    @Override
    public ObjectNode getProcessInstanceGraphical(String processInstanceId, @Nullable String tenantId) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId);
        if (StringUtils.hasText(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        HistoricProcessInstance processInstance = query.singleResult();
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());

        List<HistoricActivityInstance> activityInstances =
                historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();

        Set<String> completedActivityInstances = new HashSet<>();
        Set<String> currentElements = new HashSet<>();
        if (!CollectionUtils.isEmpty(activityInstances)) {
            for (HistoricActivityInstance activityInstance : activityInstances) {
                if (activityInstance.getEndTime() != null) {
                    completedActivityInstances.add(activityInstance.getActivityId());
                } else {
                    currentElements.add(activityInstance.getActivityId());
                }
            }
        }
        // Gather completed flows
        List<String> completedFlows = graphicService.gatherCompletedFlows(completedActivityInstances, currentElements
                , bpmnModel);

        Set<String> completedElements = new HashSet<>(completedActivityInstances);
        completedElements.addAll(completedFlows);

        ObjectNode displayNode = graphicService.processProcessElements(bpmnModel, completedElements, currentElements,
                new ArrayList<>(), processInstanceId);
        if (!CollectionUtils.isEmpty(completedActivityInstances)) {
            ArrayNode completedActivities = displayNode.putArray("completedActivities");
            for (String completed : completedActivityInstances) {
                completedActivities.add(completed);
            }
        }

        if (!CollectionUtils.isEmpty(currentElements)) {
            ArrayNode currentActivities = displayNode.putArray("currentActivities");
            for (String current : currentElements) {
                currentActivities.add(current);
            }
        }

        if (!CollectionUtils.isEmpty(completedFlows)) {
            ArrayNode completedSequenceFlows = displayNode.putArray("completedSequenceFlows");
            for (String current : completedFlows) {
                completedSequenceFlows.add(current);
            }
        }
        return displayNode;
    }

    @Override
    public BpmPageResult<HistoricProcessInstanceVO> historicProcessInstancePage(HistoricProcessInstanceSearchDTO
                                                                                        dto) {
        NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
        String tableName = managementService.getTableName(HistoricProcessInstance.class);
        StringBuilder baseQuerySql = new StringBuilder("SELECT a.* FROM ").append(tableName).append(" a");
        if (StringUtils.hasText(dto.getKey())) {
            baseQuerySql.append(" LEFT JOIN ACT_RE_PROCDEF b ON a.PROC_DEF_ID_ = b.ID_ ")
                    .append(sqlConnectors(baseQuerySql))
                    .append(" b.KEY_ = #{key}");
            query.parameter("key", dto.getKey());
        }
        if (StringUtils.hasText(dto.getSearchKey())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" (a.BUSINESS_KEY_ LIKE #{searchKey}")
                    .append(" or")
                    .append(" a.NAME_ LIKE #{searchKey})");
            query.parameter("searchKey", "%" + dto.getSearchKey() + "%");
        }
        if (StringUtils.hasText(dto.getResult())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.BUSINESS_STATUS_ = #{result}");
            query.parameter("result", dto.getResult());
        }
        if (Objects.nonNull(dto.getTenantId())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.TENANT_ID_ = #{tenantId}");
            query.parameter("tenantId", dto.getTenantId());
        }
        if (!CollectionUtils.isEmpty(dto.getUserIds())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.START_USER_ID_ in (");
            for (int i = 0; i < dto.getUserIds().size(); i++) {
                baseQuerySql.append("#{userId").append(i).append("}");
                if (i < dto.getUserIds().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("userId" + i, dto.getUserIds().get(i));
            }
            baseQuerySql.append(")");
        }
        if (Objects.nonNull(dto.getStartBeginTime()) && Objects.nonNull(dto.getStartEndTime())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.START_TIME_ between #{startBeginTime} and #{startEndTime}");
            query.parameter("startBeginTime", dto.getStartBeginTime());
            query.parameter("startEndTime", dto.getStartEndTime());
        }
        if (Objects.nonNull(dto.getFinishedBeginTime()) && Objects.nonNull(dto.getFinishedEndTime())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.END_TIME_ between #{finishedBeginTime} and #{finishedEndTime}");
            query.parameter("finishedBeginTime", dto.getFinishedBeginTime());
            query.parameter("finishedEndTime", dto.getFinishedEndTime());
        }
        baseQuerySql.append(" ORDER BY a.START_TIME_ DESC");
        NativeHistoricProcessInstanceQuery dataSqlQuery = query.sql(baseQuerySql.toString());
        List<HistoricProcessInstance> instances = dataSqlQuery
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
        if (CollectionUtils.isEmpty(instances)) {
            return BpmPageResult.empty();
        }

        NativeHistoricProcessInstanceQuery countSqlQuery = query.sql(countSql(baseQuerySql));
        if (StringUtils.hasText(dto.getKey())) {
            return new BpmPageResult<>(historicProcessInstanceConverter.toVos(instances, dto.getKey()),
                    countSqlQuery.count());
        } else {
            Set<String> procDefIds = new HashSet<>();
            instances.forEach(i -> procDefIds.add(i.getProcessDefinitionId()));
            Map<String, String> defCategoryMap =
                    repositoryService.createProcessDefinitionQuery().processDefinitionIds(procDefIds).list()
                            .stream().collect(Collectors.toMap(ProcessDefinition::getId,
                                    ProcessDefinition::getKey, (s, t) -> s));
            List<HistoricProcessInstanceVO> vos = new ArrayList<>();
            instances.forEach(i -> vos.add(historicProcessInstanceConverter.toVo(i,
                    defCategoryMap.getOrDefault(i.getProcessDefinitionId(), ""))));
            return new BpmPageResult<>(vos, countSqlQuery.count());
        }
    }

    @Override
    public List<ProcessNodeDetailVO> getProcessInstanceNodeForecast(String processInstanceId,
                                                                    @Nullable String tenantId) {

        return getProcessInstanceNodeFilterForecast(processInstanceId, tenantId, Collections.emptyList());
    }

    @Override
    public List<ProcessNodeDetailVO> getProcessInstanceNodeForecastWithSpecifyTaskDefinitionKey(String processInstanceId,
                                                                                                ProcessInstance instance,
                                                                                                String startNodeDefinitionKey,
                                                                                                Boolean containSelf,
                                                                                                Boolean checkAliveThrowException) {
        if (Objects.isNull(instance)) {
            instance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processInstanceId).singleResult();
            if (Objects.isNull(instance)) {
                if (Boolean.TRUE.equals(checkAliveThrowException)) {
                    throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST);
                } else {
                    return Collections.emptyList();
                }
            }
        }

        List<FlowElement> flowElements = forecastService.performProcessForecasting(instance.getProcessInstanceId(), instance, startNodeDefinitionKey, containSelf);

        return buildNodeDetailVos(processInstanceId, Collections.emptyList(), instance, flowElements, startNodeDefinitionKey, containSelf);
    }


    @Override
    public List<ProcessNodeDetailVO> getProcessInstanceNodeFilterForecast(String processInstanceId, String
            tenantId, List<String> nodeDefinitionKeys) {
        ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId);
        if (StringUtils.hasText(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        ProcessInstance instance = query.singleResult();
        if (Objects.isNull(instance)) {
            throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST);
        }
        List<FlowElement> flowElements = forecastService.performProcessForecasting(processInstanceId, instance);

        return buildNodeDetailVos(processInstanceId, nodeDefinitionKeys, instance, flowElements, null, false);
    }

    private List<ProcessNodeDetailVO> buildNodeDetailVos(String processInstanceId,
                                                         List<String> nodeDefinitionKeys,
                                                         ProcessInstance instance,
                                                         List<FlowElement> flowElements,
                                                         String startNodeDefinitionKey,
                                                         Boolean containSelf) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
        Integer categoryVersion = getCategoryVersion(bpmnModel.getMainProcess()).orElse(1);
        List<ProcessNodeDetailVO> resultList = new ArrayList<>(flowElements.size());
        // 发起人节点,也是一个 UserTask 节点, 所以这里默认就包含了发起人节点
        flowElements.stream()
                .filter(i -> (i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask))
                .forEach(i -> {
                    ProcessNodeDetailVO node = new ProcessNodeDetailVO();
                    node.setForecastAssigners(Collections.emptyList());
                    // 每个节点设置的按钮配置
                    getButtonConfig(bpmnModel.getMainProcess(), i.getId()).ifPresent(node::setButtonConf);
                    // 设置审批方式
                    getApprovalMethod(i).ifPresent(node::setApprovalMethod);
                    node.setFutureNode(Objects.equals(Boolean.TRUE, containSelf) && !Objects.equals(i.getId(), startNodeDefinitionKey));
                    // 提级审批配置
                    getUpgradeApprovalConf(i).ifPresent(node::setUpgradeApprovalConf);
                    getNodeType(i).ifPresent(node::setNodeType);
                    if (Objects.equals(NODE_STARTER.getType(), i.getId())) {
                        node.setNodeType(NODE_STARTER);
                    }
                    node.setNodeMode(GENERAL);
                    node.setId(i.getId()).setName(i.getName());
                    if (i instanceof UserTask) {
                        parseUserTask(processInstanceId, (UserTask) i, node, nodeDefinitionKeys, categoryVersion);
                    } else if (i instanceof ServiceTask) {
                        parseServiceTask(processInstanceId, (ServiceTask) i, node, nodeDefinitionKeys, categoryVersion);
                    }
                    resultList.add(node);
                });
        return resultList;
    }

    private void parseServiceTask(String processInstanceId, ServiceTask i, ProcessNodeDetailVO
            node, List<String> skipTaskDefinitionKeys, Integer categoryVersion) {
        // ServiceTask 主要作用于抄送
        node.setId(i.getId()).setName(i.getName());
        if (skipTaskDefinitionKeys.contains(i.getId())) {
            return;
        }
        if (categoryVersion < 2) {
            getCarbonCopyConfigs(i).ifPresent(carbons ->
                    node.setForecastAssigners(springProcessEngineConfiguration.getCommandExecutor()
                            .execute(new CustomCarbonCopyUserSelectorCmd(processInstanceId, carbons,
                                    i, engineExecutionStartListener,
                                    historicTaskInstanceConverter, serviceVersion, categoryVersion))));
        } else {
            node.setForecastAssigners(springProcessEngineConfiguration.getCommandExecutor()
                    .execute(new CustomCarbonCopyUserSelectorCmd(processInstanceId, null,
                            i, engineExecutionStartListener,
                            historicTaskInstanceConverter, serviceVersion, categoryVersion)));
        }
    }

    private void parseUserTask(String processInstanceId, UserTask i, ProcessNodeDetailVO
            node, List<String> skipTaskDefinitionKeys, Integer categoryVersion) {
        node.setFormKey(i.getFormKey());
        // 设置审批模式,
        if (i.getBehavior() instanceof MultiInstanceActivityBehavior) {
            MultiInstanceActivityBehavior behavior =
                    (MultiInstanceActivityBehavior) i.getBehavior();
            node.setNodeMode(Objects.equals(AND_SIGN_EXPRESSION,
                    behavior.getCompletionCondition()) ?
                    AND : OR);
        } else {
            node.setNodeMode(BpmnFlowNodeMode.GENERAL);
        }

        if (skipTaskDefinitionKeys.contains(i.getId())) {
            return;
        }
        if (Objects.equals(node.getApprovalMethod(), human)) {
            // 推测当前节点的审批人,并且如果审批人为空,也会根据审批人为空的策略去找人
            List<BpmnTaskDelegateAssigner> forecastAssigners =
                    springProcessEngineConfiguration.getCommandExecutor()
                            .execute(new CustomForecastUserTaskAssigneeCmd(processInstanceId,
                                    i, engineExecutionStartListener, categoryVersion));
            node.setForecastAssigners(forecastAssigners);
            if (CollectionUtils.isEmpty(forecastAssigners)) {
                getApproverEmptyHandleType(i).ifPresent(emptyHandleType -> {
                    switch (emptyHandleType) {
                        case autoPassed:
                            node.setApprovalMethod(autoPassed_empty);
                            break;
                        case autoRejection:
                            node.setApprovalMethod(autoRejection_empty);
                            break;
                        case transferToAdmin:
                            node.setApprovalMethod(transferToAdmin);
                            break;
                        default:
                            node.setNodeMode(EXCEPTIONAL);
                            break;
                    }
                });
            }
        }
    }

    @Override
    public List<String> getTenantIds() {
        NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
        String tableName = managementService.getTableName(HistoricProcessInstance.class);
        String baseQuerySql = "SELECT DISTINCT TENANT_ID_ FROM " + tableName +
                " WHERE TENANT_ID_ IS NOT NULL AND TENANT_ID_ != '' AND TENANT_ID_ != 0 AND TENANT_ID_ != 'NULL'";
        NativeHistoricProcessInstanceQuery dataSqlQuery = query.sql(baseQuerySql);
        return ListUtils.emptyIfNull(dataSqlQuery.list()).stream().map(HistoricProcessInstance::getTenantId).distinct().collect(Collectors.toList());
    }

    @Override
    public Boolean checkInstanceApprover(BpmnProcessInstanceCheckApproverDTO dto) {
        if (Boolean.TRUE.equals(dto.getOnlyPersonId())) {
            dto.getApprover().setOuId(null);
        }
        List<Task> list = taskService.createTaskQuery()
                .processInstanceId(dto.getProcessInstanceId())
                .taskAssigneeLikeIgnoreCase(dto.getApprover().buildAssigneeId())
                .active()
                .list();
        return !CollectionUtils.isEmpty(list);
    }

    @Override
    public BpmnProcessInstanceLogVO getProcessInstanceLog(BpmnProcessInstanceLogQueryDTO dto) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(dto.getProcessInstanceId())
                .includeProcessVariables().singleResult();
        if (Objects.isNull(historicProcessInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, dto.getProcessInstanceId());
        }

        ExtAxProcessLog query = new ExtAxProcessLog();
        query.setProcessInstanceId(dto.getProcessInstanceId());
        List<ExtAxProcessLog> logs = processLogService.genericQuery(query).stream()
                .sorted(Comparator.comparing(ExtAxProcessLog::getEndTime, Comparator.nullsLast(Comparator.naturalOrder())))
                .collect(Collectors.toList());

        List<ProcessNodeDetailVO> forecasting = new ArrayList<>();
        // 只有还在运行中的实例才需要推测后续节点
        if (Objects.equals(historicProcessInstance.getBusinessStatus(), PROCESSING.getStatus())) {
            ProcessInstance instance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(dto.getProcessInstanceId())
                    .includeProcessVariables()
                    .singleResult();
            logs.stream().reduce((f, s) -> Objects.equals(s.getActivityId(), NODE_ROBOT.getType()) || Objects.equals(s.getActivityId(), NODE_COMMENT.getType()) ? f : s)
                    .ifPresent(e -> forecasting.addAll(
                            getProcessInstanceNodeForecastWithSpecifyTaskDefinitionKey(dto.getProcessInstanceId(), instance, e.getActivityId(), true, false))
                    );
        }

        BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());

        Map<String, Object> variables = historicProcessInstance.getProcessVariables();
        BpmnProcessInstanceLogVO logVO = BpmnProcessInstanceLogVO.builder()
                .id(historicProcessInstance.getId())
                .name(historicProcessInstance.getName())
                .result(BpmnProcessInstanceResultEnum.valueOfStatus(historicProcessInstance.getBusinessStatus()))
                .startTime(historicProcessInstance.getStartTime())
                .endTime(historicProcessInstance.getEndTime())
                .processDefinitionKey(historicProcessInstance.getProcessDefinitionKey())
                .processDefinitionId(historicProcessInstance.getProcessDefinitionId())
                .businessKey(historicProcessInstance.getBusinessKey())
                .businessStatus(historicProcessInstance.getBusinessStatus())
                .initiator(BpmnTaskDelegateAssigner.toObjectCompatible(Optional.ofNullable(variables.getOrDefault(INTERNAL_INITIATOR, null))
                        .orElse(variables.getOrDefault(OLD_INTERNAL_INITIATOR, null))))
                .tenantId(historicProcessInstance.getTenantId())
                .agented((Boolean) Optional.ofNullable(variables.get(INTERNAL_PROCESS_AGENT)).orElse(false))
                // 任务
                .taskDetails(genericTaskLogVos(historicProcessInstance.getId(), logs, forecasting, dto))
                .defaultButtonConf(getButtonConfig(bpmnModel.getMainProcess()).orElse(new BpmnButtonConf()))
                .supportBatchOperation(getProcessApproveConf(bpmnModel.getMainProcess()).orElse(new BpmnApproveConf()).getSupportBatchOperation())
                .userAgreeSignature(getProcessApproveConf(bpmnModel.getMainProcess()).orElse(new BpmnApproveConf()).getUserAgreeSignature())
                .workflowEngineVersion((String) variables.getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121))
                .catPrint(hasPrintTemplate(null, historicProcessInstance.getProcessDefinitionId()))
                .build();


        categoryService.get(BPM_MODEL_CATEGORY, historicProcessInstance.getProcessDefinitionKey())
                .ifPresent(category -> {
                    logVO.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(category.getWorkspaceTypeCode())));
                    logVO.setCategory(category.getValue());
                });

        // 根据传入的访问人计算有权限的按钮
        calcAuthorizedButtons(logVO, dto.getVisitor());
        return logVO;
    }


    @Override
    public BpmnTaskButtonVo findTaskButtons(BpmnTaskButtonSearchDTO taskButtonsSearchDTO) {
        BpmnTaskButtonVo bpmnTaskButtonVo = new BpmnTaskButtonVo();
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(taskButtonsSearchDTO.getProcessInstanceId())
                .includeProcessVariables()
                .singleResult();
        if (Objects.isNull(historicProcessInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, taskButtonsSearchDTO.getProcessInstanceId());
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
        ExtAxHiTaskInst extAxHiTaskInst = extAxHiTaskInstService.getByTaskId(taskButtonsSearchDTO.getTaskId(), taskButtonsSearchDTO.getProcessInstanceId());
        if (extAxHiTaskInst == null) {
            log.warn("extAxHiTaskInst is null,request param:{}", JSON.toJSONString(taskButtonsSearchDTO));
            throw new ServiceException("can find extAxHiTaskInst");
        }
        if (Objects.equals(extAxHiTaskInst.getStatus(), DELETED.getStatus())) {
            ExtHiTaskSearchDTO extHiTaskSearchDTO = ExtHiTaskSearchDTO.builder()
                    .processInstanceId(taskButtonsSearchDTO.getProcessInstanceId())
                    .taskDefinitionKey(extAxHiTaskInst.getTaskDefinitionKey())
                    .assignee(extAxHiTaskInst.getAssignee())
                    .excludeIds(Collections.singletonList(extAxHiTaskInst.getId()))
                    .build();
            List<ExtAxHiTaskInst> extAxHiTaskInsts = extAxHiTaskInstService.queryList(extHiTaskSearchDTO);
            log.info("extAxHiTaskInst status is DELETED, find another task list in same assignee and task def key,result is:{}", JSON.toJSONString(extAxHiTaskInsts));
            if (!CollectionUtils.isEmpty(extAxHiTaskInsts)) {
                extAxHiTaskInst = extAxHiTaskInsts.get(0);
            }
        }
        //判断节点类型
        FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(extAxHiTaskInst.getTaskDefinitionKey());
        BpmnFlowNodeType nodeType = getNodeType(flowElement).orElse(null);
        //设置任务状态
        bpmnTaskButtonVo.setExecutorTaskResult(getTaskStatus(nodeType, extAxHiTaskInst));
        String buttonConfigName = null;
        BpmnButtonConf buttonConf = getButtonConfig(bpmnModel.getMainProcess(), extAxHiTaskInst.getTaskDefinitionKey()).orElse(null);
        BpmnProcessInstanceLogVO logVO = BpmnProcessInstanceLogVO.builder()
                .id(historicProcessInstance.getId())
                .defaultButtonConf(getButtonConfig(bpmnModel.getMainProcess()).orElse(new BpmnButtonConf()))
                .calculatingButtonConf(buttonConf)
                .build();
        List<BpmnButtonMetaInfo> carbonCopyButtons = chooseButtons(logVO, CONFIG_BUTTON_TYPE_CARBON_COPY);
        List<BpmnButtonMetaInfo> initiatorButtons;
        List<BpmnButtonMetaInfo> executorButtons;
        if (Objects.equals(historicProcessInstance.getBusinessStatus(), PROCESSING.getStatus())) {
            //发起人按钮
            initiatorButtons = chooseButtons(logVO, CONFIG_BUTTON_TYPE_INITIATOR);
            if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_CARBON_COPY)) {
                buttonConfigName = CONFIG_BUTTON_TYPE_CARBON_COPY;
            } else if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_TASK) || //审批节点加业务节点指定审批人
                    (Objects.equals(nodeType, BpmnFlowNodeType.NODE_BUSINESS) && flowElement.getClass().isAssignableFrom(UserTask.class))) {
                // 待审批的节点
                if ((Objects.equals(PROCESSING.getStatus(), extAxHiTaskInst.getStatus()))) {
                    buttonConfigName = CONFIG_BUTTON_TYPE_CURRENT;
                }
                //已审批节点
                if (!Objects.equals(PROCESSING.getStatus(), extAxHiTaskInst.getStatus())) {
                    buttonConfigName = CONFIG_BUTTON_TYPE_HISTORY;
                }
            } else if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_STARTER)) {
                buttonConfigName = CONFIG_BUTTON_TYPE_INITIATOR;
            }
            if (!StringUtils.hasText(buttonConfigName)) {
                log.warn("can't find button config name ,req:{},extAxHiTaskInst:{}", JSON.toJSONString(taskButtonsSearchDTO), JSON.toJSONString(extAxHiTaskInst));
                throw new ServiceException("can't find button config name");
            }
            executorButtons = chooseButtons(logVO, buttonConfigName);
        } else {
            //需要判断流程是否撤回,撤回需要设置发起人状态
            if (Objects.equals(historicProcessInstance.getBusinessStatus(), CANCELLED.getStatus())) {
                Object object = historicProcessInstance.getProcessVariables().get(CANCEL_PROCESS_NODE_DEF_KEY_NAME);
                if (object instanceof String) {
                    String cancelNodeKey = (String) object;
                    if (StringUtils.hasText(cancelNodeKey) && cancelNodeKey.equals(extAxHiTaskInst.getTaskDefinitionKey())) {
                        bpmnTaskButtonVo.setInitiatorTaskResult(BpmnProcessTaskResultEnum.CANCELED);
                    }
                }
            }
            initiatorButtons = new ArrayList<>(carbonCopyButtons);
            executorButtons = new ArrayList<>(carbonCopyButtons);
        }

        bpmnTaskButtonVo.setCustomHiddenButtons(getHiddenCustomButtons(logVO, executorButtons));
        //设置allConfigButtons
        List<BpmnButtonMetaInfo> allConfigButtons = new ArrayList<>(executorButtons);
        bpmnTaskButtonVo.setAllConfigButtons(allConfigButtons);
        Map<BpmnButtonMetaInfo, Set<ButtonVisibleScopeEnum>> metaInfoListMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(executorButtons)) {
            for (BpmnButtonMetaInfo metaInfo : filterValuableButtons(executorButtons)) {
                metaInfoListMap.computeIfAbsent(metaInfo, k -> new HashSet<>()).add(ButtonVisibleScopeEnum.EXECUTOR);
            }
        }
        if (!CollectionUtils.isEmpty(initiatorButtons)) {
            for (BpmnButtonMetaInfo metaInfo : filterValuableButtons(initiatorButtons)) {
                metaInfoListMap.computeIfAbsent(metaInfo, k -> new HashSet<>()).add(ButtonVisibleScopeEnum.INITIATOR);
            }
        }

        List<BpmnTaskButtonVo.BpmnButtonMetaInfoWithVisibleScope> scopeButtons = metaInfoListMap.entrySet().stream()
                .map(m -> {
                    BpmnTaskButtonVo.BpmnButtonMetaInfoWithVisibleScope scopeButton = BeanUtil.copyProperties(m.getKey(), BpmnTaskButtonVo.BpmnButtonMetaInfoWithVisibleScope.class);
                    scopeButton.setVisibleScopes(new ArrayList<>(m.getValue()));
                    return scopeButton;
                })
                .collect(Collectors.toList());
        bpmnTaskButtonVo.setButtons(scopeButtons);
        return bpmnTaskButtonVo;
    }

    private BpmnProcessTaskResultEnum getTaskStatus(BpmnFlowNodeType nodeType, ExtAxHiTaskInst axHiTaskInst) {
        String logResultStatus = axHiTaskInst.getStatus();
        if (!StringUtils.hasText(logResultStatus)) {
            log.warn("log result status is blank!");
            return null;
        }
        BpmnProcessTaskResultEnum taskResult = null;
        BpmnProcessInstanceResultEnum instanceResultEnum = BpmnProcessInstanceResultEnum.valueOf(logResultStatus);
        switch (instanceResultEnum) {
            case CANCELLED:
                taskResult = BpmnProcessTaskResultEnum.CANCELED;
                break;
            case APPROVED:
                if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_CARBON_COPY)) {
                    taskResult = BpmnProcessTaskResultEnum.NONE;
                } else {
                    taskResult = BpmnProcessTaskResultEnum.APPROVED;
                }
                break;
            case REJECTED:
                taskResult = BpmnProcessTaskResultEnum.REJECTED;
                break;
            case TRANSFER:
                taskResult = BpmnProcessTaskResultEnum.TRANSFERRED;
                break;
            case PROCESSING:
                taskResult = BpmnProcessTaskResultEnum.PENDING;
                break;
            case DELETED:
                taskResult = BpmnProcessTaskResultEnum.DELETED;
                break;
            default:
                break;
        }
        return taskResult;
    }

    @Override
    public boolean hasPrintTemplate(String processInstanceId, String processDefinitionId) {
        try {
            return extAxReModelService.hasPrintTemplateConfig(getModelIdByProcessDefinitionId(processInstanceId, processDefinitionId));
        } catch (Exception e) {
            log.warn("hasPrintTemplate error:{}", e.getMessage(), e);
            return false;
        }
    }

    private void calcAuthorizedButtons(BpmnProcessInstanceLogVO logVO, BpmnTaskDelegateAssigner visitor) {
        List<BpmnButtonMetaInfo> authorizedButtons = new ArrayList<>();
        if (Objects.nonNull(logVO.getDefaultButtonConf())
                && !CollectionUtils.isEmpty(logVO.getDefaultButtonConf().getCarbonCopy())) {
            authorizedButtons.addAll(logVO.getDefaultButtonConf().getCarbonCopy());
        }

        AtomicBoolean supportUpgrade = new AtomicBoolean(false);
        if (Objects.nonNull(visitor)) {
            String ge130Assignee = getGe130Assignee(visitor);
            String le130Assignee = getLe130Assignee(visitor);

            // 运行到的当前节点的按钮配置
            logVO.getTaskDetails().stream()
                    .filter(i -> Objects.equals(PROCESSING, i.getResult()))
                    .findFirst()
                    .ifPresent(i -> logVO.setCalculatingButtonConf(i.getButtonConf()));

            // 比对发起人
            if (Objects.nonNull(logVO.getInitiator()) && Objects.equals(logVO.getResult(), PROCESSING) &&
                    (Objects.equals(logVO.getInitiator().buildAssigneeId_1_2_1(), le130Assignee)
                            || logVO.getInitiator().buildAssigneeId().contains(ge130Assignee))) {
                authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_INITIATOR));
            }


            // 比对当前审批人
            logVO.getTaskDetails().stream().filter(i -> Objects.equals(PROCESSING, i.getResult())
                            || (Objects.equals(DELETED, i.getResult()) && Objects.isNull(i.getEndTime())))
                    .findFirst()
                    .map(i -> {
                        List<BpmnTaskDelegateAssigner> list = new ArrayList<>();
                        if (Objects.nonNull(i.getAssigneeSnapshot())) {
                            list.add(i.getAssigneeSnapshot());
                        }
                        if (!CollectionUtils.isEmpty(i.getForecastAssignees())) {
                            list.addAll(i.getForecastAssignees());
                        }
                        return list;
                    })
                    .orElse(Collections.emptyList())
                    .stream()
                    .filter(Objects::nonNull)
                    .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee))
                    .findAny()
                    .ifPresent(i -> {
                        authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_CURRENT));
                        supportUpgrade.set(Objects.equals(Boolean.TRUE, i.getSupportUpgradeApproval()));
                    });


            // 比对历史审批人
            logVO.getTaskDetails().stream()
                    .filter(i -> Objects.equals(i.getNodeType(), NODE_TASK)
                            || Objects.equals(i.getNodeType(), NODE_BUSINESS)
                            || Objects.equals(i.getNodeType(), NODE_SIGN))
                    .filter(i -> !Objects.equals(PROCESSING, i.getResult()))
                    .map(BpmnTaskInstanceLogVO::getAssigneeSnapshot)
                    .filter(Objects::nonNull)
                    .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee))
                    .findAny()
                    .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_HISTORY)));

            // 比对抄送人
            logVO.getTaskDetails().stream()
                    .filter(i -> Objects.equals(i.getNodeType(), NODE_CARBON_COPY))
                    .flatMap(i -> ListUtils.emptyIfNull(i.getForecastAssignees()).stream())
                    .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee))
                    .findAny()
                    .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_CARBON_COPY)));
        }
        // 提级审批按钮特殊处理

        if (!supportUpgrade.get()) {
            authorizedButtons.removeIf(button -> Objects.equals(BPMN_UPGRADE.getBtnKey(), button.getBtnKey()));
        }


        logVO.setCurrentUserButtons(authorizedButtons);
        // 有权限访问的自定义按钮
        logVO.setCustomHiddenButtons(getHiddenCustomButtons(logVO, authorizedButtons));
    }

    private List<BpmnButtonMetaInfo> getHiddenCustomButtons(BpmnProcessInstanceLogVO logVO, List<BpmnButtonMetaInfo> bpmnButtonMetaInfos) {
        // 有权限访问的自定义按钮
        List<String> customButtonKeys = bpmnButtonMetaInfos.stream()
                .filter(i -> Objects.equals(i.getType(), BpmnButtonMetaInfo.BUTTON_TYPE_CUSTOM))
                .filter(i -> Boolean.FALSE.equals(i.getDisabled()) && Boolean.TRUE.equals(i.getChecked()))
                .map(BpmnButtonMetaInfo::getBtnKey)
                .distinct()
                .collect(Collectors.toList());
        return logVO.getDefaultButtonConf().getInitiator()
                .stream()
                .filter(i -> Objects.equals(i.getType(), BpmnButtonMetaInfo.BUTTON_TYPE_CUSTOM))
                .filter(i -> !customButtonKeys.contains(i.getBtnKey()))
                .collect(Collectors.toList());
    }

    /**
     * 按钮的通用处理, 有限使用节点的按钮配置,如果没有则按兜底按钮配置
     *
     * @param logVO            该对象中的 calcButtonConf 字段为当前节点的按钮配置
     * @param buttonConfigName String CONFIG_BUTTON_TYPE_INITIATOR = "initiator";
     *                         String CONFIG_BUTTON_TYPE_CURRENT = "current";
     *                         String CONFIG_BUTTON_TYPE_HISTORY = "history";
     *                         String CONFIG_BUTTON_TYPE_CARBON_COPY = "carbonCopy";
     * @return
     */
    private List<BpmnButtonMetaInfo> chooseButtons(BpmnProcessInstanceLogVO logVO, String buttonConfigName) {
        List<BpmnButtonMetaInfo> mergeButtons = new ArrayList<>();
        if (Objects.isNull(logVO.getCalculatingButtonConf())) {
            BpmnButtonConf defaultButtonConf = logVO.getDefaultButtonConf();
            if (Objects.isNull(defaultButtonConf)) {
                return mergeButtons;
            }
            logVO.setCalculatingButtonConf(defaultButtonConf);
        }
        switch (buttonConfigName) {
            case CONFIG_BUTTON_TYPE_INITIATOR:
                mergeButtons.addAll(logVO.getCalculatingButtonConf().getInitiator());
                break;
            case CONFIG_BUTTON_TYPE_CURRENT:
                mergeButtons.addAll(logVO.getCalculatingButtonConf().getCurrent());
                break;
            case CONFIG_BUTTON_TYPE_HISTORY:
                mergeButtons.addAll(logVO.getCalculatingButtonConf().getHistory());
                break;
            case CONFIG_BUTTON_TYPE_CARBON_COPY:
                mergeButtons.addAll(logVO.getCalculatingButtonConf().getCarbonCopy());
                break;
            default:
                break;
        }
        return mergeButtons;
    }

    /**
     * 过滤Disabled=false && Checked=true
     *
     * @param bpmnButtonMetaInfos 按钮列表
     * @return 有效的按钮列表
     */
    private List<BpmnButtonMetaInfo> filterValuableButtons(List<BpmnButtonMetaInfo> bpmnButtonMetaInfos) {
        if (CollectionUtils.isEmpty(bpmnButtonMetaInfos)) {
            return Collections.emptyList();
        }
        return bpmnButtonMetaInfos.stream()
                .filter(i -> !i.getDisabled())
                .filter(BpmnButtonMetaInfo::getChecked)
                .distinct()
                .collect(Collectors.toList());
    }

    public static String getLe130Assignee(BpmnTaskDelegateAssigner visitor) {
        return visitor.getTenantId() + "|" + visitor.getAssignee() + "|" + visitor.getAssigneeType();
    }

    public static String getGe130Assignee(BpmnTaskDelegateAssigner visitor) {
        //        String ge130Assignee = contextInfo.getOuId() + "|" + contextInfo.getUserInfo().getPersonId();
        // 130版本以上,产品要求仅校验 personId
        return "|" + visitor.getPersonId();
    }

    private List<BpmnTaskInstanceLogVO> genericTaskLogVos(String processInstanceId,
                                                          List<ExtAxProcessLog> logs,
                                                          List<ProcessNodeDetailVO> forecasting,
                                                          BpmnProcessInstanceLogQueryDTO dto) {
        List<BpmnTaskInstanceLogVO> tasks = new ArrayList<>();
        Map<String, List<Attachment>> attachmentByTaskMap =
                taskService.getProcessInstanceAttachments(processInstanceId).stream()
                        .collect(Collectors.groupingBy(Attachment::getTaskId));
        // 已完成的和进行中的
        getHistoricTasks(logs, forecasting, tasks, attachmentByTaskMap, dto.getVisitor());
        // 未来节点
        getFutureTasks(forecasting, tasks);
        // 处理是否加密
        handleEncrypt(dto.getEncrypt(), tasks);
        // reset field forecastAssignees Empty to null;
        resetCollectionToNull(tasks);
        return tasks;
    }

    private void resetCollectionToNull(List<BpmnTaskInstanceLogVO> tasks) {
        tasks.forEach(i -> {
            if (ListUtils.emptyIfNull(i.getForecastAssignees()).isEmpty()) {
                i.setForecastAssignees(null);
            }
        });
    }

    private static void handleEncrypt(Boolean encrypt, List<BpmnTaskInstanceLogVO> tasks) {
        if (Boolean.FALSE.equals(encrypt)) {
            return;
        }
        tasks.forEach(i -> {
            if (Objects.equals(NODE_STARTER.getType(), i.getTaskDefinitionKey())) {
                i.setOperationDesc(i.getAssigneeSnapshot().getAssignerName());
            } else if (Objects.equals(i.getResult(), APPROVED)) {
                i.setOperationDesc(APPROVED.getDesc());
            } else if (Objects.equals(i.getResult(), REJECTED)) {
                i.setOperationDesc(REJECTED.getDesc());
            } else if (Objects.equals(i.getResult(), PROCESSING) || Objects.isNull(i.getTaskId())) {
                i.setOperationDesc("待处理");
            } else {
                i.setOperationDesc("已处理");
            }
            // 统一将多人节点数据全部置空
            i.setForecastAssignees(null);
            // 统一将签名数据置空
            i.setSignatureUrl(null);
        });
    }

    private static void getFutureTasks
            (List<ProcessNodeDetailVO> forecasting, List<BpmnTaskInstanceLogVO> tasks) {
        ListUtils.emptyIfNull(forecasting).stream()
                .filter(i -> Objects.equals(Boolean.TRUE, i.getFutureNode()))
                .forEach(e -> {
                    BpmnTaskInstanceLogVO build = BpmnTaskInstanceLogVO.builder()
                            .taskDefinitionKey(e.getId())
                            .name(e.getName())
                            .approvalMethod(e.getApprovalMethod())
                            .nodeType(e.getNodeType())
                            .nodeMode(e.getNodeMode())
                            .forecastAssignees(e.getForecastAssigners())
                            .build();
                    if (Objects.nonNull(e.getApprovalMethod())) {
                        switch (e.getApprovalMethod()) {
                            case bizSpecify:
                                build.setOperationDesc("动态审批人");
                                break;
                            case nobody:
                                build.setOperationDesc("系统处理");
                                break;
                            case autoPassed:
                                build.setOperationDesc("无需审批人，自动同意");
                                break;
                            case autoRejection:
                                build.setOperationDesc("无需审批人，自动驳回");
                                break;
                            case autoPassed_empty:
                                build.setOperationDesc("未找到审批人，自动同意");
                                break;
                            case autoRejection_empty:
                                build.setOperationDesc("未找到审批人，自动驳回");
                                break;
                            case transferToAdmin:
                                build.setOperationDesc("找不到审批人且转交管理员失败，自动中止");
                                break;
                            case human:
                                if (Objects.equals(e.getNodeMode(), EXCEPTIONAL)) {
                                    build.setOperationDesc("");
                                } else {
                                    int countPerson = e.getForecastAssigners().size();
                                    if (Objects.equals(BpmnFlowNodeMode.AND, e.getNodeMode())) {
                                        build.setOperationDesc(countPerson + "人会签，需要全部同意");
                                    } else if (Objects.equals(BpmnFlowNodeMode.OR, e.getNodeMode())) {
                                        build.setOperationDesc(countPerson + "人或签，仅一人同意即可");
                                    }
                                    if (Objects.equals(countPerson, 1)) {
                                        // 如果未来节点是单人，则按单人节点展示
                                        build.setAssigneeSnapshot(build.getForecastAssignees().get(0));
                                        build.setOperationDesc(build.getAssigneeSnapshot().getAssignerName());
                                        build.setForecastAssignees(null);
                                    }
                                }
                                break;
                        }
                    }
                    if (Objects.equals(e.getNodeType(), NODE_CARBON_COPY)) {
                        build.setOperationDesc("抄送" + e.getForecastAssigners().size() + "人");
                    }
                    build.setUpgradeApprovalConf(e.getUpgradeApprovalConf());
                    tasks.add(build);
                });
    }

    private void getHistoricTasks(List<ExtAxProcessLog> logs,
                                  List<ProcessNodeDetailVO> forecasting,
                                  List<BpmnTaskInstanceLogVO> tasks,
                                  Map<String, List<Attachment>> attachmentByTaskMap,
                                  BpmnTaskDelegateAssigner visitor) {
        ListUtils.emptyIfNull(logs).forEach(e -> {
            Optional<BpmnTaskInstanceLogVO> processingTask = tasks.stream().filter(i -> Objects.equals(PROCESSING, i.getResult()))
                    .filter(i -> Objects.equals(i.getTaskDefinitionKey(), e.getActivityId())).findAny();

            BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(CollectionUtils.isEmpty(ListUtils.emptyIfNull(e.getAssigneeFull())) ? null : e.getAssigneeFull().get(0));
            if (processingTask.isPresent()) {
                // 多实例的情况，需要合并节点
                processingTask.ifPresent(i -> {
                    List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>(ListUtils.emptyIfNull(i.getForecastAssignees()));
                    if (CollectionUtils.isEmpty(assigners)) {
                        if (Objects.nonNull(i.getAssigneeSnapshot())) {
                            assigners.add(i.getAssigneeSnapshot());
                        }
                    }
                    if (Objects.nonNull(assigner)) {
                        assigners.add(assigner);
                    }
                    switch (i.getNodeMode()) {
                        case AND:
                            i.setOperationDesc(assigners.size() + "人会签，需要全部同意");
                            break;
                        case OR:
                            i.setOperationDesc(assigners.size() + "人或签，仅一人同意即可");
                            break;
                        default:
                            // 不修改操作描述
                            break;
                    }
                    i.setAssigneeSnapshot(null);
                    i.setForecastAssignees(assigners);
                    i.setButtonConf(e.getButtonConf());
                    // 根据当前登录人重设聚合后的节点 taskId
                    if (Objects.nonNull(visitor)) {
                        assigners.stream().filter(user -> Objects.equals(user.getPersonId(), visitor.getPersonId())).findFirst()
                                .ifPresent(user -> i.setTaskId(e.getTaskId()));
                    }
                });
            } else {
                tasks.add(BpmnTaskInstanceLogVO.builder()
                        .taskId(e.getTaskId())
                        .taskDefinitionKey(e.getActivityId())
                        .name(e.getActivityName())
                        .createTime(e.getStartTime())
                        .endTime(e.getEndTime())
                        .approvalMethod(ApprovalMethodEnum.valueOfType(e.getApprovalMethod()))
                        .nodeType(BpmnFlowNodeType.valueOfType(e.getNodeType()))
                        .nodeMode(BpmnFlowNodeMode.valueOfType(e.getNodeMode()))
                        .result(BpmnProcessInstanceResultEnum.valueOfStatus(e.getStatus()))
                        .operationDesc(e.getOperationDesc())
                        .advice(e.getAdvice())
                        .commentExt("")
                        .buttonConf(e.getButtonConf())
                        .imageList(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.image))
                        .fileList(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.file))
                        .signatureUrl(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.signature).stream().findFirst().orElse(new AttachmentDTO()).getUrl())
                        .signature(e.getSignature())
                        .assigneeSnapshot(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? null :
                                assigner)
                        .forecastAssignees(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? ListUtils.emptyIfNull(e.getAssigneeFull()) : Collections.emptyList())
                        .upgradeApprovalConf(forecasting.stream().filter(i -> Objects.equals(i.getId(), e.getActivityId())).findAny().orElse(new ProcessNodeDetailVO()).getUpgradeApprovalConf())
                        .build());
            }
        });
    }

    public List<AttachmentDTO> getAttachmentByType(Map<String, List<Attachment>> attachmentByTaskMap, String
            taskId, AttachmentTypeEnum type) {
        return ListUtils.emptyIfNull(attachmentByTaskMap.get(taskId)).stream()
                .filter(attachment -> Objects.equals(type.getType(), attachment.getType()))
                .map(e -> AttachmentDTO.builder()
                        .id(e.getId())
                        .type(type)
                        .name(e.getName())
                        .description(e.getDescription())
                        .url(e.getUrl())
                        .build())
                .collect(Collectors.toList());
    }

    @Override
    public String getModelIdByProcessInstanceId(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        if (Objects.isNull(historicProcessInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId);
        }
        return getModelIdByProcessDefinitionId(null, historicProcessInstance.getProcessDefinitionId());
    }

    @Override
    public String getModelIdByProcessDefinitionId(String processInstanceId, String processDefinitionId) {
        String key, tenantId;
        if (StringUtils.hasText(processInstanceId)) {
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
            if (Objects.isNull(historicProcessInstance)) {
                throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId);
            }
            key = historicProcessInstance.getProcessDefinitionKey();
            tenantId = historicProcessInstance.getTenantId();
        } else {
            ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
            if (Objects.isNull(processDefinition)) {
                throw new WorkflowEngineException(PROCESS_DEFINITION_ID_NOT_EXISTS, processDefinitionId);
            }
            key = processDefinition.getKey();
            tenantId = processDefinition.getTenantId();
        }
        return repositoryService.createModelQuery()
                .modelKey(key)
                .list().stream()
                .filter(i -> (Objects.equals(i.getTenantId(), tenantId) || Objects.equals(i.getTenantId(), NO_TENANT_ID))
                        && (Objects.equals(i.getCategory(), BPMN_FILE_SUFFIX) || Objects.equals(i.getCategory(), i.getKey()))
                        && i.getMetaInfo().contains(MODEL_TYPE_PROCESS))
                .sorted((a, b) -> {
                    if (Objects.equals(a.getTenantId(), tenantId)) {
                        return -1;
                    } else if (Objects.equals(b.getTenantId(), tenantId)) {
                        return 1;
                    }
                    return 0;
                })
                .findFirst()
                .orElseThrow(() -> new WorkflowEngineException(MODEL_NOT_EXISTS))
                .getId();
    }

    @Override
    public void overrideFormVariables(FormVariablesUpdateDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        commandExecutor.execute(new CustomOverrideFormVariablesByLatestInstanceCmd(dto.getProcessInstanceId(), dto.getFormVariables()));
    }

    @Override
    public List<DocPendingVO> processInstanceSelectDocs(ProcessDocQueryDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        List<DocBaseVO> docs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService));

        Map<Long, Boolean> readStatusMap = new HashMap<>();
        if (Objects.nonNull(dto.getAssigner())) {
            readStatusMap.putAll(extAxReadRecordService.queryReadStatus(ApproverReadStatusDTO.builder()
                    .processInstanceId(dto.getProcessInstanceId())
                    .assigner(dto.getAssigner())
                    .build()).stream().collect(Collectors.toMap(SimpleDocDTO::getId, SimpleDocDTO::getReadStatus, (s, t) -> s)));
        }
        ExtAxProcessSign processSign = extAxProcessSignService.findByProcessInstanceId(dto.getProcessInstanceId());
        Map<Long, SignFileDTO> archiveFileMap = new HashMap<>();
        if (Objects.nonNull(processSign)) {
            archiveFileMap.putAll(processSign.getFileArchive().stream().collect(Collectors.toMap(SignFileDTO::getId, Function.identity())));
        }

        return BeanMapper.copyList(docs, DocPendingVO.class, (s, t) -> {
            SignFileDTO archive = archiveFileMap.getOrDefault(t.getId(), null);
            if (Objects.nonNull(archive)) {
                t.setFileRelationId(archive.getFileCode());
                t.setFileKey(archive.getFileKey());
            }
            t.setReadStatus(readStatusMap.getOrDefault(t.getId(), false));
        });
    }
}
