ProcessSpecServiceImpl.java

package com.mycim.server.spec.service;

import com.alipay.sofa.runtime.api.annotation.SofaService;
import com.alipay.sofa.runtime.api.annotation.SofaServiceBinding;
import com.fa.sesa.exception.Assert;
import com.fa.sesa.exception.Errors;
import com.fa.sesa.exception.SystemIllegalArgumentException;
import com.fa.sesa.i18n.I18nUtils;
import com.fa.sesa.threadlocal.LocalContext;
import com.mycim.framework.jdbc.Page;
import com.mycim.framework.utils.beans.BeanUtils;
import com.mycim.framework.utils.lang.BooleanUtils;
import com.mycim.framework.utils.lang.StringUtils;
import com.mycim.framework.utils.lang.collections.CollectionUtils;
import com.mycim.framework.utils.lang.collections.MapUtils;
import com.mycim.framework.utils.lang.math.NumberUtils;
import com.mycim.server.base.manager.NamedObjectManager;
import com.mycim.server.base.manager.TransactionLogManager;
import com.mycim.server.edc.manager.EcnManager;
import com.mycim.server.edc.manager.ParameterSetManager;
import com.mycim.server.prp.manager.*;
import com.mycim.server.rcp.manager.RecipeManager;
import com.mycim.server.reticle.manager.ReticleFamilyManager;
import com.mycim.server.spec.manager.*;
import com.mycim.server.system.manager.ReferenceFileManager;
import com.mycim.utils.FieldValidateUtils;
import com.mycim.utils.WflLinkContextSetupAttributeUtil;
import com.mycim.valueobject.MessageIdList;
import com.mycim.valueobject.ObjectList;
import com.mycim.valueobject.Pair;
import com.mycim.valueobject.SystemConstant;
import com.mycim.valueobject.bas.TransactionLog;
import com.mycim.valueobject.consts.StepTypeConst;
import com.mycim.valueobject.consts.TransactionNames;
import com.mycim.valueobject.consts.VersionStatus;
import com.mycim.valueobject.prp.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.Override;
import java.sql.Timestamp;
import java.util.*;
/**
 * @author Qiansheng.Wang
 * @version 1.0
 * @since 2021-03-15
 **/
@Service
@Transactional
@SofaService(interfaceType = ProcessSpecService.class, bindings = {@SofaServiceBinding(bindingType = "bolt")})
public class ProcessSpecServiceImpl implements ProcessSpecService {

    private static final String KEY_INSERT_SPEC_ITEMS = "insertSpecItems";

    private static final String KEY_UPDATE_SPEC_ITEMS = "updateSpecItems";

    private static final String KEY_DELETE_SPEC_ITEMS = "deleteSpecItems";

    @Autowired
    NamedObjectManager namedObjectManager;

    @Autowired
    TransactionLogManager transactionLogManager;

    @Autowired
    ReferenceFileManager referenceFileManager;

    @Autowired
    EcnManager ecnManager;

    @Autowired
    ProcessSpecInfoManager processSpecInfoManager;

    @Autowired
    ProcessSpecItemManager processSpecItemManager;

    @Autowired
    ProductAttributeInfoManager productAttributeInfoManager;

    @Autowired
    ProductAttributeItemManager productAttributeItemManager;

    @Autowired
    ProductSpecInfoManager productSpecInfoManager;

    @Autowired
    AttributeSetupManager attributeSetupManager;

    @Autowired
    AttributeQueryManager attributeQueryManager;

    @Autowired
    AttributeConvertManager attributeConvertManager;

    @Autowired
    SpecManager specManager;

    @Autowired
    ProcessManager processManager;

    @Autowired
    OperationManager operationManager;

    @Autowired
    ProductVersionManager productVersionManager;

    @Autowired
    ProductProcessManager productProcessManager;

    @Autowired
    RecipeManager recipeManager;

    @Autowired
    TimeLimitSetUpManager timeLimitSetupManager;

    @Autowired
    ProcessReworkManager processReworkManager;

    @Autowired
    ProductManager productManager;

    @Autowired
    ParameterSetManager parameterSetManager;

    @Autowired
    ReticleFamilyManager reticleFamilyManager;

    @Autowired
    WorkflowManager workflowManager;

    @Override
    public void saveProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        Assert.isFalse(CollectionUtils.isEmpty(processSpecForm.getSpecItems()),
                       Errors.create().content("no data to save").build());

        processSpecForm.setProcessRrn(
                namedObjectManager.getNamedObjectRrn(processSpecForm.getProcessId(), LocalContext.getFacilityRrn(),
                                                     ObjectList.WFL_KEY));

        Assert.isFalse(processSpecForm.getProcessRrn() <= 0,
                       Errors.create().content("Process ID does not exist.").build());

        Assert.isFalse(isEditableProcessSpec(processSpecForm.getProcessRrn(), processSpecForm.getProcessVersion()),
                       Errors.create()
                             .content("Process Version does not exist or status is UNFROZEN, cannot save spec.")
                             .build());

        List<ProcessSpecItem> willSaveProcessSpecItems = buildSaveProcessSpecItemList(LocalContext.getFacilityRrn(),
                                                                                      processSpecForm.getSpecItems());
        Assert.state(CollectionUtils.isNotEmpty(willSaveProcessSpecItems),Errors.create().key(MessageIdList.NOT_EXIST_WET_LIST).content("list is empty!").build());

        List<ProcessSpecItem> processSpecItemsInDb = processSpecItemManager.getProcessSpecItems(
                processSpecForm.getProcessRrn(), processSpecForm.getProcessVersion());

        ProcessSpecInfo processSpecInfo = getProcessSpecInfoForSave(processSpecForm);

        Map<String, List<ProcessSpecItem>> saveProcessSpecItemMap = new HashMap<>();

        boolean isAlreadyActivated = BooleanUtils.toBoolean(processSpecInfo.getActiveFlag());
        if (isAlreadyActivated) {
            checkIfTheAttributeTypeHasBeenModified(willSaveProcessSpecItems, processSpecItemsInDb);
            saveProcessSpecItemMap.put(KEY_UPDATE_SPEC_ITEMS, willSaveProcessSpecItems);
        } else {
            /*
             If the spec has never been activated, all items need to be inserted/updated/deleted. Because the process
              steps (workflow) may have been modified.
             */
            saveProcessSpecItemMap = buildProcessSpecItemSaveMap(processSpecItemsInDb, willSaveProcessSpecItems);
        }

        TransactionLog transactionLog = transactionLogManager.startTransactionLog(LocalContext.getUserId(),
                                                                                  TransactionNames.SAVE_KEY);

        // Save Process Spec Base Info (Table Status)
        processSpecInfoManager.saveProcessSpecItemInfo(transactionLog, processSpecInfo);

        // Save Process Spec Items.
        processSpecItemManager.saveProcessSpecItems(transactionLog, saveProcessSpecItemMap);

        if (isAlreadyActivated) {
            // If the process spec table is "active". No other operations are required.
            return;
        }

        checkIfFlowSequencesIsValid(willSaveProcessSpecItems);

        List<ProductVersion> allProductVersions = getProductVersions(processSpecInfo.getProcessRrn());

        // Save Product Attribute
        productAttributeItemManager.saveProductAttributeWithReset(transactionLog, processSpecInfo,
                                                                  willSaveProcessSpecItems, allProductVersions);

        // Save Flow Sequence to context value
        Ecn ecn = ecnManager.generateUnFrozenEcnForContext(LocalContext.getUserRrn());

        attributeSetupManager.saveFlowSequenceToContextValue(LocalContext.getFacilityRrn(), ecn,
                                                             willSaveProcessSpecItems);
    }

    @Override
    public void restoreProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        // processSpecForm.setSpecItems(); TODO 重写该方法

        saveProcessSpecItems(processSpecForm);
    }

    @Override
    public List<ProcessSpecItemDto> queryProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        String processId = processSpecForm.getProcessId();
        Integer processVersion = processSpecForm.getProcessVersion();
        String productId = processSpecForm.getProductId();
        long productRrn = productManager.getProductRrnById(productId);

        Long processRrn = namedObjectManager.getNamedObjectRrn(processId, LocalContext.getFacilityRrn(),
                                                               ObjectList.WFL_KEY);

        // TODO 优化查询流程的方法
        List<Map<String, Object>> processFlow = processManager.getProcessAllStepInfo(processRrn, processVersion, null,
                                                                                     null, 99999L, null, null, 0);

        ProcessSpecInfo processSpecInfo = processSpecInfoManager.getProcessSpecInfoByPrimaryKey(processRrn,
                                                                                                processVersion);

        Map<String, ProcessAttributeDto> attributes;
        Integer highestProcessVersion = processVersion;
        if (processSpecInfo == null && processVersion > 1) {
            highestProcessVersion = processSpecInfoManager.getHighestActivatedProcessVersion(processRrn);
            attributes = attributeQueryManager.getProcessAttributesFromTargetVersion(processRrn, processVersion,
                                                                                     highestProcessVersion);
        } else {
            attributes = attributeQueryManager.getProcessAttributesWithSetup(processRrn, processVersion);
        }

        if (processSpecInfo == null) {
            processSpecInfo = new ProcessSpecInfo();

            processSpecInfo.setProcessRrn(processRrn);
            processSpecInfo.setProcessId(processId);
            processSpecInfo.setProcessVersion(processVersion);
        }

        // 构建FlipTypeMap
        Map<String, String> flipTypeMap = attributeConvertManager.getFlipTypeKeyMap();

        ProcessSpecItemBuildParameterDto processSpecItemBuildParameterDto = new ProcessSpecItemBuildParameterDto(
                processSpecInfo).setAttributes(attributes).setStageIdKeyMap(attributeConvertManager.getStageIdKeyMap())
                                .setOperationTypeKeyMap(attributeConvertManager.getOperationTypeKeyMap())
                                .setWorkAreaKeyMap(attributeConvertManager.getWorkAreaKeyMap())
                                .setFlipTypeMap(flipTypeMap);

        return buildProcessSpecItems(processSpecItemBuildParameterDto, highestProcessVersion, processFlow, productRrn);
    }

    @Override
    public ProcessSpecInfo getProcessSpecInfo(ProcessSpecFormDto processSpecForm) {
        Long processRrn = namedObjectManager.getNamedObjectRrn(processSpecForm.getProcessId(),
                                                               LocalContext.getFacilityRrn(), ObjectList.WFL_KEY);

        return processSpecInfoManager.getProcessSpecInfoByPrimaryKey(processRrn, processSpecForm.getProcessVersion());
    }

    @Override
    public void freezeProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        String processId = processSpecForm.getProcessId();
        Integer processVersion = processSpecForm.getProcessVersion();

        Long processRrn = namedObjectManager.getNamedObjectRrn(processId, LocalContext.getFacilityRrn(),
                                                               ObjectList.WFL_KEY);

        ProcessSpecInfo processSpecInfo = processSpecInfoManager.getProcessSpecInfoByPrimaryKey(processRrn,
                                                                                                processVersion);

        Assert.isFalse(
                processSpecInfo == null || !VersionStatus.UNFROZEN_KEY.equals(processSpecInfo.getCurrentStatus()),
                Errors.create().content(
                        "This spec table has never been saved or the status of this spec table is not UNFROZEN. " +
                                "Unable to perform freeze operation.").build());

        Assert.isFalse(isEditableProcessSpec(processRrn, processVersion), Errors.create().content(
                "Process Version does not exist or status is UNFROZEN, cannot freeze spec.").build());

        List<ProcessSpecItem> processSpecItems = processSpecItemManager.getProcessSpecItems(processRrn, processVersion,
                                                                                            VersionStatus.UNFROZEN_KEY);

        StringBuilder errorMsg = new StringBuilder();

        checkIfProcessSpecItemsCanDoFreeze(processSpecItems, errorMsg);

        this.checkFlipType(null, errorMsg, processSpecItemManager.checkFlipSetTogether(processRrn, processVersion));

        Assert.isFalse(errorMsg.length() > 0, Errors.create().content(errorMsg.toString()).build());

        TransactionLog transactionLog = transactionLogManager.startTransactionLog(LocalContext.getUserId(),
                                                                                  TransactionNames.FROZEN_KEY);
        transactionLog.setTransSequence(0L);

        updateStatusOfProcessSpec(transactionLog, processSpecInfo, processSpecItems, null, VersionStatus.FROZEN_KEY);
    }

    @Override
    public void unfreezeProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        String processId = processSpecForm.getProcessId();
        Integer processVersion = processSpecForm.getProcessVersion();

        Long processRrn = namedObjectManager.getNamedObjectRrn(processId, LocalContext.getFacilityRrn(),
                                                               ObjectList.WFL_KEY);

        ProcessSpecInfo processSpecInfo = processSpecInfoManager.getProcessSpecInfoByPrimaryKey(processRrn,
                                                                                                processVersion);

        Assert.isFalse(processSpecInfo == null || !VersionStatus.FROZEN_KEY.equals(processSpecInfo.getCurrentStatus()),
                       Errors.create().content(
                               "This spec table has never been saved or the status of this spec table is not FROZEN. " +
                                       "Unable to perform unfreeze operation.").build());

        List<ProcessSpecItem> processSpecItems = processSpecItemManager.getProcessSpecItems(processRrn, processVersion,
                                                                                            VersionStatus.FROZEN_KEY);

        TransactionLog transactionLog = transactionLogManager.startTransactionLog(LocalContext.getUserId(),
                                                                                  TransactionNames.UNFROZEN_KEY);

        updateStatusOfProcessSpec(transactionLog, processSpecInfo, processSpecItems, null, VersionStatus.UNFROZEN_KEY);
    }

    @Override
    public void activateProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        String processId = processSpecForm.getProcessId();
        Integer processVersion = processSpecForm.getProcessVersion();

        long facilityRrn = LocalContext.getFacilityRrn();

        Long processRrn = namedObjectManager.getNamedObjectRrn(processId, facilityRrn, ObjectList.WFL_KEY);

        ProcessSpecInfo processSpecInfo = processSpecInfoManager.getProcessSpecInfoByPrimaryKey(processRrn,
                                                                                                processVersion);

        Assert.isFalse(processSpecInfo == null || !VersionStatus.FROZEN_KEY.equals(processSpecInfo.getCurrentStatus()),
                       Errors.create().content(
                               "This spec table has never been saved or the status of this spec table is not FROZEN. " +
                                       "Unable to perform activate operation.").build());

        //校验数据完整性
        validationActiveProcessSpecItems(processSpecForm);

        // 若 VERSION_LAST_UPDATED_TIME < LAST_UPDATE_TIME,则表示流程版本被修改过,需重新做 Save 操作。
        if (processSpecInfo != null && processSpecInfo.getVersionLastUpdateTime() != null) {
            ProcessVersion processVersionInfo = processManager.getProcessVersionInfo(processRrn, processVersion);
            Assert.isFalse(
                    processSpecInfo.getVersionLastUpdateTime().before(processVersionInfo.getLastUpdateTimestamp()),
                    Errors.create().key(MessageIdList.SPEC_CHECK_UPDATE_TIME)
                          .content("The process has been modified and needs to be saved again!").build());
        }

        checkIfHasUnfrozenDataInProductAttribute(processRrn, processVersion);

        List<ProcessSpecItem> processSpecItems = processSpecItemManager.getProcessSpecItems(processRrn, processVersion,
                                                                                            VersionStatus.FROZEN_KEY);

        this.checkFlipType(null, null, processSpecItemManager.checkFlipSetTogether(processRrn, processVersion));

        checkIfHasUnfrozenTimeLimit(processSpecItems, processId, processVersion);

        //check Multipath 是否已设定
        Assert.isFalse(specManager.checkMultipath(processId, processRrn, processVersion, null),
                       Errors.create().key(MessageIdList.MULTIPATH_PROCESS_SET)
                             .content("Multipath is not set for this process!").build());

        Ecn ecn = ecnManager.generateActiveEcnForContext(LocalContext.getUserRrn());

        TransactionLog transactionLog = transactionLogManager.startTransactionLog(LocalContext.getUserId(),
                                                                                  TransactionNames.ACTIVE_KEY);

        updateStatusOfProcessSpec(transactionLog, processSpecInfo, processSpecItems, ecn, VersionStatus.ACTIVE_KEY);

        attributeSetupManager.saveProcessAttributeToContextValue(facilityRrn, ecn, processSpecItems);

        if (processSpecInfo != null) {
            //每次535激活都需要更新PRO_CONTEXT_ACTIVE_DTL
            activateProduct(transactionLog, processSpecInfo);
        }
    }

    @Override
    public Page queryProcessSpecItemHistories(Page page, String processId, Integer processVersion, Date queryStartTime,
                                              Date queryEndTime) {
        return processSpecItemManager.queryProcessSpecItemHistories(page, processId, processVersion, queryStartTime,
                                                                    queryEndTime);
    }

    @Override
    public Map<String, Object> checkImportProcessSpecItems(String processId, Integer processVer,
                                                           List<ProcessSpecItemDto> importList) {
        long facilityRrn = LocalContext.getFacilityRrn();

        Map<String, String> stageIdDescKeyMap = attributeConvertManager.getStageIdDescKeyMap();
        Map<String, String> operationTypeDescKeyMap = attributeConvertManager.getOperationTypeDescKeyMap();
        Map<String, String> workAreaDescKeyMap = attributeConvertManager.getWorkAreaDescKeyMap();
        Map<String, String> bankFlagDescKeyMap = attributeConvertManager.getBankFlagDescKeyMap();
        Map<String, String> processLocationKeyMap = attributeConvertManager.getProcessLocationKeyMap();
        Map<String, String> contaminationKeyMap = attributeConvertManager.getContaminationKeyDescMap();
        Map<String, String> flipTypeValueMapMap = attributeConvertManager.getFlipTypeValueMap();
        // List<String> flipTypeArr = ListUtils.newArrayList("A", "B");//, "A To B", "B To A"

        Long processRrn = namedObjectManager.getNamedObjectRrn(processId, facilityRrn, ObjectList.WFL_KEY);

        Map<String, String> allFlowSeq = attributeQueryManager.getAllFlowSeqMap(processRrn, processVer);

        StringBuilder errorMsg = new StringBuilder();
        Map<String, ProcessSpecItemDto> validData = new LinkedHashMap<>();

        Set<String> keySet = new HashSet<>();

        for (int i = 0; i < importList.size(); i++) {
            Integer rowNum = i;
            ProcessSpecItemDto processSpecItem = importList.get(i);

            Assert.isTrue(StringUtils.equalsIgnoreCase(processId, processSpecItem.getProcessId()),
                          Errors.create().content("The process ID is different from the process spec table.").build());

            Assert.isTrue(processVer != null && processVer.equals(processSpecItem.getProcessVersion()),
                          Errors.create().content("The process version is different from the process spec table.")
                                .build());

            StringBuilder rowErrorMsg = new StringBuilder();

            long routeRrn = namedObjectManager.getNamedObjectRrn(processSpecItem.getRouteId(), facilityRrn,
                                                                 ObjectList.WFL_KEY);
            if (routeRrn <= 0) {
                rowErrorMsg.append("Sub Plan dose not existed.<br>");
            }

            long operationRrn = namedObjectManager.getNamedObjectRrn(processSpecItem.getOperationId(), facilityRrn,
                                                                     ObjectList.OPERATION_KEY);
            if (operationRrn <= 0) {
                rowErrorMsg.append("Step dose not existed.<br>");
            }

            if (routeRrn > 0 && operationRrn > 0 && StringUtils.isNotBlank(processSpecItem.getFlowSeq())) {

                if (!FlowSeqGenerator.isValid(processSpecItem.getFlowSeq())) {

                    errorMsg.append("Flow Seq: ").append(processSpecItem.getFlowSeq())
                            .append(" is not valid! it is must be six digits!<br>");

                } else {
                    String flowSeq = FlowSeqGenerator.generate(processSpecItem.getFlowSeq());

                    String oldFlowSeq = allFlowSeq.get(
                            ContextValueUtils.buildKeyByProcess(processRrn, processVer, routeRrn, operationRrn));
                    if (StringUtils.isNotEmpty(oldFlowSeq) && !StringUtils.equals(oldFlowSeq, flowSeq)) {
                        rowErrorMsg.append("Flow Seq: ").append(oldFlowSeq).append(" cannot be modified.<br>");
                    }

                    if (StringUtils.isEmpty(oldFlowSeq) && allFlowSeq.containsValue(flowSeq)) {
                        rowErrorMsg.append("Flow seq: ").append(processSpecItem.getFlowSeq())
                                   .append(" already exists on another step.<br>");
                    }
                }
            } else if (StringUtils.isBlank(processSpecItem.getFlowSeq())) {
                rowErrorMsg.append("Flow seq cannot be empty. <br>");
            }

            String recipeId = processSpecItem.getRecipeId();
            if (StringUtils.isNotBlank(recipeId)) {
                long recipeRrn = namedObjectManager.getNamedObjectRrn(recipeId, facilityRrn, ObjectList.RECIPE_KEY);

                if (recipeRrn <= 0) {
                    rowErrorMsg.append(recipeManager.checkRecipeAndCreate(recipeId));
                } else if (recipeManager.isMainRecipe(recipeRrn) != recipeRrn) {
                    rowErrorMsg.append("Recipe: ").append(recipeId).append(" is chamber recipe!<br>");
                }
            }

            String reticleGroupId = processSpecItem.getReticleFamilyId();
            if (StringUtils.isNotBlank(reticleGroupId)) {
                long reticleGroupRrn = namedObjectManager.getNamedObjectRrn(reticleGroupId, facilityRrn,
                                                                            ObjectList.RETICLEFAMILY_KEY);
                if (reticleGroupRrn <= 0 && ProductVariableEnum.isReticleGroupVariable(reticleGroupId)) {
                    rowErrorMsg.append(reticleFamilyManager.checkReticleFamilyAndCreate(reticleGroupId));
                } else if (reticleGroupRrn <= 0) {
                    rowErrorMsg.append("Reticle Group: ").append(reticleGroupId).append(" dose not existed.<br>");
                }
            }

            String parameterSetId = processSpecItem.getParameterSetId();
            if (StringUtils.isNotBlank(parameterSetId)) {
                long parameterSetRrn = namedObjectManager.getNamedObjectRrn(parameterSetId, facilityRrn,
                                                                            ObjectList.PARAMETERSET_KEY);
                if (parameterSetRrn <= 0 && ProductVariableEnum.isEdcPlanVariable(parameterSetId)) {
                    rowErrorMsg.append(parameterSetManager.checkParameterSetAndCreate(parameterSetId));
                } else if (parameterSetRrn <= 0) {
                    rowErrorMsg.append("EDC Plan: ").append(parameterSetId).append(" dose not existed.<br>");
                }
            }

            if (StringUtils.isNotBlank(processSpecItem.getOperationDesc()) &&
                    processSpecItem.getOperationDesc().getBytes().length > 200) {
                rowErrorMsg.append("Operation Description should smaller than 200.<br>");
            }

            if (StringUtils.isNotEmpty(processSpecItem.getPollutionLevel()) &&
                    (!NumberUtils.isCreatable(processSpecItem.getPollutionLevel()) ||
                            StringUtils.isEmpty(contaminationKeyMap.get(processSpecItem.getPollutionLevel())))) {
                rowErrorMsg.append("Contamination is not a number or contamination does not existed.<br>");
            }

            if (StringUtils.isNotBlank(processSpecItem.getStageId()) &&
                    StringUtils.isBlank(stageIdDescKeyMap.get(processSpecItem.getStageId()))) {
                rowErrorMsg.append("Stage ID: ").append(processSpecItem.getStageId()).append(" not exists.<br> ");
            }

            if (StringUtils.isNotBlank(processSpecItem.getWorkArea()) &&
                    StringUtils.isBlank(workAreaDescKeyMap.get(processSpecItem.getWorkArea()))) {
                rowErrorMsg.append("Work area: ").append(processSpecItem.getWorkArea()).append(" not exists.<br> ");
            }

            if (StringUtils.isNotBlank(processSpecItem.getBankFlag()) &&
                    StringUtils.isBlank(bankFlagDescKeyMap.get(processSpecItem.getBankFlag()))) {
                rowErrorMsg.append("Bank flag: ").append(processSpecItem.getBankFlag()).append(" not exists!<br>");
            }

            if (StringUtils.isNotBlank(processSpecItem.getFlipTypeDesc())) {
                String flipTypeCode = flipTypeValueMapMap.get(processSpecItem.getFlipTypeDesc());
                if (StringUtils.isEmpty(flipTypeCode)) {
                    rowErrorMsg.append("Flip Type: ").append(processSpecItem.getFlipTypeDesc())
                               .append(" not exists!<br>");
                } else {
                    processSpecItem.setFlipType(flipTypeCode);
                }
            }

            if (StringUtils.isNotBlank(processSpecItem.getOperationType()) &&
                    StringUtils.isBlank(operationTypeDescKeyMap.get(processSpecItem.getOperationType()))) {
                rowErrorMsg.append("Operation type: ").append(processSpecItem.getOperationType())
                           .append(" not exists!<br>");
            }

            if (StringUtils.isNotBlank(processSpecItem.getProcessLocation()) &&
                    StringUtils.isBlank(processLocationKeyMap.get(processSpecItem.getProcessLocation()))) {
                rowErrorMsg.append("Process location: ").append(processSpecItem.getProcessLocation())
                           .append(" not exists!<br>");
            }

            if (StringUtils.isNotBlank(processSpecItem.getEquipmentGroupId())) {
                String eqptGroupRrns = "";
                String[] eqptGroupIdArr = processSpecItem.getEquipmentGroupId().split(",");
                for (String eqptGroupId : eqptGroupIdArr) {
                    Long eqptGroupRrn = namedObjectManager.getNamedObjectRrn(eqptGroupId, facilityRrn,
                                                                             ObjectList.ENTITYGROUP_KEY);
                    eqptGroupRrns += eqptGroupRrn + ",";
                    if (eqptGroupRrn <= 0) {
                        rowErrorMsg.append("Eqpt Group: ").append(eqptGroupId).append(" not exists!<br>");
                    }
                }
                eqptGroupRrns = eqptGroupRrns.substring(0, eqptGroupRrns.length() - 1);
                processSpecItem.setEquipmentGroupRrns(eqptGroupRrns);
            }

            String key = processSpecItem.getRouteId() + "#$#" + processSpecItem.getOperationId();
            if (keySet.add(key)) {
                validData.put(key, processSpecItem);
            } else {
                rowErrorMsg.append("The current row (Route and Operation) is duplicated with other rows! <br>");
            }

            if (rowErrorMsg.length() > 0) {
                errorMsg.append(" Row Num: ").append(rowNum).append(" <br>").append(rowErrorMsg);
            }
        }

        this.checkFlipType(importList, errorMsg, false);

        Map<String, Object> map = new HashMap<>();

        map.put("errorMsg", errorMsg.toString());
        map.put("data", validData);

        return map;
    }

    @Override
    public Boolean canRestore(ProcessSpecFormDto processSpecForm) {
        return false;
    }

    @Override
    public ProcessSpecItemDto queryProcessSpecItem(ProcessSpecItemDto processSpecItemDto) {
        ProcessSpecItem psi = processSpecItemManager.getProcessSpecItemForActive(processSpecItemDto);
        ProcessSpecItemDto psid = new ProcessSpecItemDto();
        if (!Objects.isNull(psi)) {
            BeanUtils.copyProperties(psi, psid);
        }
        return psid;
    }

    @Override
    public Map<String, String> getFlipTypeKeyMap() {
        return attributeConvertManager.getFlipTypeKeyMap();
    }

    private boolean isEditableProcessSpec(Long processRrn, Integer processVersion) {
        ProcessVersion processVersionObj = processManager.getProcessVersion(processRrn, processVersion);

        return processVersionObj == null || VersionStatus.UNFROZEN_KEY.equals(processVersionObj.getVersionStatus());
    }

    private List<ProductVersion> getProductVersions(Long processRrn) {
        List<ProductVersion> allProductVersions = new ArrayList<>();
        for (Item product : processManager.getUsedByProducts(processRrn)) {
            List<ProductVersion> allVersions = productVersionManager.getAllVersions(product.getInstanceRrn());
            for (ProductVersion productVersion : allVersions) {
                productVersion.setProductId(product.getInstanceId());
            }
            allProductVersions.addAll(allVersions);
        }
        return allProductVersions;
    }

    private ProcessSpecInfo getProcessSpecInfoForSave(ProcessSpecFormDto processSpecForm) {
        ProcessSpecInfo processSpecInfo = processSpecInfoManager.getProcessSpecInfoByPrimaryKey(
                processSpecForm.getProcessRrn(), processSpecForm.getProcessVersion());
        if (processSpecInfo == null) {
            processSpecInfo = new ProcessSpecInfo();

            processSpecInfo.setProcessRrn(processSpecForm.getProcessRrn());
            processSpecInfo.setProcessId(processSpecForm.getProcessId());
            processSpecInfo.setProcessVersion(processSpecForm.getProcessVersion());
            processSpecInfo.setActiveFlag(false);
        }
        processSpecInfo.setCurrentStatus(VersionStatus.UNFROZEN_KEY);
        return processSpecInfo;
    }

    private void activateProduct(TransactionLog transactionLog, ProcessSpecInfo processSpecInfo) {
        //如果不存在变量,更新 ProductSpecInfo 状态为ACTIVE(所有当前绑定产品的所有版本)
        if (canActivateProducts(processSpecInfo)) {
            List<ProductVersion> allProductVersions = getProductVersions(processSpecInfo.getProcessRrn());
            productSpecInfoManager.activateProductSpecInfos(transactionLog, processSpecInfo, allProductVersions);
        }
        specManager.activateProduct(transactionLog, processSpecInfo.getProcessRrn(),
                                    processSpecInfo.getProcessVersion());
    }

    private boolean canActivateProducts(ProcessSpecInfo processSpecInfo) {
        // isNoAnyVariable
        return CollectionUtils.isEmpty(
                productAttributeInfoManager.getProductAttributeInfosByProcess(processSpecInfo.getProcessRrn(),
                                                                              processSpecInfo.getProcessVersion()));
    }

    private void updateStatusOfProcessSpec(TransactionLog transactionLog, ProcessSpecInfo processSpecInfo,
                                           List<ProcessSpecItem> processSpecItems, Ecn ecn, String status) {
        processSpecInfoManager.updateStatusOfProcessSpecInfo(transactionLog, processSpecInfo, status);
        processSpecItemManager.updateStatusOfProcessSpecItems(transactionLog, processSpecItems, ecn, status);
    }

    /**
     * Check whether the product attributes have unfreeze data. If so, the activation operation cannot be performed
     * and an exception is thrown.
     *
     * @param processRrn     Checked process
     * @param processVersion Checked process version
     */
    private void checkIfHasUnfrozenDataInProductAttribute(Long processRrn, Integer processVersion) {
        List<ProductAttributeItem> productAttributeItems = productAttributeItemManager.getProductAttributeItems(
                processRrn, processVersion);

        boolean hasUnfrozenData = false;
        for (ProductAttributeItem productAttributeItem : productAttributeItems) {
            if (VersionStatus.UNFROZEN_KEY.equals(productAttributeItem.getStatus())) {
                hasUnfrozenData = true;
                break;
            }
        }

        Assert.isFalse(hasUnfrozenData,
                       Errors.create().content("The product attribute has unfrozen data and cannot be activated.")
                             .build());
    }

    private void checkIfFlowSequencesIsValid(List<ProcessSpecItem> processSpecItems) {
        StringBuilder errorMsg = new StringBuilder();

        Set<Integer> flowSeqSet = new LinkedHashSet<>();

        List<String> invalidFlowSeqs = new ArrayList<>();
        List<String> duplicatedFlowSeqs = new ArrayList<>();
        for (ProcessSpecItem processSpecItem : processSpecItems) {

            if (!FlowSeqGenerator.isValid(processSpecItem.getFlowSeq())) {
                invalidFlowSeqs.add(processSpecItem.getFlowSeq());
            }

            if (!flowSeqSet.add(NumberUtils.toInt(processSpecItem.getFlowSeq()))) {
                duplicatedFlowSeqs.add(processSpecItem.getFlowSeq());
            }
        }

        if (invalidFlowSeqs.size() > 0) {
            errorMsg.append("Flow Seq: ");
            errorMsg.append(StringUtils.join(", ", invalidFlowSeqs));
            errorMsg.append(" is not valid! it is must be six digits!<br>");
        }

        if (duplicatedFlowSeqs.size() > 0) {
            errorMsg.append("Flow Seq: ");
            errorMsg.append(StringUtils.join(", ", duplicatedFlowSeqs));
            errorMsg.append(" can not be duplicated!<br>");
        }

        List<String> outOfOrderFlowSeqs = new ArrayList<>();
        Integer[] flowSeqArray = flowSeqSet.toArray(new Integer[0]);
        for (int i = 1; i < flowSeqArray.length - 1; i++) {

            Integer flowSeq = flowSeqArray[i];
            Integer lastFlowSeq = flowSeqArray[i - 1];
            Integer nextFlowSeq = flowSeqArray[i + 1];

            if (flowSeq - lastFlowSeq < 0 || flowSeq - nextFlowSeq > 0) {
                outOfOrderFlowSeqs.add(FlowSeqGenerator.generate(flowSeq));
            }
        }

        if (outOfOrderFlowSeqs.size() > 0) {
            errorMsg.append("Reseq should be between the previous step and next step Reseq! the unqualified Reseq:");
            errorMsg.append(StringUtils.join(", ", outOfOrderFlowSeqs)).append("<br>");
        }

        Assert.isFalse(errorMsg.length() > 0, Errors.create().content(errorMsg.toString()).build());
    }

    private void checkIfTheAttributeTypeHasBeenModified(List<ProcessSpecItem> processSpecItemsInDb,
                                                        List<ProcessSpecItem> willSaveProcessSpecItems) {
        StringBuilder errorMsg = new StringBuilder();

        for (ProcessSpecItem processSpecItemTemp : willSaveProcessSpecItems) {
            int index = processSpecItemsInDb.indexOf(processSpecItemTemp);
            if (index < 0) {
                continue;
            }

            ProcessSpecItem processSpecItem = processSpecItemsInDb.get(index);

            errorMsg.append(
                    checkIfAttributeCanBeModified(processSpecItem.getFlowSeq(), processSpecItemTemp.getRecipeId(),
                                                  processSpecItem.getRecipeId(), "Recipe"));
            errorMsg.append(
                    checkIfAttributeCanBeModified(processSpecItem.getFlowSeq(), processSpecItemTemp.getParameterSetId(),
                                                  processSpecItem.getParameterSetId(), "EDC Plan"));
            errorMsg.append(checkIfAttributeCanBeModified(processSpecItem.getFlowSeq(),
                                                          processSpecItemTemp.getReticleFamilyId(),
                                                          processSpecItem.getReticleFamilyId(), "Reticle Group"));
        }

        Assert.isFalse(errorMsg.length() > 0, Errors.create().content(errorMsg.toString()).build());
    }

    private String checkIfAttributeCanBeModified(String flowSeq, String sourceValue, String targetValue, String type) {
        if (!StringUtils.equals(sourceValue, targetValue)) {
            boolean oldIsVariable = ProductVariableEnum.isVariable(sourceValue);
            boolean newIsVariable = ProductVariableEnum.isVariable(targetValue);

            if (oldIsVariable && !newIsVariable) {
                return "Cannot modify the variable of the " + type + " (flow seq " + flowSeq + ") into a constant!<br>";
            }

            if (!oldIsVariable && newIsVariable) {
                return "Cannot modify the constant of the " + type + " (flow seq " + flowSeq + ") into a variable!<br>";
            }
        }
        return StringUtils.EMPTY;
    }

    private Map<String, List<ProcessSpecItem>> buildProcessSpecItemSaveMap(List<ProcessSpecItem> processSpecItemsInDb,
                                                                           List<ProcessSpecItem> willSaveProcessSpecItems) {
        Map<String, List<ProcessSpecItem>> saveProcessSpecItems = new HashMap<>();

        saveProcessSpecItems.put(KEY_INSERT_SPEC_ITEMS, new ArrayList<>());
        saveProcessSpecItems.put(KEY_UPDATE_SPEC_ITEMS, new ArrayList<>());
        saveProcessSpecItems.put(KEY_DELETE_SPEC_ITEMS, new ArrayList<>());

        for (ProcessSpecItem processSpecItemTemp : willSaveProcessSpecItems) {
            if (processSpecItemsInDb.contains(processSpecItemTemp)) {
                // If the spec item already exists, it will be updated
                saveProcessSpecItems.get(KEY_UPDATE_SPEC_ITEMS).add(processSpecItemTemp);
            } else {
                // If the spec item does not exists, it will be inserted
                saveProcessSpecItems.get(KEY_INSERT_SPEC_ITEMS).add(processSpecItemTemp);
            }
        }

        for (ProcessSpecItem processSpecItemTemp : processSpecItemsInDb) {
            if (!willSaveProcessSpecItems.contains(processSpecItemTemp)) {
                // If the spec item already exists but is not in the willSave list, delete it
                saveProcessSpecItems.get(KEY_DELETE_SPEC_ITEMS).add(processSpecItemTemp);
            }
        }
        return saveProcessSpecItems;
    }

    private List<ProcessSpecItem> buildSaveProcessSpecItemList(Long facilityRrn,
                                                               List<ProcessSpecItemDto> processSpecItems) {
        Map<String, String> stageIdDescKeyMap = attributeConvertManager.getStageIdDescKeyMap();
        Map<String, String> operationTypeDescKeyMap = attributeConvertManager.getOperationTypeDescKeyMap();
        Map<String, String> workAreaDescKeyMap = attributeConvertManager.getWorkAreaDescKeyMap();
        Map<String, String> processLocationKeyMap = attributeConvertManager.getProcessLocationKeyMap();
        Map<String, String> contaminationKeyMap = attributeConvertManager.getContaminationKeyDescMap();

        StringBuilder errorMsg = new StringBuilder();
        int rowNum = 1;
        List<ProcessSpecItem> willSaveProcessSpecItems = new ArrayList<>();
        for (ProcessSpecItemDto processSpecItemTemp : processSpecItems) {
            ProcessSpecItem processSpecItem = new ProcessSpecItem();

            BeanUtils.copyProperties(processSpecItemTemp, processSpecItem);

            if (processSpecItemTemp.getCreatedTime() != null) {
                processSpecItem.setCreatedTime(new Timestamp(processSpecItemTemp.getCreatedTime().getTime()));
            }

            StringBuilder rowErrorMsg = new StringBuilder();

            if (StringUtils.isEmpty(processSpecItem.getFlowSeq())) {
                rowErrorMsg.append("Flow Seq can not be empty.<br>");
            }

            checkIfRecipeIsValidAndSet(facilityRrn, processSpecItem, rowErrorMsg);

            checkIfReticleFamilyIsValidAndSet(facilityRrn, processSpecItem, rowErrorMsg);

            checkIfParameterSetIsValidAndSet(facilityRrn, processSpecItem, rowErrorMsg);

            if (StringUtils.isBlank(processSpecItem.getEquipmentGroupId())) {
                Long equipmentGroupRrn = operationManager.getOperationEquipmentGroupRrn(
                        processSpecItem.getOperationRrn());
                if (equipmentGroupRrn != null) {
                    processSpecItem.setEquipmentGroupId(namedObjectManager.getInstanceId(equipmentGroupRrn));
                    processSpecItem.setEquipmentGroupRrns(StringUtils.toString(equipmentGroupRrn));
                }
            } else if (processSpecItem.getEquipmentGroupId().length() > 512) {
                rowErrorMsg.append("Equipment Group ID should smaller than 512.<br>");
            }

            if (StringUtils.isNotEmpty(processSpecItem.getStageId())) {
                String stageId = stageIdDescKeyMap.get(processSpecItem.getStageId());
                if (StringUtils.isEmpty(stageId)) {
                    rowErrorMsg.append("Stage ID does not existed.<br>");
                }
                processSpecItem.setStageId(stageId);
            }

            if (StringUtils.isNotEmpty(processSpecItem.getWorkArea())) {
                String workArea = workAreaDescKeyMap.get(processSpecItem.getWorkArea());
                if (StringUtils.isEmpty(workArea)) {
                    rowErrorMsg.append("Work Area does not existed.<br>");
                }
                processSpecItem.setWorkArea(workArea);
            }

            if (StringUtils.isNotEmpty(processSpecItem.getOperationType())) {
                String operationType = operationTypeDescKeyMap.get(processSpecItem.getOperationType());
                if (StringUtils.isEmpty(operationType)) {
                    rowErrorMsg.append("Operation Type does not existed.<br>");
                }
                processSpecItem.setOperationType(operationType);
            }

            if (StringUtils.isNotEmpty(processSpecItem.getOperationDesc()) &&
                    processSpecItem.getOperationDesc().getBytes().length > 200) {
                rowErrorMsg.append("Operation Description should smaller than 200.<br>");
            }

            processSpecItem.setPollutionLevel(StringUtils.trimToEmpty(processSpecItem.getPollutionLevel()));
            if (StringUtils.isNotEmpty(processSpecItem.getPollutionLevel()) &&
                    (!NumberUtils.isCreatable(processSpecItem.getPollutionLevel()) ||
                            StringUtils.isEmpty(contaminationKeyMap.get(processSpecItem.getPollutionLevel())))) {
                rowErrorMsg.append("Contamination is not a number or contamination does not existed.<br>");
            }

            if (StringUtils.isNotEmpty(processSpecItem.getProcessLocation()) &&
                    StringUtils.isEmpty(processLocationKeyMap.get(processSpecItem.getProcessLocation()))) {
                rowErrorMsg.append("Process Location does not existed.<br>");
            }

            /* 优化 #45306 新增EDC PLAN验证,长度128位以内。字符必须为字母 或 数字 或 英文半角减号- 或 英文半角下划线_ 或者 英文半角@ 或 英文半角$ 或 英文半角%  */
            if(StringUtils.isNotBlank(processSpecItem.getParameterSetId())){
                if (StringUtils.length(processSpecItem.getParameterSetId()) < 128) {
                    String result = FieldValidateUtils.validateSpecialChar(processSpecItem.getParameterSetId());
                    if(StringUtils.isNotBlank(result)){
                        rowErrorMsg.append("EdcPlan "+result);
                    }
                } else {
                    rowErrorMsg.append("EDCPLAN ID length cannot more than 128!.<br />");
                }
            }

            /*优化 #45305 新增Reticle Group验证,长度128位以内。字符必须为字母 或 数字 或 英文半角减号- 或 英文半角下划线_ 或者 英文半角@ 或 英文半角$ 或 英文半角%   */
            if(StringUtils.isNotEmpty(processSpecItem.getReticleFamilyId())){
                if (StringUtils.length(processSpecItem.getReticleFamilyId()) < 128) {
                    String result = FieldValidateUtils.validateSpecialChar(processSpecItem.getReticleFamilyId());
                    if(StringUtils.isNotBlank(result)){
                        rowErrorMsg.append("Recticle Group "+result);
                        // break;
                    }
                } else {
                    rowErrorMsg.append("Recticle Group ID length cannot more than 128!.<br />");
                }
            }

            if (StringUtils.isNotEmpty(processSpecItem.getRecipeId())) {
                //错误 #45489 允许$R 创建 与 Recipe setup 一致 删除循环式判断
                String msg = FieldValidateUtils.validateRecipeId(processSpecItem.getRecipeId());
                if(StringUtils.isNotEmpty(msg)) {
                    rowErrorMsg.append(msg);
                }
            }

            if (rowErrorMsg.length() > 0) {
                errorMsg.append(" Row Num: ").append(rowNum).append("<br>").append(rowErrorMsg);
            }

            willSaveProcessSpecItems.add(processSpecItem);
            rowNum++;
        }

        Assert.isFalse(errorMsg.length() > 0, Errors.create().content(errorMsg.toString()).build());

        return willSaveProcessSpecItems;
    }

    private void checkIfParameterSetIsValidAndSet(Long facilityRrn, ProcessSpecItem processSpecItem,
                                                  StringBuilder rowErrorMsg) {
        processSpecItem.setParameterSetId(StringUtils.trimToUpperCase(processSpecItem.getParameterSetId()));
        if (StringUtils.isNotEmptyTrim(processSpecItem.getParameterSetId())) {
            processSpecItem.setParameterSetRrn(
                    namedObjectManager.getNamedObjectRrn(processSpecItem.getParameterSetId(), facilityRrn,
                                                         ObjectList.PARAMETERSET_KEY));
            if (processSpecItem.getParameterSetRrn() == null || processSpecItem.getParameterSetRrn() <= 0) {
                if (ProductVariableEnum.isEdcPlanVariable(processSpecItem.getParameterSetId())) {
                    rowErrorMsg.append(
                            parameterSetManager.checkParameterSetAndCreate(processSpecItem.getParameterSetId()));
                    processSpecItem.setParameterSetRrn(
                            namedObjectManager.getNamedObjectRrn(processSpecItem.getParameterSetId(), facilityRrn,
                                                                 ObjectList.PARAMETERSET_KEY));
                } else {
                    rowErrorMsg.append("EDC Plan dose not existed.<br>");
                }
            }
            processSpecItem.setVariableFlag(StringUtils.startsWith(processSpecItem.getParameterSetId(),
                                                                   ProductVariableEnum.EDCPLAN_VARIABLE_PREFIX.getValue()));
        }
    }

    private void checkIfReticleFamilyIsValidAndSet(Long facilityRrn, ProcessSpecItem processSpecItem,
                                                   StringBuilder rowErrorMsg) {
        processSpecItem.setReticleFamilyId(StringUtils.trimToUpperCase(processSpecItem.getReticleFamilyId()));
        if (StringUtils.isNotEmptyTrim(processSpecItem.getReticleFamilyId())) {
            processSpecItem.setReticleFamilyRrn(
                    namedObjectManager.getNamedObjectRrn(processSpecItem.getReticleFamilyId(), facilityRrn,
                                                         ObjectList.RETICLEFAMILY_KEY));
            if (processSpecItem.getReticleFamilyRrn() == null || processSpecItem.getReticleFamilyRrn() <= 0) {
                if (ProductVariableEnum.isReticleGroupVariable(processSpecItem.getReticleFamilyId())) {
                    rowErrorMsg.append(
                            reticleFamilyManager.checkReticleFamilyAndCreate(processSpecItem.getReticleFamilyId()));
                    processSpecItem.setReticleFamilyRrn(
                            namedObjectManager.getNamedObjectRrn(processSpecItem.getReticleFamilyId(), facilityRrn,
                                                                 ObjectList.RETICLEFAMILY_KEY));
                } else {
                    rowErrorMsg.append("Reticle Group dose not existed.<br>");
                }
            }
            processSpecItem.setVariableFlag(StringUtils.startsWith(processSpecItem.getReticleFamilyId(),
                                                                   ProductVariableEnum.RTL_VARIABLE_PREFIX.getValue()));

        }
    }

    private void checkIfRecipeIsValidAndSet(Long facilityRrn, ProcessSpecItem processSpecItem,
                                            StringBuilder rowErrorMsg) {
        processSpecItem.setRecipeId(StringUtils.trimToUpperCase(processSpecItem.getRecipeId()));
        if (StringUtils.isNotEmptyTrim(processSpecItem.getRecipeId())) {
            processSpecItem.setRecipeRrn(
                    namedObjectManager.getNamedObjectRrn(processSpecItem.getRecipeId(), facilityRrn,
                                                         ObjectList.RECIPE_KEY));
            if (processSpecItem.getRecipeRrn() == null || processSpecItem.getRecipeRrn() <= 0) {
                rowErrorMsg.append(recipeManager.checkRecipeAndCreate(processSpecItem.getRecipeId()));
                processSpecItem.setRecipeRrn(
                        namedObjectManager.getNamedObjectRrn(processSpecItem.getRecipeId(), facilityRrn,
                                                             ObjectList.RECIPE_KEY));
            } else if (recipeManager.isMainRecipe(processSpecItem.getRecipeRrn()) != processSpecItem.getRecipeRrn()) {
                rowErrorMsg.append("Recipe: ").append(processSpecItem.getRecipeId()).append(" is chamber recipe!<br>");
            }

            processSpecItem.setVariableFlag(StringUtils.startsWith(processSpecItem.getRecipeId(),
                                                                   ProductVariableEnum.RECIPE_VARIABLE_PREFIX.getValue()));
        } else {
            processSpecItem.setRecipeId("");
        }
    }


    private List<ProcessSpecItemDto> buildProcessSpecItems(ProcessSpecItemBuildParameterDto parameter,
                                                           Integer highestProcessVersion,
                                                           List<Map<String, Object>> processFlow, Long productRrn) {
        Long processRrn = parameter.getProcessSpecInfo().getProcessRrn();
        Integer processVersion = parameter.getProcessSpecInfo().getProcessVersion();
        String processId = parameter.getProcessSpecInfo().getProcessId();

        List<Pair<Long, Integer>> reworkRoutes = new ArrayList<>();

        List<ProcessSpecItemDto> processSpecItems = new ArrayList<>();
        for (Map<String, Object> map : processFlow) {
            ProcessSpecItemDto processSpecItem = new ProcessSpecItemDto();

            processSpecItem.setProcessRrn(processRrn);
            processSpecItem.setProcessId(processId);
            processSpecItem.setProcessVersion(processVersion);

            processSpecItem.setRouteRrn(MapUtils.getLong(map, "routeRrn"));
            processSpecItem.setRouteId(MapUtils.getString(map, "routeId", StringUtils.EMPTY));
            processSpecItem.setRouteVersion(MapUtils.getInteger(map, "routeVersion"));
            processSpecItem.setRouteSeq(MapUtils.getString(map, "routeProcessSeq", StringUtils.EMPTY));

            processSpecItem.setOperationRrn(MapUtils.getLong(map, "operationRrn"));
            processSpecItem.setOperationId(MapUtils.getString(map, "operationId", StringUtils.EMPTY));
            processSpecItem.setOperationSeq(MapUtils.getString(map, "operationRouteSeq", StringUtils.EMPTY));

            // Default Value
            processSpecItem.setOperationType(
                    parameter.getOperationTypeDesc(MapUtils.getString(map, "operationType", StringUtils.EMPTY)));
            processSpecItem.setWorkArea(
                    parameter.getWorkAreaDesc(MapUtils.getString(map, "workArea", StringUtils.EMPTY)));


            processSpecItem.setIsCopyFromLastVer(!processVersion.equals(highestProcessVersion));

            ProcessAttributeDto attribute = parameter.getAttribute(
                    ContextValueUtils.buildKeyByProcess(processSpecItem.getProcessRrn(),
                                                        processSpecItem.getProcessVersion(),
                                                        processSpecItem.getRouteRrn(),
                                                        processSpecItem.getOperationRrn()));

            if (attribute != null) {
                if (StringUtils.isBlank(attribute.getProcessSpecItem().getEquipmentGroupRrns())) {
                    Long equipmentGroupRrn = operationManager.getOperationEquipmentGroupRrn(
                            processSpecItem.getOperationRrn());
                    if (equipmentGroupRrn != null) {
                        processSpecItem.setEquipmentGroupId(namedObjectManager.getInstanceId(equipmentGroupRrn));
                        processSpecItem.setEquipmentGroupRrns(StringUtils.toString(equipmentGroupRrn));
                    }
                } else {
                    processSpecItem.setEquipmentGroupRrns(attribute.getProcessSpecItem().getEquipmentGroupRrns());
                    processSpecItem.setEquipmentGroupId(attribute.getProcessSpecItem().getEquipmentGroupId());
                }


                processSpecItem.setOperationDesc(attribute.getProcessSpecItem().getOperationDesc());

                if (StringUtils.isNotBlank(attribute.getProcessSpecItem().getOperationType())) {
                    processSpecItem.setOperationType(
                            parameter.getOperationTypeDesc(attribute.getProcessSpecItem().getOperationType()));
                }

                processSpecItem.setFlowSeq(attribute.getProcessSpecItem().getFlowSeq());

                if (StringUtils.isNotBlank(attribute.getProcessSpecItem().getWorkArea())) {
                    processSpecItem.setWorkArea(
                            parameter.getWorkAreaDesc(attribute.getProcessSpecItem().getWorkArea()));

                    if (StringUtils.isBlank(processSpecItem.getWorkArea())) {
                        // 兼容老数据
                        processSpecItem.setWorkArea(attribute.getProcessSpecItem().getWorkArea());
                    }
                }

                processSpecItem.setStageId(parameter.getStageIdDesc(attribute.getProcessSpecItem().getStageId()));

                processSpecItem.setRecipeId(attribute.getProcessSpecItem().getRecipeId());
                processSpecItem.setReticleFamilyId(attribute.getProcessSpecItem().getReticleFamilyId());
                processSpecItem.setParameterSetId(attribute.getProcessSpecItem().getParameterSetId());

                processSpecItem.setPollutionLevel(attribute.getProcessSpecItem().getPollutionLevel());
                processSpecItem.setProcessLocation(attribute.getProcessSpecItem().getProcessLocation());

                processSpecItem.setBankFlag(attribute.getProcessSpecItem().getBankFlag());
                processSpecItem.setFlipType(attribute.getProcessSpecItem().getFlipType());
                processSpecItem.setFlipTypeDesc(parameter.getFlipTypeDesc(processSpecItem.getFlipType()));

                if (!processSpecItem.getIsCopyFromLastVer()) {
                    processSpecItem.setEcnRrn(attribute.getProcessSpecItem().getEcnRrn());
                    processSpecItem.setStatus(attribute.getProcessSpecItem().getStatus());
                    processSpecItem.setEffectiveTime(attribute.getProcessSpecItem().getEffectiveTime());
                    processSpecItem.setTerminatedTime(attribute.getProcessSpecItem().getTerminatedTime());
                    processSpecItem.setCreatedUser(attribute.getProcessSpecItem().getCreatedUser());
                    processSpecItem.setCreatedTime(attribute.getProcessSpecItem().getCreatedTime());
                    processSpecItem.setUpdatedUser(attribute.getProcessSpecItem().getUpdatedUser());
                    processSpecItem.setUpdatedTime(attribute.getProcessSpecItem().getUpdatedTime());
                }
            }

            processSpecItem.setTimeLimitInfo(getOperationQueueTimeFilterProduct(processSpecItem, productRrn));

            processSpecItem.setIsMain(BooleanUtils.toBoolean(MapUtils.getIntValue(map, "isMain")));
            processSpecItem.setIsRework(BooleanUtils.toBoolean(MapUtils.getIntValue(map, "isRework")));

            if (attribute != null && attribute.getProcessRework() != null) {
                ProcessReworkDto processReworkDTO = attribute.getProcessRework();
                processSpecItem.setReworkRouteId(
                        namedObjectManager.getInstanceId(processReworkDTO.getReworkRouteRrn()));
                processSpecItem.setReworkRouteVersion(processReworkDTO.getReworkRouteVersion());
                processSpecItem.setMaxReworkTimes(StringUtils.toString(processReworkDTO.getMaxReworkTimes()));

                reworkRoutes.add(
                        new Pair<>(processReworkDTO.getReworkRouteRrn(), processReworkDTO.getReworkRouteVersion()));
            }

            processSpecItems.add(processSpecItem);
        }

        if (CollectionUtils.isNotEmpty(reworkRoutes)) {
            for (Pair<Long, Integer> reworkRoute : reworkRoutes) {
                Long reworkRouteRrn = reworkRoute.getKey();
                Integer reworkRouteVersion = reworkRoute.getValue();

                if (parameter.getReworkRrnInProcessSet().add(reworkRouteRrn)) {

                    List<Map<String, Object>> reworkRouteFlow = getReworkRouteFlow(reworkRouteRrn, reworkRouteVersion);

                    processSpecItems.addAll(
                            buildProcessSpecItems(parameter, highestProcessVersion, reworkRouteFlow, productRrn));
                }
            }
        }

        //将multipath step对应的default step做上标记
        return setMultipathAndDefaultStepFlag(processSpecItems);
    }

    /**
     * 将process对应的multipath和default step都做上标记
     **/
    public List<ProcessSpecItemDto> setMultipathAndDefaultStepFlag(List<ProcessSpecItemDto> nodes) {
        if(CollectionUtils.isEmpty(nodes)){
            return new ArrayList<>();
        }
        List<Map<String,Object>> multipathAndDefultWfls = new ArrayList<>();
        List<Map<String,Object>> multipathAndDefultWflByOperation = specManager.getMultipathAndDefaultSteps(nodes.get(0).getProcessRrn(), nodes.get(0).getProcessVersion(), WflLinkContextSetupAttributeUtil.OPERATION_FLAG);
        multipathAndDefultWfls.addAll(multipathAndDefultWflByOperation);
        List<Map<String,Object>> multipathAndDefultWflByRoute = specManager.getMultipathAndDefaultSteps(nodes.get(0).getProcessRrn(), nodes.get(0).getProcessVersion(), WflLinkContextSetupAttributeUtil.ROUTE_FLAG);
        multipathAndDefultWfls.addAll(multipathAndDefultWflByRoute);

        if(CollectionUtils.isNotEmpty(multipathAndDefultWfls)){
            for(ProcessSpecItemDto node : nodes){
                for(Map<String,Object> multipathAndDefultWfl : multipathAndDefultWfls){
                    Long operationRrn = MapUtils.getLong(multipathAndDefultWfl,"operationRrn");
                    Long routeRrn = MapUtils.getLong(multipathAndDefultWfl,"routeRrn");
                    if(operationRrn.equals(node.getOperationRrn()) && routeRrn.equals(node.getRouteRrn())){
                        node.setMultipathAndDefaultStepFlag("1");
                        break;
                    }
                }
            }
        }
        return nodes;
    }

    private List<Map<String, Object>> getReworkRouteFlow(Long reworkRouteRrn, Integer reworkRouteVersion) {
        // 兼容 Rework 加 Rework Route Version
        List<Map<String, Object>> reworkRouteFlow;
        if (reworkRouteVersion == null || reworkRouteVersion <= 0) {
            reworkRouteFlow = processReworkManager.getReworkWorkflow(reworkRouteRrn);
        } else {
            reworkRouteFlow = processReworkManager.getReworkWorkflow(reworkRouteRrn, reworkRouteVersion);
        }
        return reworkRouteFlow;
    }

    private String getOperationQueueTimeFilterProduct(ProcessSpecItemDto processSpecItem, Long productRrn) {
        TimeLimitSetup timeLimitSetup = new TimeLimitSetup();
        timeLimitSetup.setStartProductRrn(productRrn);
        timeLimitSetup.setStartProcessRrn(processSpecItem.getProcessRrn());
        timeLimitSetup.setStartProcessVersion(processSpecItem.getProcessVersion());
        timeLimitSetup.setStartRouteRrn(processSpecItem.getRouteRrn());
        timeLimitSetup.setStartOperationRrn(processSpecItem.getOperationRrn());

        List<TimeLimitSetup> timeLimitSetups = timeLimitSetupManager.getTimeLimitSetupsByStartInfo(timeLimitSetup);

        List<TimeLimitSetup> realTimeLimitSetups = new ArrayList<>();
        //若没有productRrn则显示公共设置
        if (productRrn == null || productRrn.longValue() <= 0) {
            for (TimeLimitSetup limitSetup : timeLimitSetups) {
                if (limitSetup.getStartProductRrn().longValue() == 0) {
                    realTimeLimitSetups.add(limitSetup);
                }
            }
        }
        //否则只显示该product的设置
        else {
            for (TimeLimitSetup limitSetup : timeLimitSetups) {
                if (limitSetup.getStartProductRrn().longValue() > 0) {
                    realTimeLimitSetups.add(limitSetup);
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        if (CollectionUtils.isNotEmpty(realTimeLimitSetups)) {
            for (TimeLimitSetup temp : realTimeLimitSetups) {
                sb.append(temp.getInstanceId()).append(" ").append(temp.getTimeLimit()).append(";");
            }
        }
        return sb.toString();
    }

    private void checkIfProcessSpecItemsCanDoFreeze(List<ProcessSpecItem> processSpecItems, StringBuilder errorMsg) {
        for (ProcessSpecItem processSpecItem : processSpecItems) {
            StringBuilder rowErrorMsg = new StringBuilder();

            if (StepTypeConst.isMeasurementStep(processSpecItem.getOperationType()) &&
                    processSpecItem.getParameterSetRrn() == 0) {
                rowErrorMsg.append("Operation Type is M must be set EDC Plan ");
            }

            if (rowErrorMsg.length() > 0) {
                errorMsg.append(" Flow Seq: ").append(processSpecItem.getFlowSeq()).append("<br>").append(rowErrorMsg);
            }
        }
    }

    private void checkIfHasUnfrozenTimeLimit(List<ProcessSpecItem> processSpecItems, String processId,
                                             Integer processVersion) {
        Page page = new Page(1, Integer.MAX_VALUE);

        page = timeLimitSetupManager.getTimeLimitSetupForTemp(page, StringUtils.EMPTY, processId, processVersion);

        /**
         * 从TIMELIMIT_SETUP_TMP表能查询出数据,当前流程版本下有未激活的Q-Timne
         * 因为当Q-Time激活时,会删除TIMELIMIT_SETUP_TMP表中对应的数据
         */
        Assert.isFalse(CollectionUtils.isNotEmpty(page.getResults()),
                       Errors.create().key(MessageIdList.SPEC_PROCESS_TIMELIMIT_NO_ACTION)
                             .content("The process has unfrozen Q-Time and cannot be activated.").build());
    }

    private void checkFlipType(List<ProcessSpecItemDto> processSpecItems, StringBuilder errorMsg,
                               boolean confirmNotTogether) {
        boolean flag;
        if (confirmNotTogether) {
            flag = true;
        } else {
            if (CollectionUtils.isNotEmpty(processSpecItems)) {
                int size = processSpecItems.size();
                int count = 0;
                for (ProcessSpecItemDto psi : processSpecItems) {
                    if (StringUtils.isNotBlank(psi.getFlipType())) {
                        count++;
                    }
                }
                flag = count != 0 && size != count;
            } else {
                flag = false;
            }
        }
        if (flag) {
            if (errorMsg == null) {
                throw new SystemIllegalArgumentException(Errors.create().key(MessageIdList.FLIP_SET_TOGETHER)
                                                               .content("Flip properties need to be set together!")
                                                               .build());
            } else {
                errorMsg.append(I18nUtils.getMessage(MessageIdList.FLIP_SET_TOGETHER,
                                                     "Flip properties need to be set together!")).append("<br>");
            }
        }
    }

    private void validationActiveProcessSpecItems(ProcessSpecFormDto processSpecForm) {
        //验证active数据完整性,防止有新增的step但未刷新的情况
        List<ProcessSpecItemDto> processSpecItemDtos = queryProcessSpecItems(processSpecForm);

        boolean validation = true;
        for (ProcessSpecItemDto processSpecItemDto : processSpecItemDtos) {
            if (!validation) {
                break;
            }
            if (StringUtils.isBlank(processSpecItemDto.getFlowSeq()) ||
                    StringUtils.isBlank(processSpecItemDto.getWorkArea()) ||
                    StringUtils.isBlank(processSpecItemDto.getOperationDesc()) ||
                    StringUtils.isBlank(processSpecItemDto.getStageId()) ||
                    StringUtils.isBlank(processSpecItemDto.getRecipeId()) ||
                    StringUtils.isBlank(processSpecItemDto.getProcessLocation()) ||
                    StringUtils.isBlank(processSpecItemDto.getEquipmentGroupId())) {
                validation = false;
            }
        }
        //提示用户点击查询刷新数据并填写必填的数据
        Assert.isTrue(validation, Errors.create().key(MessageIdList.PRODSPEC_INCOMPLETE_DATA).content(
                "The data is incomplete, please click the query button to refresh the data and supplement the data")
                                        .build());
    }

}