# 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());
        }

    }