# PcdManagerImpl.java

    package com.mycim.server.carrier.manager.impl;

    import com.fa.sesa.exception.Assert;
    import com.fa.sesa.exception.Errors;
    import com.fa.sesa.i18n.I18nUtils;
    import com.fa.sesa.threadlocal.LocalContext;
    import com.mycim.framework.utils.lang.ObjectUtils;
    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.framework.utils.lang.time.DateUtils;
    import com.mycim.server.base.manager.EventManager;
    import com.mycim.server.base.manager.NamedObjectManager;
    import com.mycim.server.base.manager.TransactionLogManager;
    import com.mycim.server.carrier.dao.DoorDAO;
    import com.mycim.server.carrier.dao.PcdDAO;
    import com.mycim.server.carrier.dao.PodDAO;
    import com.mycim.server.carrier.manager.CarrierManager;
    import com.mycim.server.carrier.manager.DoorManager;
    import com.mycim.server.carrier.manager.PcdManager;
    import com.mycim.server.carrier.manager.PodManager;
    import com.mycim.server.ems.manager.EntityManager;
    import com.mycim.server.status.manager.StatusManager;
    import com.mycim.server.system.manager.ReferenceFileManager;
    import com.mycim.utils.WipUtils;
    import com.mycim.valueobject.Constants;
    import com.mycim.valueobject.MessageIdList;
    import com.mycim.valueobject.ObjectList;
    import com.mycim.valueobject.bas.NamedObject;
    import com.mycim.valueobject.bas.TransactionLog;
    import com.mycim.valueobject.consts.EventName;
    import com.mycim.valueobject.consts.PcdStatus;
    import com.mycim.valueobject.consts.ReferenceDetailNames;
    import com.mycim.valueobject.consts.TransactionNames;
    import com.mycim.valueobject.ems.*;
    import com.mycim.valueobject.sys.ReferenceFileDetail;
    import com.mycim.valueobject.wip.Lot;
    import com.mycim.valueobject.wip.TransReason;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.sql.Timestamp;
    import java.text.DecimalFormat;
    import java.util.*;
    import java.util.function.BiConsumer;
    import java.util.function.Consumer;
    import java.util.regex.Matcher;
    import java.util.stream.Collectors;

    /**
     * @author yanbing.chen
     * @date 2019/10/10
     * @since 1.8
     **/
    @Service
    @Transactional
    public class PcdManagerImpl implements PcdManager {

        private final static String PCD_CLEAN_EQPT_REFERENCE_ID = "$PCD_CLEAN_EQPT";

        @Autowired
        PcdDAO pcdDAO;

        @Autowired
        PodDAO podDAO;

        @Autowired
        DoorDAO doorDAO;

        @Autowired
        NamedObjectManager namedObjectManager;

        @Autowired
        ReferenceFileManager referenceFileManager;

        @Autowired
        TransactionLogManager transactionLogManager;

        @Autowired
        CarrierManager carrierManager;

        @Autowired
        PodManager podManager;

        @Autowired
        DoorManager doorManager;

        @Autowired
        EventManager eventManager;

        @Autowired
        EntityManager entityManager;

        @Autowired
        StatusManager statusManager;

        @Override
        public PcdAssembly getPcdAssembly(Long carrierRrn, Long podRrn, Long doorRrn) {
            return pcdDAO.getPcdAssembly(carrierRrn, podRrn, doorRrn);
        }

        @Override
        public boolean isPcdCleaningTimeIsLessThan3Days(Long pcdRrn) {
            if (pcdRrn == null || pcdRrn <= 0) {
                return false;
            }
            PcdClean pcdCleanInfo = pcdDAO.getPcdCleanInfo(pcdRrn);
            if (pcdCleanInfo != null && pcdCleanInfo.getInstanceRrn() > 0) {
                if (this.isCleanValidPcd(pcdCleanInfo.getCleanOverDate())) {
                    // 过期时间三天前的时间（毫秒数）
                    long time = DateUtils.addTimeByUnit(pcdCleanInfo.getCleanOverDate().getTime(), "D", -3);
                    if (this.isCleanValidPcd(new Timestamp(time))) {
                        return false;
                    }
                }
            }
            return true;
        }

        @Override
        public boolean isCleanValidPcd(Timestamp cleanOverDate) {
            if (cleanOverDate != null) {
                if (DateUtils.compareDateWithNow(cleanOverDate) >= 1) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public PcdClean getPcdCleanInfo(Long pcdRrn) {
            if (pcdRrn != null && pcdRrn > 0) {
                PcdClean pcdCleanInfo = pcdDAO.getPcdCleanInfo(pcdRrn);
                if (pcdCleanInfo != null && pcdCleanInfo.getInstanceRrn() > 0) {
                    return pcdCleanInfo;
                }
            }
            return null;
        }

        @Override
        public Map<String, Object> checkPcdChangeStatusButton(Long facilityRrn, Long pcdRrn, String currentStatus) {
            Map<String, Object> btnMap = new HashMap();
            // WAIT_CLEAN状态下，如果前一个状态是ASSEMBLY状态，允许To Free按钮使用，以解绑PCD
            String preStatus = StringUtils.EMPTY;
            PcdAssembly assembly = null;
            preStatus = entityManager.getPreStatusByEntityRrn(pcdRrn);
            assembly = getPcdAssembly(pcdRrn, null, null);
            if (assembly == null || StringUtils.isBlank(assembly.getCarrierId())) {
                assembly = getPcdAssembly(null, pcdRrn, null);
                if (assembly == null || StringUtils.isBlank(assembly.getCarrierId())) {
                    assembly = getPcdAssembly(null, null, pcdRrn);
                }
            }

            if (StringUtils.isNotBlank(preStatus)) {
                if (StringUtils.equals(currentStatus, PcdStatus.WAIT_CLEAN_KEY) &&
                        StringUtils.equals(preStatus, PcdStatus.ASSEMBLY_KEY)) {
                    btnMap.put("toFreeBtn", true);
                } else {
                    btnMap.put("toFreeBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.FREE_KEY));
                }
            } else {
                if (StringUtils.equals(currentStatus, PcdStatus.WAIT_CLEAN_KEY) && assembly != null &&
                        StringUtils.isNotBlank(assembly.getCarrierId())) {
                    btnMap.put("toFreeBtn", true);
                } else {
                    btnMap.put("toFreeBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.FREE_KEY));
                }
            }

            btnMap.put("toInDamageBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.IN_DAMAGE_KEY));
            btnMap.put("toScrapBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.SCRAP_KEY));
            btnMap.put("toWatiCleanBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.WAIT_CLEAN_KEY));
            btnMap.put("toInCleanBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.IN_CLEAN_KEY));
            btnMap.put("toWaitCheckBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.WAIT_CHECK_KEY));
            btnMap.put("toHoldBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.HOLD_KEY));
            btnMap.put("toStartCleanBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.IN_CLEAN_KEY));
            btnMap.put("toEndCleanBtn", isPcdChangeStatus(facilityRrn, pcdRrn, currentStatus, PcdStatus.FREE_KEY));
            return btnMap;
        }

        @Override
        public boolean isPcdChangeStatus(Long facilityRrn, Long pcdRrn, String currentStatus, String tragetStatus) {
            String autoChangeStatusRefId = "$$AUTO_CHANGE_STATUS";
            String refNamedSpace = namedObjectManager.getNamedSpace(facilityRrn, ObjectList.REFERENCE_FILE_KEY);
            // 获取设置的只有系统能自动执行的事件模型
            List<ReferenceFileDetail> refColl = referenceFileManager.getRefFileValues(autoChangeStatusRefId, refNamedSpace);
            List<Map> eventInfoList = eventManager.getEventInfoList(facilityRrn, pcdRrn, currentStatus, tragetStatus);
            if (CollectionUtils.isNotEmpty(eventInfoList)) {
                Map<String, Object> eventMap = eventInfoList.get(0);
                if (CollectionUtils.isNotEmpty(refColl)) {
                    for (ReferenceFileDetail de : refColl) {
                        if (StringUtils.isNotBlank(de.getData2Value())) {
                            // 如果有相同的，则代表是只有系统自动使用的，用户不可以手动修改
                            if (StringUtils.equals(de.getKey1Value(), MapUtils.getString(eventMap, "eventId"))) {
                                return false;
                            }
                        }
                    }
                }
                return true;
            } else {
                return false;
            }
        }

        @Override
        public Map getPcdDefaultCleanCycleInfo(String category, String type) {
            return pcdDAO.getPcdDefaultCleanCycleInfo(category, type);
        }

        @Override
        public void deletePcdCleanInfo(Long pcdRrn, Long transRrn) {
            if (pcdRrn != null && pcdRrn > 0) {
                PcdClean cleanInfo = pcdDAO.getPcdCleanInfo(pcdRrn);
                pcdDAO.deletePcdCleanInfo(pcdRrn);
                pcdDAO.insertPcdCleanH(transRrn, cleanInfo);
            }
        }

        @Override
        public int insertPcdCleanH(Long transRrn, PcdClean cleanInfo) {
            return pcdDAO.insertPcdCleanH(transRrn, cleanInfo);
        }

        @Override
        public void insertPcdNamedObjectExt(Long pcdRrn, String flagType, String dmm_type) {
            pcdDAO.insertPcdNamedObjectExt(pcdRrn, flagType, dmm_type);
        }

        @Override
        public void updatePcdNamedObjectExt(Long pcdRrn, String flagType) {
            pcdDAO.updatePcdNamedObjectExt(pcdRrn, flagType);
        }

        @Override
        public void addPcdCleanInfo(Long pcdRrn, Long transRrn, Long facilityRrn, String pcdType, double cleanCycle) {
            if (pcdRrn != null && pcdRrn > 0) {
                PcdClean cleanInfo = new PcdClean();
                cleanInfo.setInstanceRrn(pcdRrn);
                cleanInfo.setCleanCycle(cleanCycle);
                cleanInfo.setPcdType(pcdType);
                long currentTime = System.currentTimeMillis();
                long cleanOverTime = (long) (currentTime + (cleanCycle * 24 * 60 * 60 * 1000));
                cleanInfo.setCleanOverDate(new Timestamp(cleanOverTime));
                cleanInfo.setCleanCount(0);
                pcdDAO.insertPcdCleanInfo(cleanInfo);
                pcdDAO.insertPcdCleanH(transRrn, cleanInfo);
            }
        }

        @Override
        public int countPcdCategoryByRrn(Long pcdRrn) {
            return pcdDAO.countPcdCategoryByRrn(pcdRrn);
        }

        @Override
        public void updatePcdCleanInfo(Long transRrn, PcdClean updateCleanInfo) {
            if (updateCleanInfo != null && updateCleanInfo.getInstanceRrn() > 0) {
                PcdClean existCleanInfo = getPcdCleanInfo(updateCleanInfo.getInstanceRrn());
                if (existCleanInfo != null && existCleanInfo.getInstanceRrn() > 0) {
                    if (updateCleanInfo.getCleanCycle() > 0) {
                        existCleanInfo.setCleanCycle(updateCleanInfo.getCleanCycle());
                        // 如果需要更新清洗周期，则需要重新计算清洗有效截止时间
                        long cleanOverTime = 0;
                        if (existCleanInfo.getOutCleanDate() != null && existCleanInfo.getOutCleanDate().getTime() > 0) {
                            cleanOverTime = (long) (existCleanInfo.getOutCleanDate().getTime() +
                                    (updateCleanInfo.getCleanCycle() * 24 * 60 * 60 * 1000));
                        } else {
                            // 没有清洗记录，使用创建时间计算
                            long currentTime = 0;
                            if (StringUtils.equalsIgnoreCase(existCleanInfo.getPcdType(), ObjectList.CARRIER_KEY)) {
                                Map<String, Object> carrierMap = carrierManager
                                        .qryCarrierInfoDetailById(updateCleanInfo.getInstanceId(),
                                                                  updateCleanInfo.getNamedSpace());
                                currentTime = DateUtils.parse(MapUtils.getString(carrierMap, "carrierCreateTime"),
                                                              DateUtils.DATE_FORMAT4DATE).getTime();
                            } else if (StringUtils.equalsIgnoreCase(existCleanInfo.getPcdType(), ObjectList.POD_KEY)) {
                                Map<String, Object> podMap = podManager
                                        .qryPodInfoDetailById(updateCleanInfo.getInstanceId(),
                                                              updateCleanInfo.getNamedSpace());
                                currentTime = DateUtils
                                        .parse(MapUtils.getString(podMap, "podCreateTime"), DateUtils.DATE_FORMAT4DATE)
                                        .getTime();
                            } else if (StringUtils.equalsIgnoreCase(existCleanInfo.getPcdType(), ObjectList.DOOR_KEY)) {
                                Map<String, Object> doorMap = doorManager
                                        .qrysDoorInfoDetailById(updateCleanInfo.getInstanceId(),
                                                                updateCleanInfo.getNamedSpace());
                                currentTime = DateUtils
                                        .parse(MapUtils.getString(doorMap, "doorCreateTime"), DateUtils.DATE_FORMAT4DATE)
                                        .getTime();
                            }
                            cleanOverTime = (long) (currentTime + (updateCleanInfo.getCleanCycle() * 24 * 60 * 60 * 1000));
                        }
                        updateCleanInfo.setCleanOverDate(new Timestamp(cleanOverTime));
                        existCleanInfo.setCleanOverDate(new Timestamp(cleanOverTime));
                    }
                    if (updateCleanInfo.getInCleanDate() != null) {
                        existCleanInfo.setInCleanDate(updateCleanInfo.getInCleanDate());
                    }
                    if (updateCleanInfo.getOutCleanDate() != null) {
                        existCleanInfo.setOutCleanDate(updateCleanInfo.getOutCleanDate());
                    } else {
                        updateCleanInfo.setOutCleanDate(existCleanInfo.getOutCleanDate());
                    }
                    if (StringUtils.isNotBlank(updateCleanInfo.getPcdType())) {
                        existCleanInfo.setPcdType(updateCleanInfo.getPcdType());
                    }
                    updateCleanInfo.setCleanCount(existCleanInfo.getCleanCount());

                    pcdDAO.updatePcdCleanInfo(updateCleanInfo);
                    pcdDAO.insertPcdCleanH(transRrn, existCleanInfo);
                }
            }
        }

        @Override
        public void inCleanPcd(String cleanEqptId, Long pcdRrn, String performedBy, Long facilityRrn) {
            cleanPcd(cleanEqptId, pcdRrn, performedBy, "in");
        }

        @Override
        public void outCleanPcd(Long pcdRrn, String performedBy, Long facilityRrn) {
            cleanPcd(StringUtils.EMPTY, pcdRrn, performedBy, "out");
        }

        @Override
        public PcdAssembly getPcdAssembly(Long carrierRrn) {
            return pcdDAO.getPcdAssembly(carrierRrn);
        }

        @Override
        public void deAssemblyPcd(Long carrierRrn, String user, Long transRrn) {
            TransactionLog transactionLog = null;
            if (transRrn == null || transRrn <= 0) {
                transactionLog = transactionLogManager.startTransactionLog(user, TransactionNames.CST_DEASSEMBLY);
                transRrn = transactionLog.getTransRrn();
            }
            pcdDAO.insertPcdAssemblyH(carrierRrn, user, transRrn);
            pcdDAO.deletePcdAssembly(carrierRrn);
            if (transactionLog != null) {
                transactionLogManager.markTransactionLog(transactionLog);
            }
        }

        @Override
        public void deAssemblyPcd(Long carrierRrn, Long podRrn, Long doorRrn, String user, Long facilityRrn) {
            TransactionLog transactionLog = transactionLogManager.startTransactionLog(user,
                                                                                      TransactionNames.CST_DEASSEMBLY);
            long transRrn = transactionLog.getTransRrn();
            Carrier carrier = carrierManager.getCarrier(carrierRrn);
            boolean isWaitClean = false;
            if (StringUtils.equalsIgnoreCase(carrier.getCarrierStatus(), PcdStatus.WAIT_CLEAN_KEY)) {
                //wait clean 状态不变
                // statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                //                                      EventName.CST_WAIT_CLEAN_TO_FREE, "Deassembly");
                isWaitClean=true;
            } else {
                statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                                                     EventName.CST_ASSEMBLY_TO_FREE, "Deassembly");
            }
            if (podRrn != null && podRrn > 0) {
                POD pod = podManager.getPod(podRrn);
                if (StringUtils.equalsIgnoreCase(pod.getPodStatus(), PcdStatus.WAIT_CLEAN_KEY)) {
                    //wait clean 状态不变
                    // statusManager.changePCDStatusByEvent(facilityRrn, user, podRrn, ObjectList.POD_KEY,
                    //                                      EventName.POD_WAIT_CLEAN_TO_FREE, "Deassembly");
                    isWaitClean=true;
                } else {
                    statusManager.changePCDStatusByEvent(facilityRrn, user, podRrn, ObjectList.POD_KEY,
                                                         EventName.POD_ASSEMBLY_TO_FREE, "Deassembly");
                }
            }

            /** by #40125 */
            if (doorRrn != null && doorRrn > 0) {
                Door door = doorManager.getDoor(doorRrn);
                if (StringUtils.equalsIgnoreCase(door.getDoorStatus(), PcdStatus.WAIT_CLEAN_KEY)) {
                    //wait clean 状态不变
                    // statusManager.changePCDStatusByEvent(facilityRrn, user, doorRrn, ObjectList.DOOR_KEY,
                    //                                      EventName.DOOR_WAIT_CLEAN_TO_FREE, "Deassembly");
                    isWaitClean=true;
                } else {
                    statusManager.changePCDStatusByEvent(facilityRrn, user, doorRrn, ObjectList.DOOR_KEY,
                                                         EventName.DOOR_ASSEMBLY_TO_FREE, "Deassembly");
                }
            }

            deAssemblyPcd(carrierRrn, user, transRrn);
            // 在PCD解绑的时候一次性同步carrier信息到POD以及DOOR，平时信息查询通过PCD关联关系查询
            updatePcdByDeassembly(carrierRrn, podRrn, doorRrn, transRrn,isWaitClean);
            transactionLogManager.markTransactionLog(transactionLog);
        }

        private void updatePcdByDeassembly(Long carrierRrn, Long podRrn, Long doorRrn, Long transRrn,boolean isWaitClean) {
            // 在PCD解绑的时候一次性同步carrier信息到POD以及DOOR，平时信息查询通过PCD关联关系查询
            Carrier carrier = carrierManager.getCarrier(carrierRrn);
            POD pod = podRrn == null || podRrn == 0 ? null : podManager.getPod(podRrn);
            Door door = doorRrn == null || doorRrn == 0 ? null : doorManager.getDoor(doorRrn);
            //因为解除绑定是要判断当前状态是否为 wait_clean 状态
            //1.如果整体状态属于wait_clean 则意味着所有备件都要进行清理
            //2.简单粗暴的方式就是直接更改pcd_clean 表的时间,所有组件时间按照清理时间去更新.
            //3.首先状态不能转换成free 等其他状态.
            //4.获取清理时间 全部更换
            String groupState =isWaitClean?PcdStatus.WAIT_CLEAN_KEY: PcdStatus.FREE_KEY;
            if (isWaitClean) {//判断是否变更为 wait_clean
                //代码进到这 意味着 所有组件时间都要更新到清理时间
                List<Long> cleanOverTimes =new ArrayList<>();
                List<Consumer<Timestamp>> callback=new ArrayList<>();
                TransactionLog transactionLog=  transactionLogManager.startTransactionLog("System","Deassembly Wait Clean");
                final BiConsumer<Timestamp,Long> updateFunction=(time,rrn)->{
                    pcdDAO.updateCleanOverTimeByRrn(rrn,time);
                    PcdClean pcdClean = new PcdClean();
                    pcdClean.setInstanceRrn(rrn);
                    pcdClean.setCleanOverDate(time);
                    pcdDAO.insertPcdCleanH(transactionLog.getTransRrn(),pcdClean);
                };
                callback.add((time)->{updateFunction.accept(time,carrierRrn);});
                cleanOverTimes.add(getPcdCleanInfo(carrierRrn).getCleanOverDate().getTime());
                if(pod!=null){
                    cleanOverTimes.add(getPcdCleanInfo(podRrn).getCleanOverDate().getTime());
                    callback.add((time)->{updateFunction.accept(time,podRrn);});
                }
                if(door!=null){
                    cleanOverTimes.add(getPcdCleanInfo(doorRrn).getCleanOverDate().getTime());
                    callback.add((time)->{updateFunction.accept(time,doorRrn);});
                }
                Long minTime = cleanOverTimes.stream().min(Long::compare).get();
                final Timestamp minTimeStamp=  new Timestamp(minTime);//最终全部改成这个时间
                callback.forEach((e)->e.accept(minTimeStamp));//更新
                transactionLogManager.markTransactionLog(transactionLog);
            }
            carrier.setCarrierStatus(groupState);
            final String commons = "Deassembly to "+groupState;
            //pod
            if (pod != null) {
                pod.setPodStatus(groupState);
                pod.setPollutionLevel(carrier.getPollutionLevel());
                pod.setMcsMoveStatus(carrier.getMcsMoveStatus());
                pod.setMcsLastMoveTime(carrier.getMcsLastMoveTime());
                pod.setMcsLastOperatorRrn(carrier.getMcsLastOperatorRrn());
                pod.setEqptRrn(carrier.getEqptRrn());
                pod.setJobRrn(carrier.getJobRrn());
                pod.setRunRrn(carrier.getRunRrn());
                pod.setCarrierMapRrn(carrier.getCarrierMapRrn());
                pod.setLoadPosition(carrier.getLoadPosition());
                podManager.updatePodByDeassembly(transRrn, pod);//此函数跟踪后 内部已经插入 POD_H流水数据
            }
            //door
            /** by #40125 */
            if (doorRrn != null && doorRrn > 0) {
                door.setDoorStatus(groupState);
                door.setPollutionLevel(carrier.getPollutionLevel());
                door.setMcsMoveStatus(carrier.getMcsMoveStatus());
                door.setMcsLastMoveTime(carrier.getMcsLastMoveTime());
                door.setMcsLastOperatorRrn(carrier.getMcsLastOperatorRrn());
                door.setEqptRrn(carrier.getEqptRrn());
                door.setJobRrn(carrier.getJobRrn());
                door.setRunRrn(carrier.getRunRrn());
                door.setCarrierMapRrn(carrier.getCarrierMapRrn());
                door.setLoadPosition(carrier.getLoadPosition());
                doorManager.updateDoorByDeassembly(transRrn, door);//此函数跟踪后 内部已经插入 DOOR_H流水数据
            }
            carrierManager.updateCarrierAvailableSlotCount(carrierRrn);
            carrierManager.insertCarrierH(carrierRrn,commons,transRrn);
            statusManager.updateCarrierStatus(carrierRrn, transRrn, groupState, "Deassembly");
        }

        @Override
        public void assemblyPcd(Long carrierRrn, Long podRrn, Long doorRrn, String user, Long facilityRrn) {
            TransactionLog transactionLog = transactionLogManager.startTransactionLog(user, TransactionNames.CST_ASSEMBLY);
            long transRrn = transactionLog.getTransRrn();

            statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                                                 EventName.CST_FREE_TO_ASSEMBLY, "Assembly");
            carrierManager.insertCarrierH(carrierRrn,PcdStatus.ASSEMBLY_KEY,transRrn);//插入流水
            if(podRrn!=null&&podRrn>0){
                statusManager
                        .changePCDStatusByEvent(facilityRrn, user, podRrn, ObjectList.POD_KEY, EventName.POD_FREE_TO_ASSEMBLY,
                                                "Assembly");
                podManager.insertPodH(podRrn,PcdStatus.ASSEMBLY_KEY,transRrn);//插入流水
            }
            if (doorRrn != null && doorRrn > 0) {
                statusManager.changePCDStatusByEvent(facilityRrn, user, doorRrn, ObjectList.DOOR_KEY,
                                                     EventName.DOOR_FREE_TO_ASSEMBLY, "Assembly");
                doorManager.insertDoorH(doorRrn,PcdStatus.ASSEMBLY_KEY,transRrn);//插入流水
            }
            //插入pcd_Assembly 插入历史表
            pcdDAO.insertPcdAssembly(carrierRrn, podRrn, doorRrn, user);
            pcdDAO.insertPcdAssemblyH(carrierRrn, podRrn, doorRrn, user, transRrn);

            //修改pdc 组件状态
            statusManager.updatePcdStatus(carrierRrn, podRrn, doorRrn, PcdStatus.ASSEMBLY_KEY, transRrn, "Assembly");
            transactionLogManager.markTransactionLog(transactionLog);
        }

        @Override
        public Boolean isAssembledPCD(Long instanceRrn) {
            return pcdDAO.isAssembledPCD(instanceRrn);
        }

        @Override
        public List<Map> getHoldReasons(long instanceRrn) {
            return pcdDAO.getHoldReasons(instanceRrn);
        }

        @Override
        public void insertMultipleHold(long instanceRrn, long holdBy, long transRrn) {
            pcdDAO.insertMultipleHold(instanceRrn, holdBy, transRrn);
        }

        @Override
        public void insertTransReason(long transRrn, long instanceRrn, TransReason transReason, int reasonCodeSequence) {
            pcdDAO.insertTransReason(transRrn, instanceRrn, transReason, reasonCodeSequence);
        }

        @Override
        public void deleteMultipleHold(long instanceRrn, long sequence) {
            pcdDAO.deleteMultipleHold(instanceRrn, sequence);
        }

        @Override
        public List<Map> getHoldReasonCodes(String holdReasonGroupID, List<String> holdReasonRoles,
                                            String referenceFileId) {
            return pcdDAO.getHoldReasonCodes(holdReasonGroupID, holdReasonRoles, referenceFileId);
        }

        @Override
        public List<Map> getReleaseGroup(String holdCode, String classTableValue) {
            return pcdDAO.getReleaseGroup(holdCode, classTableValue);
        }

        @Override
        public List<Map> getReleaseReasonCodes(String releaseReasonGroupID, List releaseRoles, String referenceFileId) {
            return pcdDAO.getReleaseReasonCodes(releaseReasonGroupID, releaseRoles, referenceFileId);
        }

        @Override
        public void resetCleanCycle(List<Map<String, Object>> defaultCleanCycleList, Long facilityRrn, String user) {
            // update之前，获取设置的clean cycle
            List<Map<String, Object>> allCycleList = pcdDAO.getAllPcdDefaultCleanCycle();
            pcdDAO.batchInsertOrUpdatePcdDefaultCleanCycle(defaultCleanCycleList);
            // 重置默认周期后，同步修改所有P/C/D的周期信息 PS:source为旧配置，target为新的配置
            TransactionLog createTrans = transactionLogManager.startTransactionLog(user, TransactionNames.CREATE_KEY);

            TransactionLog modifyTrans = transactionLogManager.startTransactionLog(user, TransactionNames.MODIFY_KEY);

            for (Map<String, Object> targetCycleMap : defaultCleanCycleList) {
                String targetCategory = MapUtils.getString(targetCycleMap, "categoryCode");
                String targetCategoryDesc = MapUtils.getString(targetCycleMap, "categoryDesc");
                String targetType = MapUtils.getString(targetCycleMap, "typeCode");
                String targetTypeDesc = MapUtils.getString(targetCycleMap, "typeDesc");
                String targetDefaultCycle = MapUtils.getString(targetCycleMap, "defaultCleanCycle");
                double targetDefaultCycleD = NumberUtils.toDouble(targetDefaultCycle);

                boolean updateFlag = true;
                for (Map<String, Object> sourceCycleMap : allCycleList) {
                    String sourceCategory = MapUtils.getString(sourceCycleMap, "category");
                    String sourceType = MapUtils.getString(sourceCycleMap, "type");
                    String sourceDefaultCycle = MapUtils.getString(sourceCycleMap, "defaultCleanCycle");

                    if (StringUtils.equals(sourceCategory, targetCategory) && StringUtils.equals(sourceType, targetType)) {
                        if (!StringUtils.equals(sourceDefaultCycle, targetDefaultCycle)) {
                            // 如果相同的category和type下的defaultCleanCycle有改动，则修改所有相关的P/C/D
                            updateAllPCDCleanCycleByCategoryAndType(facilityRrn, user, targetCategory, targetType,
                                                                    targetDefaultCycleD);
                            // modify history
                            pcdDAO.addPcdDefaultCleanCycleHistory(modifyTrans.getTransRrn(), targetCategoryDesc,
                                                                  targetTypeDesc, targetDefaultCycleD, user);
                        }
                        updateFlag = false;
                        break;
                    } else {
                        updateFlag = true;
                    }
                }
                if (updateFlag) {
                    // 走到此处，表示有新的配置是之前没有的，则直接根据category和type更新cycle信息
                    updateAllPCDCleanCycleByCategoryAndType(facilityRrn, user, targetCategory, targetType,
                                                            targetDefaultCycleD);
                    // create history
                    pcdDAO.addPcdDefaultCleanCycleHistory(createTrans.getTransRrn(), targetCategoryDesc, targetTypeDesc,
                                                          targetDefaultCycleD, user);
                }
            }

            transactionLogManager.markTransactionLog(createTrans);
            transactionLogManager.markTransactionLog(modifyTrans);
        }

        @Override
        public List<ReferenceFileDetail> getCleanEqptRefDetailInfo(String eqptId, Long facilityRrn) {
            if (StringUtils.isBlank(eqptId)) {
                return new ArrayList<>();
            }

            List<ReferenceFileDetail> refDetailList = new ArrayList<ReferenceFileDetail>();
            List<ReferenceFileDetail> deatilColl = referenceFileManager
                    .getRefFileValues(PCD_CLEAN_EQPT_REFERENCE_ID, facilityRrn);
            if (CollectionUtils.isNotEmpty(deatilColl)) {
                for (ReferenceFileDetail detail : deatilColl) {
                    if (StringUtils.equals(detail.getData1Value(), eqptId)) {
                        refDetailList.add(detail);
                    }
                }
            }
            return refDetailList;
        }

        @Override
        public String checkCleanEqptIsInUse(String eqptId, Long facilityRrn) {
            String entityNamedSpace = namedObjectManager.getNamedSpace(facilityRrn, ObjectList.ENTITY_KEY);
            List<PcdClean> list = getPcdInfoByCleanEqpt(eqptId, entityNamedSpace);
            if (CollectionUtils.isNotEmpty(list)) {
                String msg = I18nUtils.getMessage(MessageIdList.PCD_EQPT_IS_CLEANING, "Equipment is cleaning.", eqptId);
                String cleaningP = "P:";
                String cleaningC = "C:";
                String cleaningD = "D:";
                for (PcdClean pc : list) {
                    if (StringUtils.equals(pc.getPcdType(), ObjectList.POD_KEY)) {
                        cleaningP += pc.getInstanceId() + " ";
                    }
                    if (StringUtils.equals(pc.getPcdType(), ObjectList.CARRIER_KEY)) {
                        cleaningC += pc.getInstanceId() + " ";
                    }
                    if (StringUtils.equals(pc.getPcdType(), ObjectList.DOOR_KEY)) {
                        cleaningD += pc.getInstanceId() + " ";
                    }
                }

                if (StringUtils.length(cleaningP) > 3) {
                    msg += cleaningP + " ";
                }
                if (StringUtils.length(cleaningC) > 3) {
                    msg += cleaningC + " ";
                }
                if (StringUtils.length(cleaningD) > 3) {
                    msg += cleaningD + " ";
                }
                return msg;
            }
            return "";
        }

        @Override
        public List<PcdClean> getCleaningPcdByEqpt(String eqptId, Long facilityRrn) {
            List<PcdClean> list = new ArrayList<PcdClean>();
            String entityNamedSpace = namedObjectManager.getNamedSpace(facilityRrn, ObjectList.ENTITY_KEY);
            list = getPcdInfoByCleanEqpt(eqptId, entityNamedSpace);
            return list;
        }

        @Override
        public void batchOutClean(Long facilityRrn, String eqptId, String cstIds, String podIds, String doorIds,
                                  String user) {
            List<ReferenceFileDetail> eqptList = getCleanEqptRefDetailInfo(eqptId, facilityRrn);
            if (CollectionUtils.isNotEmpty(eqptList)) {
                String eqptCleaningMsg = checkCleanEqptIsInUse(eqptId, facilityRrn);
                Assert.isFalse(StringUtils.isBlank(eqptCleaningMsg),
                               Errors.create().key(MessageIdList.PCD_NO_CLEANING_PCD).content("当前设备没有正在清洗的PCD!").build());

                double cstMinMinutes = 0;
                double podMinMinutes = 0;
                double doorMinMinutes = 0;
                for (ReferenceFileDetail eqpt : eqptList) {
                    double minMinutes = Double.parseDouble(eqpt.getData4Value());
                    if (StringUtils.equals(eqpt.getData2Value(), "CASSETTE")) {
                        cstMinMinutes = minMinutes;
                    } else if (StringUtils.equals(eqpt.getData2Value(), ObjectList.POD_KEY)) {
                        podMinMinutes = minMinutes;
                    } else if (StringUtils.equals(eqpt.getData2Value(), ObjectList.DOOR_KEY)) {
                        doorMinMinutes = minMinutes;
                    }
                }

                String[] cstArray = StringUtils.split(cstIds, ",");
                String[] podArray = StringUtils.split(podIds, ",");
                String[] doorArray = StringUtils.split(doorIds, ",");

                IsPcdCleaningDateAboveThanMinMinutes(podArray, podMinMinutes, facilityRrn);

                IsPcdCleaningDateAboveThanMinMinutes(cstArray, cstMinMinutes, facilityRrn);

                IsPcdCleaningDateAboveThanMinMinutes(doorArray, doorMinMinutes, facilityRrn);

                // batch out clean
                if (cstArray.length > 0) {
                    batchOutCleanByPcd(eqptId, cstArray, ObjectList.CARRIER_KEY, EventName.CST_IN_CLEAN_TO_FREE,
                                       facilityRrn, user);
                }
                if (podArray.length > 0) {
                    batchOutCleanByPcd(eqptId, podArray, ObjectList.POD_KEY, EventName.POD_IN_CLEAN_TO_FREE, facilityRrn,
                                       user);
                }
                if (doorArray.length > 0) {
                    batchOutCleanByPcd(eqptId, doorArray, ObjectList.DOOR_KEY, EventName.DOOR_IN_CLEAN_TO_FREE, facilityRrn,
                                       user);
                }
            }
        }

        @Override
        public List<Map<String, Object>> getAllPcdCategory(Long facilityRrn) {
            List<ReferenceFileDetail> categoryColl = referenceFileManager.getRefFileValues("$PCD_CATEGORY",
                                                                                           namedObjectManager.getNamedSpace(
                                                                                                   facilityRrn,
                                                                                                   ObjectList.REFERENCE_FILE_KEY));

            List<Map<String, Object>> categoryList = new ArrayList<Map<String, Object>>();
            if (CollectionUtils.isNotEmpty(categoryColl)) {
                for (ReferenceFileDetail detail : categoryColl) {
                    Map<String, Object> map = new HashMap<String, Object>();
                    map.put("categoryCode", detail.getKey1Value());
                    map.put("categoryDesc", detail.getData1Value());

                    categoryList.add(map);
                }
                return categoryList;
            }
            return new ArrayList<Map<String, Object>>();
        }

        @Override
        public List<Map<String, Object>> getAllPcdType(Long facilityRrn) {
            List<ReferenceFileDetail> categoryColl = referenceFileManager.getRefFileValues("$CARRIER_TYPE",
                                                                                           namedObjectManager.getNamedSpace(
                                                                                                   facilityRrn,
                                                                                                   ObjectList.REFERENCE_FILE_KEY));

            List<Map<String, Object>> categoryList = new ArrayList<Map<String, Object>>();
            if (CollectionUtils.isNotEmpty(categoryColl)) {
                for (ReferenceFileDetail detail : categoryColl) {
                    Map<String, Object> map = new HashMap<String, Object>();
                    map.put("typeCode", detail.getKey1Value());
                    map.put("typeDesc", detail.getData1Value());
                    map.put("pcdFlag", detail.getData2Value());
                    categoryList.add(map);
                }
                return categoryList;
            }
            return new ArrayList<Map<String, Object>>();
        }

        @Override
        public List<PcdClean> getPcdInfoByCleanEqpt(String eqptId, String entityNamedSpace) {
            return pcdDAO.getPcdInfoByCleanEqpt(eqptId, entityNamedSpace);
        }

        @Override
        public void batchInClean(Long facilityRrn, String eqptId, String cstIds, String podIds, String doorIds,
                                 String user) {
            List<ReferenceFileDetail> eqptList = getCleanEqptRefDetailInfo(eqptId, facilityRrn);
            Assert.isTrue(CollectionUtils.isNotEmpty(eqptList),
                          Errors.create().key(MessageIdList.PCD_CLEAN_EQPT_MISSING).content("清洗作业设备不存在!").build());

            String eqptCleaningMsg = checkCleanEqptIsInUse(eqptId, facilityRrn);
            Assert.isFalse(StringUtils.isNotBlank(eqptCleaningMsg), Errors.create().content(eqptCleaningMsg).build());

            String[] cstArray = StringUtils.split(cstIds, ",");
            String[] podArray = StringUtils.split(podIds, ",");
            String[] doorArray = StringUtils.split(doorIds, ",");

            int cstMaxCount = 0;
            int podMaxCount = 0;
            int doorMaxCount = 0;
            for (ReferenceFileDetail eqpt : eqptList) {
                int maxCount = Integer.parseInt(eqpt.getData3Value());
                if (StringUtils.equals(eqpt.getData2Value(), "CASSETTE")) {
                    cstMaxCount = maxCount;
                } else if (StringUtils.equals(eqpt.getData2Value(), ObjectList.POD_KEY)) {
                    podMaxCount = maxCount;
                } else if (StringUtils.equals(eqpt.getData2Value(), ObjectList.DOOR_KEY)) {
                    doorMaxCount = maxCount;
                }
            }

            Assert.isFalse(cstArray.length > cstMaxCount,
                           Errors.create().key(MessageIdList.PCD_CASSETTE_OUT_OF_MAX).content("Cassette超出最大计数!").build());
            Assert.isFalse(podArray.length > podMaxCount,
                           Errors.create().key(MessageIdList.PCD_POD_OUT_OF_MAX).content("Cassette超出最大计数!").build());
            Assert.isFalse(doorArray.length > doorMaxCount,
                           Errors.create().key(MessageIdList.PCD_DOOR_OUT_OF_MAX).content("Cassette超出最大计数!").build());

            // 检查PCD是否都存在并且是未绑定的空P/C/D
            if (cstArray.length > 0 && StringUtils.isNotBlank(cstArray[0])) {
                checkPcdIsExist(cstArray, facilityRrn);
            }

            if (podArray.length > 0 && StringUtils.isNotBlank(podArray[0])) {
                checkPcdIsExist(podArray, facilityRrn);

            }

            if (doorArray.length > 0 && StringUtils.isNotBlank(doorArray[0])) {
                checkPcdIsExist(doorArray, facilityRrn);
            }

            // batch in clean
            if (cstArray.length > 0) {
                batchInCleanByPcd(eqptId, cstArray, ObjectList.CARRIER_KEY, EventName.CST_WAIT_CLEAN_TO_IN_CLEAN,
                                  facilityRrn, user);
            }
            if (podArray.length > 0) {
                batchInCleanByPcd(eqptId, podArray, ObjectList.POD_KEY, EventName.POD_WAIT_CLEAN_TO_IN_CLEAN, facilityRrn,
                                  user);
            }
            if (doorArray.length > 0) {
                batchInCleanByPcd(eqptId, doorArray, ObjectList.DOOR_KEY, EventName.DOOR_WAIT_CLEAN_TO_IN_CLEAN,
                                  facilityRrn, user);
            }
        }

        @Override
        public void checkPcdIsValid(Long carrierRrn, Long facilityRrn) {
            String cleanInvalidMsg = checkPcdIsValidReturnString(carrierRrn, facilityRrn);

            Assert.isFalse(StringUtils.isNotBlank(cleanInvalidMsg),
                           Errors.create().content(cleanInvalidMsg).build());

            Map<String, Object> resultMap = checkNotAllowUsePcdStatusByCarrier(carrierRrn, facilityRrn);
            Assert.isTrue(MapUtils.getBooleanValue(resultMap, "result", true),
                          Errors.create().content(MapUtils.getString(resultMap, "msg", "Invalid Entity")).build());
        }

        /**
         * 通过carrierRrn检查OPEN-CST或PCD当前状态是否允许使用
         *
         * @return 返回一个map，包含两个key（1.result，2.msg） result为boolean类型，true 表示允许，false 表示不允许 msg为String类型，包含不允许的原因
         */
        @Override
        public Map<String, Object> checkNotAllowUsePcdStatusByCarrier(Long carrierRrn, Long facilityRrn) {
            Map<String, Object> resultMap = new HashMap<String, Object>();
            resultMap.put("result", true);
            String refId = "$$NOT_ALLOW_USE_CASSETTE_STATUS";
            String refNamedSpace = namedObjectManager.getNamedSpace(facilityRrn, ObjectList.REFERENCE_FILE_KEY);
            List<ReferenceFileDetail> notAllowStatusColl = referenceFileManager.getRefFileValues(refId, refNamedSpace);
            if (CollectionUtils.isEmpty(notAllowStatusColl)) {
                return resultMap;
            }

            PcdAssembly pcd = this.getPcdAssembly(carrierRrn, null, null);
            // 如果是PCD组装的，则逐个检查每个组件
            if (pcd != null && StringUtils.isNotBlank(pcd.getPodId())) {
                Long podRrn = pcd.getPodRrn();
                Long doorRrn = pcd.getDoorRrn();
                Carrier c = carrierManager.getCarrier(carrierRrn);
                POD p = podRrn==null||podRrn==0?null:podManager.getPod(podRrn);
                Door d = doorRrn==null||doorRrn==0?null:doorManager.getDoor(doorRrn);

                for (ReferenceFileDetail refDetail : notAllowStatusColl) {
                    if (StringUtils.equalsIgnoreCase(c.getCarrierStatus(), refDetail.getKey1Value())) {
                        resultMap.put("result", false);
                        resultMap.put("msg", "Invalid " + c.getInstanceId() + " status: " + refDetail.getKey1Value());
                        break;
                    }
                    if (p!=null&&StringUtils.equalsIgnoreCase(p.getPodStatus(), refDetail.getKey1Value())) {
                        resultMap.put("result", false);
                        resultMap.put("msg", "Invalid " + p.getInstanceId() + " status: " + refDetail.getKey1Value());
                        break;
                    }
                    if (d!=null&&StringUtils.equalsIgnoreCase(d.getDoorStatus(), refDetail.getKey1Value())) {
                        resultMap.put("result", false);
                        resultMap.put("msg", "Invalid " + d.getInstanceId() + " status: " + refDetail.getKey1Value());
                        break;
                    }
                }
            } else {
                for (ReferenceFileDetail refDetail : notAllowStatusColl) {
                    Carrier c = carrierManager.getCarrier(carrierRrn);
                    if (StringUtils.equalsIgnoreCase(c.getCarrierStatus(), refDetail.getKey1Value())) {
                        resultMap.put("result", false);
                        resultMap.put("msg", "Invalid " + c.getInstanceId() + " status: " + refDetail.getKey1Value());
                        break;
                    }
                }
            }
            return resultMap;
        }

        @Override
        public double getDefaultCleanCycle(String category, String type, Long facilityRrn) {
            if (StringUtils.isNotBlank(category) && StringUtils.isNotBlank(type)) {
                Map<String, Object> map = getPcdDefaultCleanCycleInfo(category, type);
                return MapUtils.getDoubleValue(map, "defaultCleanCycle");
            }
            return 0;
        }

        @Override
        public String generateCarrierId(String prefix) {
            String subfix = pcdDAO.getCarrierIdSerial(prefix);
            Integer serialNumber = Integer.parseInt(subfix);
            Assert.isFalse(serialNumber >= 9999,
                           Errors.create().key(MessageIdList.CARRIER_ID_OVERFLOW).content("晶舟号已超过系统定义，请联系管理员!").build());
            serialNumber++;
            DecimalFormat dfSuffix = new DecimalFormat("0000");
            subfix = dfSuffix.format(serialNumber);
            return prefix + subfix;
        }

        @Override
        public String generateCarrierId(String prefix, int serialLength) {
            String zerosPattern = "";
            String maxSerial = "";
            for (int i = 0; i < serialLength; i++) {
                maxSerial += "9";
                zerosPattern += "0";
            }
            String subfix = pcdDAO.getCarrierIdSerial(prefix, serialLength);
            Long serialNumber = Long.parseLong(subfix);
            Assert.isFalse(serialNumber >= NumberUtils.toLong(maxSerial),
                           Errors.create().key(MessageIdList.CARRIER_ID_OVERFLOW).content("晶舟号已超过系统定义，请联系管理员!").build());
            serialNumber++;
            DecimalFormat dfSuffix = new DecimalFormat(zerosPattern);
            subfix = dfSuffix.format(serialNumber);
            return prefix + subfix;
        }

        @Override
        public boolean checkPcdOverClean(long pcdRrn) {
            PcdClean pcdCleanInfo = getPcdCleanInfo(pcdRrn);
            if (pcdCleanInfo != null && pcdCleanInfo.getInstanceRrn() > 0) {
                if (!isCleanValidPcd(pcdCleanInfo.getCleanOverDate())) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public void exchangePCD(List<Map> transInfo) {
            for (Map transMap : transInfo) {
                Lot lot = (Lot) transMap.get("lot");
                Long targetCarrierRrn = MapUtils.getLongValue(transMap, "targetCarrierRrn", 0L);
                List<Map> unitsList = (List<Map>) transMap.get("unitsList");
                this.changeStatusFromInUseToFreeOrAssembly(MapUtils.getLong(transMap, "facilityRrn"),
                                                           MapUtils.getString(transMap, "user"), lot.getCarrierRrn(),
                                                           "Release for move out exchange pcd");
                this.changeStatusFromFreeOrAssemblyToInUse(MapUtils.getLong(transMap, "facilityRrn"),
                                                           MapUtils.getString(transMap, "user"), targetCarrierRrn,
                                                           "In use for move out exchange pcd");
                carrierManager.exchangeCarrierByCarrier(lot, targetCarrierRrn, unitsList);
            }
        }

        @Override
        public void changeStatusFromInUseToFreeOrAssembly(Long facilityRrn, String user, Long carrierRrn, String comments) {
            Carrier carrier = new Carrier();
            carrier = carrierManager.getCarrier(carrierRrn);
            if (StringUtils.isNotBlank(carrier.getInstanceId())) {
                if (carrier.getSlotCount() > carrier.getAvailableSlotCount()) {
                    // 将PCD的状态从IN USE切换到ASSEMBLY
                    PcdAssembly pcd = getPcdAssembly(carrierRrn, null, null);
                    if (pcd != null && StringUtils.isNotBlank(pcd.getPodId())) {
                        // pcd-cst
                        POD p =pcd.getPodRrn()==null||pcd.getPodRrn()<=0?null: podManager.getPod(pcd.getPodRrn());
                        Door d =pcd.getDoorRrn()==null||pcd.getDoorRrn()<=0?null: doorManager.getDoor(pcd.getDoorRrn());

                        /**
                         * 释放PCD，如果释放时，批次上PCD是IN_USE状态，直接释放为ASSEMBLY状态
                         * 如果批次上PCD某个组件是WAIT_CLEAN状态，则需要先释放为ASSEMBLY状态
                         * 再从ASSEMBLY状态自动切换为WAIT_CLEAN状态
                         */
                        if (StringUtils.equalsIgnoreCase(PcdStatus.IN_USE_KEY, carrier.getCarrierStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getCarrierRrn(),
                                                                 ObjectList.CARRIER_KEY, EventName.CST_IN_USE_TO_ASSEMBLY,
                                                                 comments);
                        } else if (StringUtils.equalsIgnoreCase(PcdStatus.WAIT_CLEAN_KEY, carrier.getCarrierStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getCarrierRrn(),
                                                                 ObjectList.CARRIER_KEY,
                                                                 EventName.CST_WAIT_CLEAN_TO_ASSEMBLY, comments);
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getCarrierRrn(),
                                                                 ObjectList.CARRIER_KEY,
                                                                 EventName.CST_ASSEMBLY_TO_WAIT_CLEAN, comments);
                        }

                        if (p!=null&&StringUtils.equalsIgnoreCase(PcdStatus.IN_USE_KEY, p.getPodStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getPodRrn(), ObjectList.POD_KEY,
                                                                 EventName.POD_IN_USE_TO_ASSEMBLY, comments);
                        } else if (p!=null&&StringUtils.equalsIgnoreCase(PcdStatus.WAIT_CLEAN_KEY, p.getPodStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getPodRrn(), ObjectList.POD_KEY,
                                                                 EventName.POD_WAIT_CLEAN_TO_ASSEMBLY, comments);
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getPodRrn(), ObjectList.POD_KEY,
                                                                 EventName.POD_ASSEMBLY_TO_WAIT_CLEAN, comments);
                        }

                        if (d!=null&&StringUtils.equalsIgnoreCase(PcdStatus.IN_USE_KEY, d.getDoorStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getDoorRrn(), ObjectList.DOOR_KEY,
                                                                 EventName.DOOR_IN_USE_TO_ASSEMBLY, comments);
                        } else if (d!=null&&StringUtils.equalsIgnoreCase(PcdStatus.WAIT_CLEAN_KEY, d.getDoorStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getDoorRrn(), ObjectList.DOOR_KEY,
                                                                 EventName.DOOR_WAIT_CLEAN_TO_ASSEMBLY, comments);
                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getDoorRrn(), ObjectList.DOOR_KEY,
                                                                 EventName.DOOR_ASSEMBLY_TO_WAIT_CLEAN, comments);
                        }
                    } else {
                        /**
                         * 释放晶舟，如果批次上晶舟是IN_USE，则直接释放为FREE状态 如果批次上晶舟是WAIT_CLEAN状态，则先释放为FREE状态，
                         * 再从FREE状态自动切换为WAIT_CLEAN状态
                         */
                        if (StringUtils.equalsIgnoreCase(PcdStatus.IN_USE_KEY, carrier.getCarrierStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                                                                 EventName.CST_IN_USE_TO_FREE, comments);
                        } else if (StringUtils.equalsIgnoreCase(PcdStatus.WAIT_CLEAN_KEY, carrier.getCarrierStatus())) {
                            statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                                                                 EventName.CST_WAIT_CLEAN_TO_FREE, comments);
                            statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                                                                 EventName.CST_FREE_TO_WAIT_CLEAN, comments);
                        }
                    }
                }
            }
        }

        @Override
        public void changeStatusFromFreeOrAssemblyToInUse(Long facilityRrn, String user, Long carrierRrn, String comments) {
            Carrier carrier = carrierManager.getCarrier(carrierRrn);
            if (StringUtils.isNotBlank(carrier.getInstanceId())) {
                if (carrierManager.isPcdType(carrier.getObjectSubtype())) {
                    // 将PCD的状态从ASSEMBLY切换为IN USE
                    if (StringUtils.equalsIgnoreCase(carrier.getCarrierStatus(), PcdStatus.ASSEMBLY_KEY)) {
                        PcdAssembly pcd = getPcdAssembly(carrierRrn, null, null);
                        if (pcd != null && StringUtils.isNotBlank(pcd.getPodId())) {

                            statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getCarrierRrn(),
                                                                 ObjectList.CARRIER_KEY, EventName.CST_ASSEMBLY_TO_IN_USE,
                                                                 comments);
                            if(pcd.getPodRrn()!=null&&pcd.getPodRrn()>0){
                                statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getPodRrn(), ObjectList.POD_KEY,
                                                                     EventName.POD_ASSEMBLY_TO_IN_USE, comments);
                            }
                            if(pcd.getDoorRrn()!=null&&pcd.getDoorRrn()>0){
                                statusManager.changePCDStatusByEvent(facilityRrn, user, pcd.getDoorRrn(), ObjectList.DOOR_KEY,
                                                                     EventName.DOOR_ASSEMBLY_TO_IN_USE, comments);
                            }
                        }
                    }
                } else {
                    // 将OPEN-CST的状态从FREE切换为IN USE
                    if (StringUtils.equalsIgnoreCase(carrier.getCarrierStatus(), PcdStatus.FREE_KEY)) {
                        statusManager.changePCDStatusByEvent(facilityRrn, user, carrierRrn, ObjectList.CARRIER_KEY,
                                                             EventName.CST_FREE_TO_IN_USE, comments);
                    }
                }
            }
        }

        @Override
        public void checkPCDCleanTimeAndLogEvent(String waitCleanKey) {
            // 在允许事件集里面，在自动的类表值里面,超过清洗时间
            List<PcdClean> pcdCleanInfos = this.qryPCDCleanInfo(waitCleanKey);

            Set<Long> pcdRrnSet = new HashSet<>(); // set做防止重复更改状态的作用
            for (PcdClean cleanInfo : pcdCleanInfos) {
                if(pcdRrnSet.contains(cleanInfo.getInstanceRrn())){
                    continue;
                }
                // 如果到了清洗时间，执行log event,将Carrier, Pod,Door变成WAIT_CLEAN状态
                statusManager.changePCDStatusAndEvent("SYSTEM", cleanInfo.getInstanceRrn(), cleanInfo.getPcdType(),
                                                      cleanInfo.getEventRrn(), "Wait clean by system auto",
                                                      PcdStatus.WAIT_CLEAN_KEY);
                pcdRrnSet.add(cleanInfo.getInstanceRrn());

                // 如果是组合的PCD，只要其中有一个组件过期，其它组件即使没过期也做过期处理(状态变成WAIT_CLEAN)
                // 如果是组合的PCD，把三个组件都查询出来
                PcdAssembly pcd = new PcdAssembly();
                if (StringUtils.equalsIgnoreCase(cleanInfo.getPcdType(), ObjectList.CARRIER_KEY)) {
                    pcd = this.getPcdAssembly(cleanInfo.getInstanceRrn(), null, null);
                } else if (StringUtils.equalsIgnoreCase(cleanInfo.getPcdType(), ObjectList.POD_KEY)) {
                    pcd = this.getPcdAssembly(null, cleanInfo.getInstanceRrn(), null);
                } else if (StringUtils.equalsIgnoreCase(cleanInfo.getPcdType(), ObjectList.DOOR_KEY)) {
                    pcd = this.getPcdAssembly(null, null, cleanInfo.getInstanceRrn());
                }
                if (pcd != null) {
                    if (StringUtils.isNotBlank(pcd.getCarrierId())) {
                        // 是组合的PCD，组装clean信息
                        //修改 pod
                        if(pcd.getPodRrn()!=null&&pcd.getPodRrn()>0){
                            PcdClean podCleanInfo = this.getPcdCleanInfo(pcd.getPodRrn());
                            podCleanInfo.setInstanceId(pcd.getPodId());
                            pcdRrnSet = checkOtherPCDCleanTimeAndLogEvent(pcdRrnSet, podCleanInfo, cleanInfo);
                        }
                        //修改door
                        if (pcd.getDoorRrn() != null && pcd.getDoorRrn() > 0) {
                            PcdClean doorCleanInfo = this.getPcdCleanInfo(pcd.getDoorRrn());
                            doorCleanInfo.setInstanceId(pcd.getDoorId());
                            pcdRrnSet = checkOtherPCDCleanTimeAndLogEvent(pcdRrnSet, doorCleanInfo, cleanInfo);
                        }
                        //修改cst
                        PcdClean carrierCleanInfo = this.getPcdCleanInfo(pcd.getCarrierRrn());
                        carrierCleanInfo.setInstanceId(pcd.getCarrierId());
                        pcdRrnSet = checkOtherPCDCleanTimeAndLogEvent(pcdRrnSet, carrierCleanInfo, cleanInfo);
                    }
                }
            }
        }

        @Override
        public List<PcdClean> qryPCDCleanInfo(String targetStatus) {
            return pcdDAO.qryPCDCleanInfo(targetStatus);
        }

        @Override
        public Set<Long> checkOtherPCDCleanTimeAndLogEvent(Set<Long> pcdRrnSet, PcdClean targetPcdClean,
                                                           PcdClean sourcePcdClean) {
            if (targetPcdClean != null && targetPcdClean.getInstanceRrn() > 0) {
                long facilityRrn = LocalContext.getFacilityRrn();
                // 查询事件信息
                Map<String, Object> eventMap = getEventInfoByPcdId(facilityRrn, targetPcdClean.getInstanceId(),
                                                                   targetPcdClean.getPcdType(), PcdStatus.WAIT_CLEAN_KEY);
                if (MapUtils.isNotEmpty(eventMap)) {

                    targetPcdClean.setEventId(MapUtils.getString(eventMap, "eventId", StringUtils.EMPTY));
                    targetPcdClean.setEventRrn(MapUtils.getLongValue(eventMap, "eventRrn"));

                    int setSize = pcdRrnSet.size();
                    pcdRrnSet.add(targetPcdClean.getInstanceRrn());
                    if (setSize == pcdRrnSet.size()) {
                        // 重复更改
                    } else {
                        statusManager.changePCDStatusAndEvent("SYSTEM", targetPcdClean.getInstanceRrn(),
                                                              targetPcdClean.getPcdType(), targetPcdClean.getEventRrn(),
                                                              "Wait clean by system auto", PcdStatus.WAIT_CLEAN_KEY);
                    }
                }
            }

            return pcdRrnSet;
        }

        @Override
        public Map<String, Object> getEventInfoByPcdId(Long facilityRrn, String pcdId, String pcdType,
                                                       String targetStatus) {
            String currentStatus = StringUtils.EMPTY;
            String entityNamedSpace = namedObjectManager.getNamedSpace(facilityRrn, ObjectList.ENTITY_KEY);
            long entityRrn = 0;
            if (StringUtils.equalsIgnoreCase(pcdType, ObjectList.CARRIER_KEY)) {
                Carrier c = carrierManager.getCarrier(facilityRrn, pcdId);
                if (c != null && c.getInstanceRrn() > 0) {
                    entityRrn = c.getInstanceRrn();
                    currentStatus = c.getCarrierStatus();
                }
            } else if (StringUtils.equalsIgnoreCase(pcdType, ObjectList.POD_KEY)) {
                long podRrn = namedObjectManager.getNamedObjectRrn(pcdId, entityNamedSpace, ObjectList.ENTITY_KEY,
                                                                   ObjectList.POD_KEY);
                POD p = podManager.getPod(podRrn);
                if (p != null && p.getInstanceRrn() > 0) {
                    entityRrn = p.getInstanceRrn();
                    currentStatus = p.getPodStatus();
                }
            } else if (StringUtils.equalsIgnoreCase(pcdType, ObjectList.DOOR_KEY)) {
                long doorRrn = namedObjectManager.getNamedObjectRrn(pcdId, entityNamedSpace, ObjectList.ENTITY_KEY,
                                                                    ObjectList.DOOR_KEY);
                Door d = doorManager.getDoor(doorRrn);
                if (d != null && d.getInstanceRrn() > 0) {
                    entityRrn = d.getInstanceRrn();
                    currentStatus = d.getDoorStatus();
                }
            }

            if (StringUtils.isNotBlank(currentStatus)) {
                List<Map> eventInfoList = eventManager.getEventInfoList(facilityRrn, entityRrn, currentStatus,
                                                                        targetStatus);
                if (CollectionUtils.isNotEmpty(eventInfoList)) {
                    // 获取第一个
                    Map<String, Object> eventMap = eventInfoList.get(0);
                    return eventMap;
                }
            }
            return null;
        }

        @Override
        public String checkPcdIsValidReturnString(Long carrierRrn, long facilityRrn) {
            StringBuilder cleanInvalidMsg = new StringBuilder();
            if (carrierRrn != null && carrierRrn > 0) {
                PcdAssembly pcd = this.getPcdAssembly(carrierRrn);
                if (pcd != null && pcd.getCarrierRrn() > 0) {
                    PcdClean carrierCleanInfo = this.getPcdCleanInfo(pcd.getCarrierRrn());
                    PcdClean podCleanInfo = this.getPcdCleanInfo(pcd.getPodRrn());
                    PcdClean doorCleanInfo = this.getPcdCleanInfo(pcd.getDoorRrn());

                    if (carrierCleanInfo != null) {
                        if (!this.isCleanValidPcd(carrierCleanInfo.getCleanOverDate())) {
                            cleanInvalidMsg.append(pcd.getCarrierId());
                        }
                    }

                    if (podCleanInfo != null) {
                        if (!this.isCleanValidPcd(podCleanInfo.getCleanOverDate())) {
                            if (StringUtils.isNotBlank(cleanInvalidMsg.toString())) {
                                cleanInvalidMsg.append(", ");
                            }
                            cleanInvalidMsg.append(pcd.getPodId());
                        }
                    }

                    if (doorCleanInfo != null) {
                        if (!this.isCleanValidPcd(doorCleanInfo.getCleanOverDate())) {
                            if (StringUtils.isNotBlank(cleanInvalidMsg.toString())) {
                                cleanInvalidMsg.append(", ");
                            }
                            cleanInvalidMsg.append(pcd.getDoorId());
                        }
                    }

                    if (cleanInvalidMsg.length() > 0) {
                        cleanInvalidMsg.append(" is out of clean date! ");
                    }
                } else {
                    // 如果不是pcd组装晶舟，则查询open-cst
                    Carrier carrier = new Carrier(carrierRrn);
                    carrier = carrierManager.getCarrier(carrier);
                    if (StringUtils.isNotBlank(carrier.getInstanceId())) {
                        PcdClean carrierCleanInfo = this.getPcdCleanInfo(carrier.getInstanceRrn());
                        if (carrierCleanInfo != null && !this.isCleanValidPcd(carrierCleanInfo.getCleanOverDate())) {
                            cleanInvalidMsg.append(carrier.getInstanceId() + " is out of clean date! ");
                        }
                    } else {
                        cleanInvalidMsg.append("Cassette is invalid!");
                    }
                }
            }
            return cleanInvalidMsg.toString();
        }

        public void updateAllPCDCleanCycleByCategoryAndType(Long facilityRrn, String user, String cateogory, String type,
                                                            double tragetCleanCycle) {
            String namedSpcace = namedObjectManager.getNamedSpace(facilityRrn, ObjectList.ENTITY_KEY);
            updateAllCstCleanCycleByCategoryAndType(facilityRrn, user, cateogory, type, tragetCleanCycle, namedSpcace);
            updateAllPodCleanCycleByCategoryAndType(facilityRrn, user, cateogory, type, tragetCleanCycle, namedSpcace);
            updateAllDoorCleanCycleByCategoryAndType(facilityRrn, user, cateogory, type, tragetCleanCycle, namedSpcace);
        }

        public void updateAllCstCleanCycleByCategoryAndType(Long facilityRrn, String user, String cateogory, String type,
                                                            double tragetCleanCycle, String carrierNamedSpcace) {
            Map<String, Object> getAllCarrierMap = new HashMap<String, Object>();
            getAllCarrierMap.put("category", cateogory);
            getAllCarrierMap.put("type", type);
            getAllCarrierMap.put("namedSpace", carrierNamedSpcace);
            List<Map> allCarrier = carrierManager.getAllCarrier(getAllCarrierMap);
            if (CollectionUtils.isNotEmpty(allCarrier)) {
                for (Map carrierMap : allCarrier) {
                    Map<String, Object> carrierAttrMap = new HashMap<String, Object>();
                    carrierAttrMap.put("carrierRrn", MapUtils.getLong(carrierMap, "carrierRrn"));
                    carrierAttrMap.put("carrierId", MapUtils.getString(carrierMap, "carrierId"));
                    carrierAttrMap.put("carrierDesc", MapUtils.getString(carrierMap, "carrierDesc"));
                    carrierAttrMap.put("carrierStatus", MapUtils.getString(carrierMap, "carrierStatus"));
                    carrierAttrMap.put("carrierType", MapUtils.getString(carrierMap, "carrierType"));

                    carrierAttrMap.put("carrierColor", MapUtils.getString(carrierMap, "carrierColorCode"));
                    carrierAttrMap.put("carrierCategory", MapUtils.getString(carrierMap, "carrierCategoryCode"));
                    // carrierAttrMap.put("carrierPosition", MapUtils.getString(carrierMap, ""));
                    // carrierAttrMap.put("carrierlocation", MapUtils.getString(carrierMap, ""));

                    carrierAttrMap.put("carrierSlotCount", MapUtils.getString(carrierMap, "slotCount"));
                    carrierAttrMap.put("carrierAvailableSlotCount", MapUtils.getString(carrierMap, "availableSlotCount"));
                    carrierAttrMap.put("carrierPollutionLevel", MapUtils.getString(carrierMap, "carrierPollution"));
                    carrierAttrMap.put("carrierAllowableEvent", MapUtils.getString(carrierMap, "carrierAllowable"));
                    carrierAttrMap.put("carrierEngineerGroupID", MapUtils.getString(carrierMap, "carrierEngineer", ""));
                    carrierAttrMap.put("carrierCleanCycle", tragetCleanCycle);

                    carrierManager.updateCarrierEntity(carrierAttrMap, facilityRrn, user);
                }
            }
        }

        public void updateAllPodCleanCycleByCategoryAndType(Long facilityRrn, String user, String cateogory, String type,
                                                            double tragetCleanCycle, String carrierNamedSpcace) {
            Map<String, Object> getAllPodMap = new HashMap<String, Object>();
            getAllPodMap.put("category", cateogory);
            getAllPodMap.put("type", type);
            getAllPodMap.put("namedSpace", carrierNamedSpcace);
            List<Map> allPod = podManager.getAllPod(getAllPodMap);
            if (CollectionUtils.isNotEmpty(allPod)) {
                for (Map podMap : allPod) {
                    Map<String, Object> updatePodMap = new HashMap<String, Object>();
                    updatePodMap.put("podRrn", MapUtils.getLong(podMap, "podRrn"));
                    updatePodMap.put("podId", MapUtils.getString(podMap, "podId", ""));
                    updatePodMap.put("podDesc", MapUtils.getString(podMap, "podDesc", ""));
                    updatePodMap.put("podStatus", MapUtils.getString(podMap, "podStatus", ""));
                    updatePodMap.put("podType", MapUtils.getString(podMap, "podType", ""));
                    updatePodMap.put("podPollutionLevel", MapUtils.getString(podMap, "podPollution", ""));
                    updatePodMap.put("podAllowableEvent", MapUtils.getString(podMap, "podAllowable", ""));
                    updatePodMap.put("podEngineerGroupID", MapUtils.getString(podMap, "podEngineer", ""));
                    updatePodMap.put("podSlotCount", MapUtils.getString(podMap, "podSlotCount", ""));
                    updatePodMap.put("podPosition", MapUtils.getString(podMap, "podLoadPosition", ""));
                    updatePodMap.put("podColor", MapUtils.getString(podMap, "podColor", ""));
                    updatePodMap.put("podCategory", MapUtils.getString(podMap, "podCategoryCode", ""));
                    updatePodMap.put("podCleanCycle", tragetCleanCycle);

                    podManager.updatePodEntity(updatePodMap, facilityRrn, user);
                }
            }
        }

        public void updateAllDoorCleanCycleByCategoryAndType(Long facilityRrn, String user, String cateogory, String type,
                                                             double tragetCleanCycle, String doorNamedSpcace) {
            Map<String, Object> getAllDoorMap = new HashMap<String, Object>();
            getAllDoorMap.put("category", cateogory);
            getAllDoorMap.put("type", type);
            getAllDoorMap.put("namedSpace", doorNamedSpcace);
            List<Map> allDoor = doorManager.getAllDoor(getAllDoorMap);
            if (CollectionUtils.isNotEmpty(allDoor)) {
                for (Map doorMap : allDoor) {
                    Map<String, Object> updateDoorMap = new HashMap<String, Object>();
                    updateDoorMap.put("doorRrn", MapUtils.getLong(doorMap, "doorRrn"));
                    updateDoorMap.put("doorId", MapUtils.getString(doorMap, "doorId", ""));
                    updateDoorMap.put("doorDesc", MapUtils.getString(doorMap, "doorDesc", ""));
                    updateDoorMap.put("doorStatus", MapUtils.getString(doorMap, "doorStatus", ""));
                    updateDoorMap.put("doorType", MapUtils.getString(doorMap, "doorType", ""));
                    updateDoorMap.put("doorColor", MapUtils.getString(doorMap, "doorColor", ""));
                    updateDoorMap.put("doorPosition", MapUtils.getString(doorMap, "doorLoadPosition", ""));
                    updateDoorMap.put("doorSlotCount", MapUtils.getString(doorMap, "doorSlotCount", ""));
                    updateDoorMap.put("doorPollutionLevel", MapUtils.getString(doorMap, "doorPollution", ""));
                    updateDoorMap.put("doorAllowableEvent", MapUtils.getString(doorMap, "doorAllowable", ""));
                    updateDoorMap.put("doorEngineerGroupID", MapUtils.getString(doorMap, "doorEngineer", ""));
                    updateDoorMap.put("doorCategory", MapUtils.getString(doorMap, "doorCategoryCode", ""));
                    updateDoorMap.put("doorCleanCycle", tragetCleanCycle);

                    doorManager.updateDoorEntity(updateDoorMap, facilityRrn, user);
                }
            }
        }

        /**
         * 检查P/C/D是否是目标状态下的未绑定的空P/C/D
         */
        public void checkPcdIsExistAndNotAssemblyOrInUseAndCurrentStatus(String pcdId, Long facilityRrn,
                                                                         String targetStatus) {
            NamedObject no = new NamedObject(pcdId, namedObjectManager.getNamedSpace(facilityRrn, ObjectList.ENTITY_KEY),
                                             ObjectList.ENTITY_KEY);
            no = namedObjectManager.getNamedObject(no);
            if (null == no) {
                no = new NamedObject();
            }
            Assert.isTrue(no.getInstanceRrn() > 0,
                          Errors.create().key(MessageIdList.PCD_NO_CLEANING_EQPT_ID).content("{} is not exist!").args(pcdId)
                                .build());

            if (StringUtils.equals(no.getObjectType(), ObjectList.CARRIER_KEY)) {
                PcdAssembly pcdAll = getPcdAssembly(no.getInstanceRrn(), null, null); // C

                Assert.isTrue(pcdAll == null || StringUtils.isBlank(pcdAll.getCarrierId()),
                              Errors.create().key(MessageIdList.PCD_DEASSEMBLY_PCD_FIRST).content("请先解绑PCD").build());
                // 没有绑定关系,检查是否是空晶舟
                Carrier c = carrierManager.getCarrier(no.getInstanceRrn());

                Assert.isTrue(c.getAvailableSlotCount().equals(c.getSlotCount()),
                              Errors.create().key(MessageIdList.PCD_IS_IN_USE).content("{} is in use!").args(pcdId)
                                    .build());
                Assert.isTrue(StringUtils.equals(c.getCarrierStatus(), targetStatus),
                              Errors.create().key(MessageIdList.PCD_STATUS_ERROR)
                                    .content("{} current " + "status is " + "not {}!").args(pcdId, targetStatus).build());

            } else if (StringUtils.equals(no.getObjectType(), ObjectList.POD_KEY)) {
                PcdAssembly pcdAll = getPcdAssembly(null, no.getInstanceRrn(), null); // P

                Assert.isFalse(pcdAll != null && StringUtils.isNotBlank(pcdAll.getCarrierId()),
                               Errors.create().key(MessageIdList.PCD_DEASSEMBLY_PCD_FIRST).content("请先解绑PCD").build());
                POD p = podManager.getPod(no.getInstanceRrn());

                Assert.isTrue(StringUtils.equals(p.getPodStatus(), targetStatus),
                              Errors.create().key(MessageIdList.PCD_STATUS_ERROR)
                                    .content("{} current status " + "is not {}!").args(pcdId, targetStatus).build());
            } else if (StringUtils.equals(no.getObjectType(), ObjectList.DOOR_KEY)) {
                PcdAssembly pcdAll = getPcdAssembly(null, null, no.getInstanceRrn()); // D
                Assert.isFalse(pcdAll != null && StringUtils.isNotBlank(pcdAll.getCarrierId()),
                               Errors.create().key(MessageIdList.PCD_DEASSEMBLY_PCD_FIRST).content("请先解绑PCD").build());
                Door d = doorManager.getDoor(no.getInstanceRrn());
                Assert.isTrue(StringUtils.equals(d.getDoorStatus(), targetStatus),
                              Errors.create().key(MessageIdList.PCD_STATUS_ERROR)
                                    .content("{} current status" + " " + "is not {}!").args(pcdId, targetStatus).build());
            }
        }

        private void batchOutCleanByPcd(String eqptId, String[] pcdIds, String pcdType, String eventId, Long facilityRrn,
                                        String user) {
            String comments = "Out clean from " + eqptId;
            String targetStatus = PcdStatus.FREE_KEY;
            String currentStatus = PcdStatus.IN_CLEAN_KEY;
            String returnMsg = "";
            for (int i = 0; i < pcdIds.length; i++) {
                statusManager.changePcdStatusByLogEvent(facilityRrn, user, pcdIds[i], pcdType, eventId, comments,
                                                        targetStatus, currentStatus, eqptId);
            }
        }

        /**
         * 判断pcd的清洗时间是否大于设置的最小清洗时间
         */
        private void IsPcdCleaningDateAboveThanMinMinutes(String[] pcdIds, double minMinutes, Long facilityRrn) {
            if (pcdIds.length > 0) {
                for (int i = 0; i < pcdIds.length; i++) {
                    String pcdId = StringUtils.EMPTY;
                    pcdId = pcdIds[i];
                    NamedObject no = new NamedObject(pcdId,
                                                     namedObjectManager.getNamedSpace(facilityRrn, ObjectList.ENTITY_KEY),
                                                     ObjectList.ENTITY_KEY);
                    no = namedObjectManager.getNamedObject(no);
                    Assert.isTrue(no.getInstanceRrn() > 0,
                                  Errors.create().key(MessageIdList.PCD_PCD_MISSING).content("PCD 不存在!").build());

                    PcdClean pc = getPcdCleanInfo(no.getInstanceRrn());
                    Assert.isTrue(pc != null && pc.getInCleanDate() != null,
                                  Errors.create().key(MessageIdList.PCD_NOT_CLEAN_RECORD).content("{} has no clean record!")
                                        .args(pcdId).build());
                    Assert.isFalse(pc.getCleaningMinutes() < minMinutes,
                                   Errors.create().key(MessageIdList.PCD_CLEAN_LESS_MIN_TIME)
                                         .content("{} &#039s clean time is less than" + " min time!").args(pcdId).build());
                }
            }
        }

        private void cleanPcd(String cleanEqptId, Long pcdRrn, String performedBy, String inOrOutClean) {
            if (pcdRrn != null && pcdRrn > 0) {
                NamedObject pcd = new NamedObject(pcdRrn);
                pcd = namedObjectManager.getNamedObject(pcd);
                if (pcd != null & StringUtils.isNotBlank(pcd.getInstanceId())) {
                    if (StringUtils.equalsIgnoreCase(pcd.getObjectType(), ObjectList.CARRIER_KEY) ||
                            StringUtils.equalsIgnoreCase(pcd.getObjectType(), ObjectList.POD_KEY) ||
                            StringUtils.equalsIgnoreCase(pcd.getObjectType(), ObjectList.DOOR_KEY)) {
                        if (StringUtils.equalsIgnoreCase("in", inOrOutClean)) {
                            inCleanPcd(cleanEqptId, pcd, performedBy);
                        } else if (StringUtils.equalsIgnoreCase("out", inOrOutClean)) {
                            outCleanPcd(pcd, performedBy);
                        }
                    }
                }
            }
        }

        /**
         * 开始清洗工作
         *
         * @param pcd
         * @param performedBy
         */
        private void inCleanPcd(String cleanEqptId, NamedObject pcd, String performedBy) {
            PcdClean cleanInfo = getPcdCleanInfo(pcd.getInstanceRrn());
            if (cleanInfo != null && cleanInfo.getInstanceRrn() > 0) {
                TransactionLog transactionLog = transactionLogManager.startTransactionLog(performedBy,
                                                                                          TransactionNames.IN_CLEAN_KEY);
                long currentTime = System.currentTimeMillis();
                cleanInfo.setInCleanDate(new Timestamp(currentTime));
                cleanInfo.setOutCleanDate(null);
                cleanInfo.setCleanEqptId(cleanEqptId);

                pcdDAO.updatePcdCleanInfo(cleanInfo);
                pcdDAO.insertPcdCleanH(transactionLog.getTransRrn(), cleanInfo);

                transactionLogManager.markTransactionLog(transactionLog);
            }
        }

        /**
         * pcd 结束清洗工作
         *
         * @param pcd
         * @param performedBy
         */
        private void outCleanPcd(NamedObject pcd, String performedBy) {
            PcdClean cleanInfo = pcdDAO.getPcdCleanInfo(pcd.getInstanceRrn());
            if (cleanInfo != null && cleanInfo.getInstanceRrn() > 0) {
                TransactionLog transactionLog = transactionLogManager.startTransactionLog(performedBy,
                                                                                          TransactionNames.OUT_CLEAN_KEY);

                // 清洗工作结束后，
                cleanInfo.setInstanceRrn(pcd.getInstanceRrn());
                long currentTime = System.currentTimeMillis();
                cleanInfo.setOutCleanDate(new Timestamp(currentTime)); // 记录清洗结束时间
                long cleanOverTime = (long) (currentTime + (cleanInfo.getCleanCycle() * 24 * 60 * 60 * 1000));
                cleanInfo.setCleanOverDate(new Timestamp(cleanOverTime)); // 更新清洗有效期限
                cleanInfo.setCleanCount(cleanInfo.getCleanCount() + 1); // 追加清洗次数

                String cleanEqptId = cleanInfo.getCleanEqptId();
                cleanInfo.setCleanEqptId(StringUtils.EMPTY); // 结束清洗时清除清洗机台
                pcdDAO.updatePcdCleanInfo(cleanInfo);
                cleanInfo.setCleanEqptId(cleanEqptId); // 历史需要记录结束清洗时的清洗机台
                pcdDAO.insertPcdCleanH(transactionLog.getTransRrn(), cleanInfo);
                // change cassette type 后清洗完成自动切换type
                cleanExchangePCDType(cleanInfo);
                transactionLogManager.markTransactionLog(transactionLog);
            }
        }

        private void batchInCleanByPcd(String eqptId, String[] pcdIds, String pcdType, String eventId, Long facilityRrn,
                                       String user) {
            String comments = "In clean from " + eqptId;
            String targetStatus = PcdStatus.IN_CLEAN_KEY;
            String currentStatus = PcdStatus.WAIT_CLEAN_KEY;
            for (int i = 0; i < pcdIds.length; i++) {
                statusManager.changePcdStatusByLogEvent(facilityRrn, user, pcdIds[i], pcdType, eventId, comments,
                                                        targetStatus, currentStatus, eqptId);
            }
        }

        /**
         * 检查PCD是否是未绑定的空的P/C/D，并且当前状态是WAIT_CLEAN
         *
         * @return 返回空字符串表示是，返回不为空的字符串表示不是
         */
        private void checkPcdIsExist(String[] pcdIds, Long facilityRrn) {
            for (int i = 0; i < pcdIds.length; i++) {
                // 检查PCD是否都存在并且是未绑定的空P/C/D
                checkPcdIsExistAndNotAssemblyOrInUseAndCurrentStatus(pcdIds[i], facilityRrn, PcdStatus.WAIT_CLEAN_KEY);
            }
        }

        @Override
        public void manualExchangePCDType(Carrier carrier) {
            Map<String,Object> map = getAutoChangeCassetteTypes();
            String sourceCstType = MapUtils.getString(map, "sourceCstType");
            String targetCstType = MapUtils.getString(map, "targetCstType");
            Assert.state(StringUtils.equalsIgnoreCase(carrier.getObjectSubtype(), sourceCstType),
                         Errors.create().key(MessageIdList.CARRIER_MANUAL_CHANGE_TYPE_ERROR)
                               .content("Manual change cassette type can only is {}!").args(sourceCstType).build());
            TransactionLog transactionLog = transactionLogManager
                    .startTransactionLog(LocalContext.getUserId(),TransactionNames.EXCHANGE_TYPE_KEY);

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("Manual exchange carrier type! Change Type:")
                         .append(sourceCstType)
                         .append("-->")
                         .append(targetCstType);

            map.put("comments", stringBuilder.toString());
            map.put("carrierRrn", carrier.getInstanceRrn());
            map.put("transRrn", transactionLog.getTransRrn());
            exchangePodAndDoorType(map);

            //手动切换carrier type
            carrier.setObjectSubtype(targetCstType);
            carrier.setInstanceId(StringUtils.EMPTY);
            carrierManager.exchangeCarrierType(carrier, stringBuilder.toString(), transactionLog.getTransRrn(),false);
            transactionLogManager.markTransactionLog(transactionLog);
        }

        @Override
        public void autoExchangePCDType(Long carrierRrn) {
            Map<String,Object> map = getAutoChangeCassetteTypes();
            String sourceCstType = MapUtils.getString(map, "sourceCstType");
            String targetCstType = MapUtils.getString(map, "targetCstType");
            TransactionLog transactionLog = transactionLogManager
                    .startTransactionLog("SYSTEM", TransactionNames.EXCHANGE_TYPE_KEY);
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("Move out auto exchange carrier type! Change Type:")
                         .append(sourceCstType)
                         .append("-->")
                         .append(targetCstType);
            map.put("comments", stringBuilder.toString());
            map.put("carrierRrn", carrierRrn);
            map.put("transRrn", transactionLog.getTransRrn());
            exchangePodAndDoorType(map);

            //moveOut 自动切换carrier type
            Carrier carrier = carrierManager.getCarrier(carrierRrn);
            carrier.setObjectSubtype(targetCstType);
            carrier.setTransId(TransactionNames.EXCHANGE_TYPE_KEY);
            carrier.setTransPerformedby("SYSTEM");
            carrier.setInstanceId(StringUtils.EMPTY);
            carrierManager.exchangeCarrierType(carrier, stringBuilder.toString(), transactionLog.getTransRrn(),true);
            transactionLogManager.markTransactionLog(transactionLog);
        }

        @Override
        public void cleanExchangePCDType(PcdClean pcdClean) {
            Map<String,Object> map = getAutoChangeCassetteTypes();
            String sourceCstType = MapUtils.getString(map, "sourceCstType");
            String targetCstType = MapUtils.getString(map, "targetCstType");
            StringBuilder stringBuilder = new StringBuilder();
            TransactionLog transactionLog = transactionLogManager
                    .startTransactionLog("SYSTEM", TransactionNames.EXCHANGE_TYPE_KEY);
            if (StringUtils.equalsIgnoreCase(pcdClean.getPcdType(), Constants.PCD_TYPE.CARRIER_KEY)) {
                stringBuilder.append("Clean after exchange carrier type! Change Type:")
                             .append(targetCstType)
                             .append("-->")
                             .append(sourceCstType);
                Carrier carrier = carrierManager.getCarrier(pcdClean.getInstanceRrn());
                if (StringUtils.equalsIgnoreCase(targetCstType, carrier.getObjectSubtype())) {
                    carrier.setObjectSubtype(sourceCstType);
                    carrier.setInstanceId(StringUtils.EMPTY);
                    carrier.setTransId(TransactionNames.EXCHANGE_TYPE_KEY);
                    carrier.setTransPerformedby("SYSTEM");
                    carrierManager.exchangeCarrierType(carrier, stringBuilder.toString(), transactionLog.getTransRrn(),false);
                }
            } else if(StringUtils.equalsIgnoreCase(pcdClean.getPcdType(), Constants.PCD_TYPE.POD_KEY)) {
                stringBuilder.append("Clean after exchange pod type! Change Type:")
                             .append(targetCstType)
                             .append("-->")
                             .append(sourceCstType);
                POD pod = podManager.getPod(pcdClean.getInstanceRrn());
                if (StringUtils.equalsIgnoreCase(targetCstType, pod.getObjectSubtype())) {
                    pod.setObjectSubtype(sourceCstType);
                    podDAO.updatePodType(pod, stringBuilder.toString(), transactionLog.getTransRrn());
                }
            } else if(StringUtils.equalsIgnoreCase(pcdClean.getPcdType(), Constants.PCD_TYPE.DOOR_KEY)) {
                stringBuilder.append("Clean after exchange door type! Change Type:")
                             .append(targetCstType)
                             .append("-->")
                             .append(sourceCstType);
                Door door = doorManager.getDoor(pcdClean.getInstanceRrn());
                if (StringUtils.equalsIgnoreCase(targetCstType, door.getObjectSubtype())) {
                    door.setObjectSubtype(sourceCstType);
                    doorDAO.updateDoorType(door, stringBuilder.toString(), transactionLog.getTransRrn());
                }
            } else {

            }
            transactionLogManager.markTransactionLog(transactionLog);
        }

        /**
         * 获取类表值对应关系  change Cassette type
         * A-B
         * @return
         */
        private Map<String,Object> getAutoChangeCassetteTypes() {
            Map<String,Object> map = new HashMap<>();
            boolean flag = true;
            String nameSpace =  namedObjectManager.getNamedSpace(LocalContext.getFacilityRrn(),
                                                                 ObjectList.REFERENCE_FILE_KEY);
            ReferenceFileDetail referenceFileDetail = new ReferenceFileDetail(ReferenceDetailNames.PROCESS_LOCATION_AUTO_CHANGE_CASSETTE_TYPE,
                                                                              nameSpace,
                                                                              ObjectList.REFERENCE_FILE_KEY);
            List<ReferenceFileDetail> referenceFileDetails = referenceFileManager
                    .getReferenceFileDetails(referenceFileDetail, LocalContext.getFacilityRrn());

            if (CollectionUtils.isNotEmpty(referenceFileDetails)) {
                for (ReferenceFileDetail ref : referenceFileDetails) {
                    Matcher mathcher = WipUtils.PCDPATERN.matcher(ref.getData1Value());
                    if (mathcher.matches()){
                        map.put("targetCstType", StringUtils.split(ref.getData1Value(), "-")[1]);
                        map.put("sourceCstType", StringUtils.split(ref.getData1Value(), "-")[0]);
                    }
                }
            }else {
                flag = false;
            }

            if (MapUtils.isEmpty(map)) {
                flag = false;
            }

            Assert.state( flag , Errors.create().key(MessageIdList.REFERENCE_$$AUTO_CHANGE_CASSETTE_TYPE).build());
            return map;
        }

        /**
         * 判断是否绑定pod、door  如果绑定一起改变type
         * @param map
         */
        private void exchangePodAndDoorType(Map<String,Object> map) {
            Long carrierRrn = MapUtils.getLong(map, "carrierRrn");
            Long transRrn = MapUtils.getLong(map, "transRrn");
            String targetCstType = MapUtils.getString(map, "targetCstType");
            String comments = MapUtils.getString(map, "comments");
            PcdAssembly pcdAssembly = pcdDAO.getPcdAssembly(carrierRrn);
            if (ObjectUtils.isNotEmpty(pcdAssembly)){
                // 修改pod type
                if (pcdAssembly.getPodRrn().longValue() > 0) {
                    POD pod = podManager.getPod(pcdAssembly.getPodRrn().longValue());
                    pod.setObjectSubtype(targetCstType);
                    podDAO.updatePodType(pod, comments, transRrn);
                }
                // 修改door type
                if (pcdAssembly.getDoorRrn().longValue() > 0) {
                    Door door = doorManager.getDoor(pcdAssembly.getDoorRrn().longValue());
                    door.setObjectSubtype(targetCstType);
                    doorDAO.updateDoorType(door, comments, transRrn);
                }
            }
        }

    }