SortJobManagementAction.java

package com.mycim.webapp.actions.sort;

import com.fa.sesa.exception.Assert;
import com.fa.sesa.exception.Errors;
import com.fa.sesa.exception.SystemIllegalArgumentException;
import com.fa.sesa.exception.TransformFunc;
import com.fa.sesa.threadlocal.LocalContext;
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.msg.JsonUtils;
import com.mycim.valueobject.LocationNames;
import com.mycim.valueobject.MessageIdList;
import com.mycim.valueobject.automonitor.entity.MonitorCarrierMapping;
import com.mycim.valueobject.consts.ReferenceDetailNames;
import com.mycim.valueobject.consts.SorterEnum;
import com.mycim.valueobject.ems.Carrier;
import com.mycim.valueobject.runcard.util.RunCardConstants;
import com.mycim.valueobject.runcard.util.RunCardStoreSubStatus;
import com.mycim.valueobject.runcard.util.RunCardUtils;
import com.mycim.valueobject.sorter.*;
import com.mycim.valueobject.sys.ReferenceFileDetail;
import com.mycim.valueobject.wip.*;
import com.mycim.webapp.WebUtils;
import com.mycim.webapp.actions.lot.ManualAndSortCheckAction;
import com.mycim.webapp.forms.SortJobForm;
import com.mycim.webapp.forms.lot.LotInfoForm;
import org.apache.struts.action.ActionMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.function.BiFunction;


/**
 * @Author: yibing.liu
 * @Date: 2021/6/30 15:25
 */
public class SortJobManagementAction extends ManualAndSortCheckAction {


    public Map loadCarrierBySrc(Map paramMap) {

        long facilityRrn = LocalContext.getFacilityRrn();
        String type = MapUtils.getString(paramMap, "sortJobType");
        Assert.isFalse(StringUtils.isBlank(type), Errors.create().key(MessageIdList.TYPE_CAN_NOT_EMPTY).build());
        String sourceCarrierId = MapUtils.getString(paramMap, "sourceCarrierId_create");
        Assert.isFalse(StringUtils.isBlank(sourceCarrierId),
                       Errors.create().key(MessageIdList.SOURCE_ID_CAN_NOT_EMPTY).build());
        //目标晶舟ID可以为空,后续由EAP指定
        String targetCarrierId = MapUtils.getString(paramMap, "targetCarrierId_create");
        Integer splitSeq = MapUtils.getInteger(paramMap, "splitSeq");

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

        Carrier sourceCarrier = carrierService.getCarrier(facilityRrn, sourceCarrierId);
        String sourceCarrierCategory = sourceCarrier.getFlagType();

        Lot sourceLot = getLotByCarrierId(sourceCarrierId);
        Assert.state(Objects.nonNull(sourceLot),
                     Errors.create().key(MessageIdList.SOURCE_IS_EMPTY).build());
        if (StringUtils.equals(LotStatus.RUNCARD_HOLD, sourceLot.getLotStatus())) {
            sourceLot = lotQueryService.getLot(sourceLot.getLotId() + RunCardConstants.MAINLOT_ID_IN_MES, facilityRrn);
            long sourceCarrierRrn = sourceLot.getCarrierRrn();
            sourceCarrier = carrierService.getCarrier(sourceCarrierRrn);
        }
        LotRunCardStore sourceRunCardStore = lotRunCardQueryService.getSplitRunCardLotStore(sourceLot.getLotRrn());
        Assert.isFalse(sourceRunCardStore == null || sourceRunCardStore.getRuncardRrn() <= 0,
                       Errors.create().key(MessageIdList.SOURCE_LOT_NOT_RC)
                             .content("Source carrier lot is not runcard lot!").build());

        Carrier targetCarrier = null;
        if (SorterEnum.Constant.SRC_MERGE.equals(type)) {
            // targetCarrier = carrierService.getCarrier(facilityRrn, targetCarrierId);
            // Assert.isFalse(!StringUtils.equals(LotStatus.RUNCARD_FINISH, sourceLot.getLotStatus()),
            //                Errors.create().key("").content("Source carrier lot status must be WAITMERGE!").build());
            //
            // Lot targetLot = lotQueryService.getLotByCarrierId(targetCarrierId, facilityRrn);
            // Assert.isFalse(targetLot == null || targetLot.getLotRrn() <= 0,
            //                Errors.create().key("").content("The targetCarrier has no lot, please check!").build());
            //
            //
            // if (StringUtils.equals(LotStatus.RUNCARD_HOLD, targetLot.getLotStatus())) {
            //     targetLot = lotQueryService.getLot(targetLot.getLotId() + RunCardConstants.MAINLOT_ID_IN_MES,
            //     facilityRrn);
            // }
            // Assert.isFalse(!StringUtils.equals(LotStatus.RUNCARD_FINISH, targetLot.getLotStatus()),
            //                Errors.create().key("").content("Target carrier lot status must be WAITMERGE!").build());
            //
            // LotRunCardStore targetRunCardStore = lotRunCardQueryService.getSplitRunCardLotStore(targetLot
            // .getLotRrn());
            // Assert.isFalse(targetRunCardStore == null || targetRunCardStore.getRuncardRrn() <= 0,
            //                Errors.create().key("").content("Target carrier lot is not runcard lot!").build());
            //
            // targetCarrier = carrierService.getCarrier(targetLot.getCarrierRrn());
            // Assert.isFalse(!StringUtils.equals(sourceCarrierCategory, targetCarrier.getFlagType()),
            //                Errors.create().key("").content("Source carrier category is not same as target carrier
            //                category!").build());
        } else {
            if (StringUtils.isNotBlank(targetCarrierId)) {
                targetCarrier = carrierService.getCarrier(facilityRrn, targetCarrierId);
                sorterService.checkCarrierStatus(targetCarrier);
                Assert.isFalse(!StringUtils.equals(sourceCarrierCategory, targetCarrier.getFlagType()),
                               Errors.create().key(MessageIdList.CATEGORY_NOT_SAME).build());
                Assert.state(!lotAutoMonitorInqService.checkMonitorCarrierUsed(targetCarrier.getInstanceRrn()),
                             Errors.create().key(MessageIdList.AUTOMONITOR_CARRIER_INUSED).build());
            } else {
                targetCarrier = new Carrier();
            }
        }

        List<Map> sourceUnits = wipQueryService.getUnitListByCarrier(sourceCarrier.getInstanceRrn());
        List<Map> targetUnits = wipQueryService.getUnitListByCarrier(targetCarrier.getInstanceRrn());
        if (SorterEnum.Constant.SRC_SPLIT.equals(type)) {
            Assert.isFalse(sourceUnits == null || sourceUnits.isEmpty(),
                           Errors.create().key(MessageIdList.SOURCE_IS_EMPTY).build());

            Assert.isFalse(targetUnits != null && !targetUnits.isEmpty(),
                           Errors.create().key(MessageIdList.TARGET_CARRIER_NOT_EMPTY).build());

            //StringUtils.equalsIgnoreCase(RunCardConstants.STATUS_BANK_IN, sourceRunCardStore.getSubStatus()) &&
            Assert.isFalse(!(StringUtils.equals(LotStatus.RUNCARD_WAITSPLIT, sourceLot.getLotStatus())),
                           Errors.create().key(MessageIdList.SOURCE_LOT_NOT_WAIT_SPIT).build());
            if (StringUtils.isNotBlank(targetCarrierId)) {
                carrierService.compareRealCarrierAndCheckAvailabile(facilityRrn, sourceCarrierId, targetCarrierId);
            }

            Assert.isFalse(splitSeq == null || splitSeq <= 0,
                           Errors.create().key(MessageIdList.SPLIT_SEQ_ERROR).build());

            buildUnitsInfoBySrcSplit(sourceLot, sourceUnits, targetUnits, sourceRunCardStore, splitSeq, facilityRrn);
            addChooseFlag(sourceUnits, SorterEnum.HardCode.FALSE_LOW);
            addChooseFlag(targetUnits, SorterEnum.HardCode.FALSE_LOW);
        } else {
            addChooseFlag(sourceUnits, SorterEnum.HardCode.FALSE_LOW);
            addChooseFlag(targetUnits, SorterEnum.HardCode.FALSE_LOW);
        }

        String sourceDate;
        int targetSlotCount =
                targetCarrier.getSlotCount() == null ? 25 : targetCarrier.getSlotCount().intValue();// 默认槽位25片
        String targetData = initEmptyCarrier(targetSlotCount);
        sourceDate = sorterQueryService.parseToJsonT(sourceUnits, new FilterParam<>(String.class));
        if (targetUnits != null && !targetUnits.isEmpty()) {
            targetData = sorterQueryService.parseToJsonT(targetUnits, new FilterParam<>(String.class));
        }
        // SPLIT_3PORT 才需传递 targetCarrierId2 , 故此处可不传
        checkTargetCarrier(type, targetCarrierId, StringUtils.EMPTY, sourceCarrier, targetCarrier);

        data.put("sourceData", sourceDate);
        data.put("targetData", targetData);
        data.put("soruceMaxSlot", sourceCarrier.getSlotCount());
        data.put("targetMaxSlot", targetSlotCount);
        return data;
    }

    /**
     * 加载 RunCard的分批集合
     *
     * @param mapping
     * @param sortJobForm
     * @param request
     * @param response
     * @return
     */
    public Map loadSRCSplitList(ActionMapping mapping, SortJobForm sortJobForm, HttpServletRequest request,
                                HttpServletResponse response) {

        long facilityRrn = LocalContext.getFacilityRrn();
        String carrierId = sortJobForm.getSourceCarrierId();

        List<Object> jsa = new ArrayList<>();

        if (StringUtils.isBlank(carrierId)) {
            return ajaxReturn(null, null);
        }


        carrierService.getCarrier(facilityRrn, carrierId = carrierId.trim());

        Lot sourceLot = getLotByCarrierId(carrierId);
        Assert.state(Objects.nonNull(sourceLot),
                     Errors.create().key(MessageIdList.SOURCE_IS_EMPTY).build());
        long carrierRrn = sourceLot.getCarrierRrn();
        if (StringUtils.equals(LotStatus.RUNCARD_HOLD, sourceLot.getLotStatus())) {
            sourceLot = lotQueryService.getLot(sourceLot.getLotId() + RunCardConstants.MAINLOT_ID_IN_MES, facilityRrn);
        }

        LotRunCardStore lotRunCardStore = lotRunCardQueryService.getSplitRunCardLotStore(sourceLot.getLotRrn());
        Assert.isFalse(lotRunCardStore == null || lotRunCardStore.getRuncardRrn() <= 0,
                       Errors.create().key(MessageIdList.SOURCE_LOT_NOT_RC).build()); //该晶舟中批次不是Runcard 批次!

        List<LotRunCardSplit> splitSet = lotRunCardQueryService.getLotRunCardSplit(lotRunCardStore.getRuncardRrn());
        List<Map<String, Object>> splitLots = lotRunCardQueryService.getSplitedRunCardLots(
                lotRunCardStore.getRuncardRrn());
        Map<Integer, Integer> splitRunCardSeqMap = new HashMap<>(splitLots.size());
        if (CollectionUtils.isNotEmpty(splitLots)) {
            splitLots.forEach(s -> {
                int splitSeq = MapUtils.getInteger(s, "splitSeq", -1);
                if (splitSeq > 0) {
                    splitRunCardSeqMap.put(splitSeq, splitSeq);
                }
            });
        }

        List<SorterBean> subJobs = sorterQueryService.getAllSortJobBySourceCarrierRrn(carrierRrn);
        Map<Integer, Integer> sortSplitSeqMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(subJobs)) {
            subJobs.forEach(s -> {
                int splitSeq = NumberUtils.toInt(s.getJsonAttributeData2(), -1);
                if (splitSeq > 0) {
                    sortSplitSeqMap.put(splitSeq, splitSeq);
                }
            });
        }

        for (LotRunCardSplit lrcs : splitSet) {
            boolean notSplited = true;
            if (splitRunCardSeqMap.containsKey(lrcs.getSplitSeq())) {
                notSplited = false;
            }
            if (sortSplitSeqMap.containsKey(lrcs.getSplitSeq())) {
                notSplited = false;
            }
            if (notSplited) {
                Map<String, Object> js = new HashMap<>();
                js.put("value", lrcs.getSplitSeq());
                js.put("key", lrcs.getUnitIds());
                jsa.add(js);
            }
        }

        //RunCard剩余的需要拆分的晶圆将放在母批的晶舟中。
        return ajaxReturn("", jsa.size() == 1 ? new ArrayList() : jsa);
    }

    public Map loadCarrierByAutoMonitor(Map paramMap) {
        Long facilityRrn = LocalContext.getFacilityRrn();

        String sourceCarrierId = MapUtils.getString(paramMap, "sourceCarrierId_create");
        Assert.state(StringUtils.isNotBlank(sourceCarrierId),
                     Errors.create().key(MessageIdList.SOURCE_ID_CAN_NOT_EMPTY).build());

        String targetCarrierId = MapUtils.getString(paramMap, "targetCarrierId_create");
        Assert.state(StringUtils.isNotBlank(sourceCarrierId),
                     Errors.create().key(MessageIdList.TARGET_ID_CAN_NOT_EMPTY).build());

        Carrier sourceCarrier = carrierService.getCarrier(facilityRrn, sourceCarrierId);
        Carrier targetCarrier = carrierService.getCarrier(facilityRrn, targetCarrierId);

        String sourceDate = initEmptyCarrier(sourceCarrier.getSlotCount().intValue());
        String targetData = initEmptyCarrier(targetCarrier.getSlotCount().intValue());

        List<MonitorCarrierMapping> monitorCarrierMappings = lotAutoMonitorInqService.getMonitorCarrierMappings(
                targetCarrier.getInstanceRrn());

        Assert.state(CollectionUtils.isNotEmpty(monitorCarrierMappings),
                     Errors.create().content("Target monitor cassette not have sort set").build());

        // create target unit info
        List<Map<String, Object>> targetUnitList = new ArrayList<Map<String, Object>>();
        for (long i = 1; i <= targetCarrier.getSlotCount().longValue(); i++) {
            Map<String, Object> unitMap = null;
            for (MonitorCarrierMapping carrierMapping : monitorCarrierMappings) {
                if (carrierMapping.getPosition().longValue() == i) {
                    unitMap = buildUnitMapByAutoMonitor(carrierMapping.getPosition().longValue(),
                                                        carrierMapping.getUnitId(), carrierMapping.getUnitRrn(),
                                                        carrierMapping.getLotId());
                }
            }

            if (unitMap == null) {
                unitMap = buildUnitMapByAutoMonitor(i, StringUtils.EMPTY, NumberUtils.LONG_ZERO, StringUtils.EMPTY);
            }

            targetUnitList.add(unitMap);
        }
        targetData = sorterQueryService.parseToJsonT(targetUnitList, new FilterParam<>(String.class));

        // source unit list
        List<Lot> carrierLotList = lotInqService.getLotListByCarrierId(sourceCarrierId);

        List<Unit> sourceCarrierUnitList = new ArrayList<>();
        List<Map<String, Object>> sourceUnitList = new ArrayList<Map<String, Object>>();
        for (Lot sourceCarrierLot : carrierLotList) {
            List<Unit> unitList = wipQueryService.getUnitList(sourceCarrierLot.getLotRrn());
            unitList.stream().forEach(unit -> {
                unit.setLotId(sourceCarrierLot.getLotId());
            });
            sourceCarrierUnitList.addAll(unitList);
        }
        for (long i = 1; i <= sourceCarrier.getSlotCount().longValue(); i++) {
            Map<String, Object> unitMap = null;
            for (Unit sourceUnit : sourceCarrierUnitList) {
                if (sourceUnit.getPositionInCarrier().longValue() == i) {
                    boolean needSort = false;
                    for (MonitorCarrierMapping carrierMapping : monitorCarrierMappings) {
                        if (sourceUnit.getUnitRrn() == carrierMapping.getUnitRrn().longValue()) {
                            needSort = true;
                        }
                    }
                    if (!needSort) {
                        unitMap = buildUnitMapByAutoMonitor(sourceUnit.getPositionInCarrier().longValue(),
                                                            sourceUnit.getUnitId(), sourceUnit.getUnitRrn(),
                                                            sourceUnit.getLotId());
                    }
                }
            }

            if (unitMap == null) {
                unitMap = buildUnitMapByAutoMonitor(i, StringUtils.EMPTY, NumberUtils.LONG_ZERO, StringUtils.EMPTY);
            }

            sourceUnitList.add(unitMap);
        }

        sourceDate = sorterQueryService.parseToJsonT(sourceUnitList, new FilterParam<>(String.class));

        Map<String, Object> data = new HashMap<String, Object>();
        data.put("sourceData", sourceDate);
        data.put("targetData", targetData);
        data.put("soruceMaxSlot", sourceCarrier.getSlotCount());
        data.put("targetMaxSlot", targetCarrier.getSlotCount());

        return data;
    }

    /**
     * 加载 晶舟 中的晶圆
     *
     * @param paramMap
     * @return
     * @throws Exception
     */
    public Map loadCarrier(Map paramMap) throws Exception {

        long facilityRrn = LocalContext.getFacilityRrn();

        String type = MapUtils.getString(paramMap, "sortJobType");
        Assert.isFalse(StringUtils.isBlank(type), Errors.create().key(MessageIdList.TYPE_CAN_NOT_EMPTY).build());
        String sourceCarrierId = MapUtils.getString(paramMap, "sourceCarrierId_create");
        Assert.isFalse(StringUtils.isBlank(sourceCarrierId),
                       Errors.create().key(MessageIdList.SOURCE_ID_CAN_NOT_EMPTY).build());
        //目标晶舟ID可以为空,后续由EAP指定
        String targetCarrierId = MapUtils.getString(paramMap, "targetCarrierId_create");
        String targetCarrierId2 = MapUtils.getString(paramMap, "targetCarrierId2_create");

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

        Carrier sourceCarrier = carrierService.getCarrier(facilityRrn, sourceCarrierId);
        Carrier targetCarrier = StringUtils.isNotBlank(targetCarrierId) ? carrierService.getCarrier(facilityRrn,
                                                                                                    targetCarrierId)
                : new Carrier();

        Assert.isFalse((SortJob.ObjectType.DUMMY.toString().equals(sourceCarrier.getObjectSubtype()) ||
                               SortJob.ObjectType.DUMMY.toString().equals(targetCarrier.getObjectSubtype())),
                       Errors.create().key(MessageIdList.CANNOT_USE_DUMMY_CARRIER).build());

        checkTargetCarrier(type, targetCarrierId, targetCarrierId2, sourceCarrier, targetCarrier);

        long sourceCarrierRrn = sourceCarrier.getInstanceRrn();
        long targetCarrierRrn = targetCarrier.getInstanceRrn();
        List<Map> sourceUnits = wipQueryService.getUnitListByCarrier(sourceCarrierRrn);
        List<Map> targetUnits =
                targetCarrierRrn > 0 ? wipQueryService.getUnitListByCarrier(targetCarrierRrn) : new ArrayList<>();

        Lot sourceLot = checkCarrierForLoad(sourceCarrier, targetCarrier, type, sourceUnits, targetUnits, facilityRrn);

        if (SorterEnum.Check.isProdLotExchange(type)){
            //RC不允许在此进行Exchange操作。
            //先检查是不是RC的批次
            Assert.state(!lotRunCardQueryService.checkLotInRunCard(sourceLot.getLotRrn()),
                         Errors.create().key(MessageIdList.IS_RC_LOT).content("{} is a RunCard Lot! Can't operate here!").args(sourceLot.getLotId()).build());
        }

        addChooseFlag(sourceUnits, SorterEnum.HardCode.TRUE_LOW);
        addChooseFlag(targetUnits, SorterEnum.HardCode.FALSE_LOW);

        String sourceDate;
        int targetSlotCount =
                targetCarrier.getSlotCount() == null ? 25 : targetCarrier.getSlotCount().intValue();// 默认槽位25片
        String targetData = initEmptyCarrier(targetSlotCount);
        FilterParam filterParam;
        if (SorterEnum.Constant.SRC_MERGE.equals(type)) {
            List<Unit> childUnitList = wipQueryService.getUnitList(sourceLot.getLotRrn());
            List<Unit> parentUnitList = wipQueryService.getUnitList(sourceLot.getCreatedPlanLotRrn());
            sourceUnits = parseUnits(childUnitList);
            targetUnits = parseUnits(parentUnitList);
        }
        if (SorterEnum.Check.isFlip(type)) {
            Assert.isFalse(StringUtils.isBlank(sourceLot.getFlipType()), Errors.create().key(MessageIdList.LOT_NOT_SET)
                                                                               .content(
                                                                                       "Flip property is not set for " +
                                                                                               "Lot {}!")
                                                                               .args(sourceLot.getLotId()).build());
            filterParam = new FilterParam<>("lotId", String.class);
            if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {
                sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
                sourceUnits = parseUnits(wipQueryService.getUnitList(sourceLot.getLotRrn()));
            }
            filterParam.setMark(sourceLot.getFlipType());
            filterParam.setCustomColumn("mark");
            sourceDate = sorterQueryService.parseToJsonT(sourceUnits, filterParam);
            filterParam.setMark(SorterEnum.Constant.getOpposite(sourceLot.getFlipType()));
            targetData = sorterQueryService.parseToJsonT(sourceUnits, filterParam);
        }else if (SorterEnum.Check.isOffLineCheckT7Code(type) || SorterEnum.Check.isOfflineReadMaterialId(type)) {
            filterParam = new FilterParam<>(String.class);
            if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {
                sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
                sourceUnits = parseUnits(wipQueryService.getUnitList(sourceLot.getLotRrn()));
            }
            sourceDate = sorterQueryService.parseToJsonT(sourceUnits, filterParam);
            targetData = sorterQueryService.parseToJsonT(sourceUnits, filterParam);
        }else {
            filterParam = new FilterParam<>(String.class);
            sourceDate = sorterQueryService.parseToJsonT(sourceUnits, filterParam);
            if (targetUnits != null && !targetUnits.isEmpty()) {
                targetData = sorterQueryService.parseToJsonT(targetUnits, filterParam);
            }
        }
        data.put("sourceData", sourceDate);
        data.put("targetData", targetData);
        data.put("soruceMaxSlot", sourceCarrier.getSlotCount());
        data.put("targetMaxSlot", targetSlotCount);


        return data;
    }

    private void checkTargetCarrier(String type, String targetCarrierId, String targetCarrierId2, Carrier sourceCarrier,
                                    Carrier targetCarrier) {
        if (SorterEnum.Check.targetCarrierMayBeEmpty(type)) {
            //Target carrier 可能为空
            if (targetCarrier.isNotEmpty()) {
                //用户指定了Cassette 1 后,才检查
                sorterService.checkCarrierStatus(targetCarrier);

                Assert.isFalse(!StringUtils.equals(sourceCarrier.getFlagType(), targetCarrier.getFlagType()),
                               Errors.create().key(MessageIdList.CATEGORY_NOT_SAME).build());
                // SPLIT_3PORT ignore type verify
                Assert.isFalse(
                        !StringUtils.equals(sourceCarrier.getObjectSubtype(), targetCarrier.getObjectSubtype()) &&
                                !StringUtils.equals(SorterEnum.Constant.SPLIT_3PORT, type),
                        Errors.create().key(MessageIdList.CARRIER_TYPE_NOT_SAME).build());
            }
        }
        if (SorterEnum.Check.needCheckTargetCarrier(type)) {
            //需要检查目标晶舟的type
            Assert.isFalse(StringUtils.isBlank(targetCarrierId),
                           Errors.create().key(MessageIdList.TARGET_ID_CAN_NOT_EMPTY).build());
            if (StringUtils.equals(type, SorterEnum.Constant.SPLIT_3PORT)) {
                Assert.isFalse(StringUtils.isBlank(targetCarrierId2),
                               Errors.create().key(MessageIdList.TARGET_ID_CAN_NOT_EMPTY).build());
                Carrier targetCarrier2 = carrierService.getCarrier(LocalContext.getFacilityRrn(), targetCarrierId2);
                sorterQueryService.checkPort2CstForLoad(sourceCarrier, targetCarrier, targetCarrier2);
            }
        }
    }

    public Map loadCarrierByExchange(Map paramMap) {
        long facilityRrn = LocalContext.getFacilityRrn();
        String type, sourceCarrierId, targetCarrierId;

        Assert.isFalse(StringUtils.isBlank(type = MapUtils.getString(paramMap, "sortJobType")),
                       Errors.create().key(MessageIdList.TYPE_CAN_NOT_EMPTY).build());
        Assert.isFalse(StringUtils.isBlank(sourceCarrierId = MapUtils.getString(paramMap, "sourceCarrierId_create")),
                       Errors.create().key(MessageIdList.SOURCE_ID_CAN_NOT_EMPTY).build());

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

        Carrier sourceCarrier = carrierService.getCarrier(facilityRrn, sourceCarrierId);
        Carrier targetCarrier = StringUtils.isNotBlank(targetCarrierId = MapUtils.getString(paramMap, "targetCarrierId_create")) ?
                carrierService.getCarrier(facilityRrn, targetCarrierId) : new Carrier();        //目标晶舟ID可以为空,后续由EAP指定

        List<BiFunction<Carrier, Carrier, TransformFunc>> functionList = this.buildCarrierCheckFunction(type, facilityRrn);

        TransformFunc transformFunc;
        for (BiFunction<Carrier, Carrier, TransformFunc> bi:functionList){
            if ((transformFunc = bi.apply(sourceCarrier, targetCarrier)) != null) throw new SystemIllegalArgumentException(transformFunc);
        }

        long sourceCarrierRrn = sourceCarrier.getInstanceRrn();
        long targetCarrierRrn = targetCarrier.getInstanceRrn();
        List<Map> sourceUnits = new ArrayList<>();
        List<Map> targetUnits = targetCarrierRrn > 0 ? wipQueryService.getUnitListByCarrier(targetCarrierRrn) : new ArrayList<>();

        // 获取LOT
        Lot sourceLot = getLotByCarrierId(sourceCarrierId);
        Assert.state(sourceLot != null, Errors.create().key(MessageIdList.SOURCE_IS_EMPTY).build());
        if (SorterEnum.Check.isSrcExchange(type)){
            //先检查是不是RC的批次
            Assert.isFalse(!lotRunCardQueryService.checkLotInRunCard(sourceLot.getLotRrn()),
                           Errors.create().key(MessageIdList.NOT_RC_LOT).content("{} is not a RunCard Lot!").args(sourceLot.getLotId()).build());

            if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {//MAINRC 不能用carrierRrn去查unit,因为它和原Lot是共用Cassette的。
                sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
                List<Unit> sourceUnitList = wipQueryService.getUnitList(sourceLot.getLotRrn());
                sourceUnits = parseUnits(sourceUnitList);
            } else {
                sourceUnits = wipQueryService.getUnitListByCarrier(sourceCarrierRrn);
            }
        } else {
            //先检查是不是RC的批次
            Assert.state(!lotRunCardQueryService.checkLotInRunCard(sourceLot.getLotRrn()),
                           Errors.create().key(MessageIdList.IS_RC_LOT).content("{} is a RunCard Lot! Can't operate here!").args(sourceLot.getLotId()).build());
            sourceUnits = wipQueryService.getUnitListByCarrier(sourceCarrierRrn);
        }
        // 若为runcard 特殊状态 则不需要hold 就可建sortJob
        if (!LotStatus.isRunCardSpecialStatus(sourceLot.getLotStatus())) {
            Assert.isFalse(!LotStatus.HOLD.equals(sourceLot.getLotStatus()) && !LotStatus.isRunCardHold(sourceLot.getLotStatus()),
                           Errors.create().key(MessageIdList.LOT_MUST_HOLD).build());
        }

        //获取unitList ,原晶舟必须有晶圆
        Assert.isFalse(sourceUnits == null || sourceUnits.isEmpty(), Errors.create().key(MessageIdList.CARRIER_NO_UNIT).build());
        Assert.isFalse((targetUnits != null && !targetUnits.isEmpty()), Errors.create().key(MessageIdList.TARGET_CARRIER_NOT_EMPTY).build());

        Assert.isFalse(targetCarrier.getInstanceRrn() > 0 && targetCarrier.getAvailableSlotCount() < sourceLot.getQty1(),
                       Errors.create().content("CST {}'s qty is {} > {}")
                             .args(sourceCarrier.getInstanceId(), sourceLot.getInt_qty1(), targetCarrier.getSlotCount()).build());

        // 3、获取ProcessLocation
        String processLocation = sourceLot.getProcessLocation();

        addChooseFlag(sourceUnits, SorterEnum.HardCode.TRUE_LOW);
        addChooseFlag(targetUnits, SorterEnum.HardCode.FALSE_LOW);

        String sourceDate;
        int targetSlotCount = targetCarrier.getSlotCount() == null ? 25 : targetCarrier.getSlotCount().intValue();// 默认槽位25片
        String targetData = initEmptyCarrier(targetSlotCount);
        FilterParam filterParam = new FilterParam<>(String.class);
        sourceDate = sorterQueryService.parseToJsonT(sourceUnits, filterParam);

        data.put("sourceData", sourceDate);
        data.put("targetData", targetData);
        data.put("soruceMaxSlot", sourceCarrier.getSlotCount());
        data.put("targetMaxSlot", targetSlotCount);
        data.put("processLocation", processLocation);

        return data;
    }


    /**
     * 保存 SortJob
     *
     * @param paramMap
     * @return
     */
    public Map saveSortJob(Map paramMap) {

        String type = MapUtils.getString(paramMap, "sortJobType");
        Assert.isFalse(StringUtils.isBlank(type), Errors.create().key(MessageIdList.TYPE_CAN_NOT_EMPTY).build());
        String sourceCarrierId = MapUtils.getString(paramMap, "sourceCarrierId_create");
        Assert.isFalse(StringUtils.isBlank(sourceCarrierId),
                       Errors.create().key(MessageIdList.SOURCE_ID_CAN_NOT_EMPTY).build());
        //目标晶舟ID可以为空,后续由EAP指定
        String targetCarrierId = MapUtils.getString(paramMap, "targetCarrierId_create");
        String targetCarrierId2 = MapUtils.getString(paramMap, "targetCarrierId2_create");

        String sourceUnitListStr = MapUtils.getString(paramMap, "sourceListValues");
        String targetUnitListStr = MapUtils.getString(paramMap, "targetListValues");
        Integer splitSeq = MapUtils.getInteger(paramMap, "splitSeq", -1);

        SortJobForm sortJobForm = new SortJobForm(type, sourceCarrierId, targetCarrierId, targetCarrierId2,
                                                  sourceUnitListStr, targetUnitListStr);
        //校验
        sortJobForm.setSplitSeq(splitSeq);//runCard split need splitSeq
        SortJobBean sortJobBean = this.buildSortJobBean(sortJobForm);

        Assert.isFalse(sortJobBean == null, Errors.create().key(MessageIdList.TYPE_NOT_EXIST).build());

        //创建SortJob
        sortJobBean.setTransPerformedby(LocalContext.getUserId());
        sorterService.addSortJob(sortJobBean);

        return ajaxReturn("Save succeeded!", null);
    }

    // pilot sortjob 参数组装
    public void pilotSplitSortJob(Map param) {
        //需要判断是否
        List<Map> childLots = (List<Map>) WebUtils.getCacheString2Obj(MapUtils.getString(param, "cacheChildLots"));
        LotInfoForm lotInfoForm = (LotInfoForm) WebUtils.getCacheString2Obj(MapUtils.getString(param, "cacheLotInfo"));
        Assert.state(CollectionUtils.isNotEmpty(childLots),
                     Errors.create().key(MessageIdList.PILOT_TARGET_CARRIER_EMPTY)
                           .content("The target carrier has not been selected!").build());
        String targetListValues = MapUtils.getString(childLots.get(0), "unitList");
        Map<String, String> map = new HashMap();
        map.put("sortJobType", MapUtils.getString(param, "sortJobType"));
        map.put("targetCarrierId_create", MapUtils.getString(childLots.get(0), "carrierId"));
        map.put("sourceCarrierId_create", lotInfoForm.getCarrierId());
        map.put("sourceListValues", buildCarrierList(lotInfoForm.getSourceListValues(), lotInfoForm.getLotRrn()));
        map.put("targetListValues", buildCarrierList(targetListValues, lotInfoForm.getLotRrn()));
        saveSortJob(map);
    }

    private Lot getLotByCarrierId(String carrierId) {
        List<Lot> lotList = lotInqService.getLotListByCarrierId(carrierId);
        Assert.state(lotList.size() <= 1, Errors.create().key(MessageIdList.REWORK_SAME_CARRIER_FOR_LOTS)
                                               .content("More than one lot in the same Carrier.").build());
        if (CollectionUtils.isNotEmpty(lotList)) {
            return lotList.iterator().next();
        }
        return null;
    }

    private void buildUnitsInfoBySrcSplit(Lot sourceLot, List<Map> sourceUnits, List<Map> targetUnits,
                                          LotRunCardStore sourceRunCardStore, Integer splitSeq, Long facilityRrn) {

        List<LotRunCardSplit> splitSet = lotRunCardQueryService.getLotRunCardSplit(sourceRunCardStore.getRuncardRrn());
        LotRunCardSplit splitInfo = null;
        for (LotRunCardSplit set : splitSet) {
            if (set.getSplitSeq().equals(splitSeq)) {
                splitInfo = set;
                break;
            }
        }
        if (splitInfo != null) {
            String[] units = StringUtils.split(splitInfo.getUnitRrns(), ",");
            for (String unitRrnStr : units) {
                Long unitRrn = RunCardUtils.patternUnitRrn(unitRrnStr);
                String unitId = RunCardUtils.buildRunCardUnitId(wipQueryService.getUnitIdByRrn(unitRrn));
                Unit unit = wipQueryService.getUnit(facilityRrn, unitId);

                for (int i = 0; i < sourceUnits.size(); i++) {
                    Map sourceUnit = sourceUnits.get(i);
                    if (MapUtils.getLongValue(sourceUnit, "unitRrn") == unit.getUnitRrn()) {
                        targetUnits.add(sourceUnits.remove(i));
                        break;
                    }
                }
            }
        }

        Assert.isFalse(targetUnits.isEmpty(), Errors.create().key(MessageIdList.SPLIT_INFO_WAFER_NOT_EXIST).build());

    }

    private void addChooseFlag(List<Map> units, String chooseFlag) {
        for (Map unit : units) {
            unit.put("chooseFlag", chooseFlag);
        }
    }

    private SortJobBean buildSortJobBean(SortJobForm sjf) {
        long facilityRrn = LocalContext.getFacilityRrn();
        Carrier sCarrier = carrierService.getCarrier(facilityRrn, sjf.getSourceCarrierId());
        Assert.isTrue(sCarrier != null,
                       Errors.create().key(MessageIdList.SOURCE_ID_NOT_EXIST).build());
        Assert.isTrue(sCarrier.getInstanceRrn() > 0,
                      Errors.create().key(MessageIdList.SOURCE_ID_NOT_EXIST).build());
        Carrier tCarrier = StringUtils.isBlank(sjf.getTargetCarrierId()) ? new Carrier() : carrierService.getCarrier(
                facilityRrn, sjf.getTargetCarrierId());
        Carrier tCarrier2 = StringUtils.isBlank(sjf.getTargetCarrierId2()) ? null : carrierService.getCarrier(
                facilityRrn, sjf.getTargetCarrierId2());
        sjf.setSourceCarrierRrn(sCarrier.getInstanceRrn());
        sjf.setTargetCarrierRrn(tCarrier.getInstanceRrn());
        sjf.setTargetCarrierRrn2(tCarrier2 == null ? null : tCarrier2.getInstanceRrn());
        ReferenceFileDetail referenceFileDetail = getReferenceFileDetail(ReferenceDetailNames.SORT_HOLD_CODE,
                                                                         sjf.getJobType(),
                                                                         LocalContext.getFacilityRrn());
        Assert.isFalse(referenceFileDetail == null,
                       Errors.create().key(MessageIdList.REFERENCE_NOT_EXIST).args(ReferenceDetailNames.SORT_HOLD_CODE)
                             .build());
        String sortHoldCode = referenceFileDetail.getData1Value();
        Assert.isFalse(StringUtils.isBlank(sortHoldCode), Errors.create().key(MessageIdList.REFERENCE_NOT_EXIST_VALUE)
                                                                .args(sjf.getJobType(),
                                                                      ReferenceDetailNames.SORT_HOLD_CODE).build());
        sjf.setSortHoldCode(sortHoldCode);


        switch (sjf.getJobType()) {
            case SorterEnum.Constant.EXCHANGE:
                return buildExchangeSortJob(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.SPLIT:
                return buildSplitSortJob(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.MERGE:
                return buildMergeSortJob(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.SPLIT_3PORT:
                return buildSplitMorePortSortJob(sCarrier, tCarrier, tCarrier2, sjf);
            case SorterEnum.Constant.SRC_SPLIT:
                return buildRunCardSplitSortJob(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.SRC_MERGE:
                return buildRunCardMergeSortJob(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.AUTO_MONITOR:
                return buildAutoMonitorSortInfo(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.FLIP_SIDE:
                return buildFlipSideSortInfo(sCarrier, sCarrier, sjf);
            case SorterEnum.Constant.PILOT_SPLIT:
                return buildPilotSplitSortJob(sCarrier, tCarrier, sjf);
            case SorterEnum.Constant.OFFLINE_CHECK_T7CODE:
                return buildCheckT7CodeSortInfo(sCarrier, sjf);
            case SorterEnum.Constant.SPECIAL_EXCHANGE:
                return buildExchangeSortJobBean(sCarrier, tCarrier, sjf, SorterEnum.Constant.SPECIAL_EXCHANGE);
            case SorterEnum.Constant.SRC_EXCHANGE:
                return buildExchangeSortJobBean(sCarrier, tCarrier, sjf, SorterEnum.Constant.SRC_EXCHANGE);
            case SorterEnum.Constant.SRC_SPECIAL_EXCHANGE:
                return buildExchangeSortJobBean(sCarrier, tCarrier, sjf, SorterEnum.Constant.SRC_SPECIAL_EXCHANGE);
            case SorterEnum.Constant.OFFLINE_READ_MATERIALID:
                return buildReadMaterialIdSortInfo(sCarrier, sjf);
            default:
                return null;
        }
    }


    private SortJobBean buildExchangeSortJobBean(Carrier sc, Carrier tc, SortJobForm sjf, String type) {
        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sc.getObjectSubtype()), Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sjf.getSourceListValues());
        Assert.isFalse(!sourceUnitList.isEmpty(), Errors.create().key(MessageIdList.SOURCE_CARRIER_HAVE_NO_WAFER).build());

        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sjf.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(), Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());


        if (tc != null && tc.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(tc.getObjectSubtype()), Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            sorterService.checkCarrierStatus(tc);
            Assert.state(!lotAutoMonitorInqService.checkMonitorCarrierUsed(tc.getInstanceRrn()), Errors.create().key(MessageIdList.AUTOMONITOR_CARRIER_INUSED).build());
            // 检查目标晶舟是否是空的
            Assert.isFalse(!carrierIsEmpty(sjf.getTargetCarrierRrn()), Errors.create().key(MessageIdList.TARGET_BE_USED).build());
        }

        Assert.isFalse(sourceUnitList.size() > 0, Errors.create().key(MessageIdList.EXCHANGE_NEED_ALL).build());

        checkSourceAndTargetInfo(sjf);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, type, sc, tc, targetUnitList);
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    /**
     * 检查需要加载的晶舟
     *
     * @param sourceCarrier sourceCarrier
     * @param targetCarrier targetCarrier
     * @param jobType       jobType
     * @param sourceUnits   sourceUnits
     * @param targetUnits   targetUnits
     * @param facilityRrn   facilityRrn
     */
    private Lot checkCarrierForLoad(Carrier sourceCarrier, Carrier targetCarrier, String jobType, List<Map> sourceUnits,
                                    List<Map> targetUnits, Long facilityRrn) {
        String sourceCarrierId = sourceCarrier.getInstanceId();
        String targetCarrierId = targetCarrier.getInstanceId();

        Assert.isFalse(StringUtils.equalsIgnoreCase(sourceCarrier.getInstanceId(), targetCarrier.getInstanceId()),
                       Errors.create().key(MessageIdList.CARRIER_CAN_NOT_SAME).build());

        // 原晶舟必须有晶圆
        Assert.isFalse(sourceUnits == null || sourceUnits.isEmpty(),
                       Errors.create().key(MessageIdList.CARRIER_NO_UNIT).build());

        // 检查批次状态
        Lot sourceLot = getLotByCarrierId(sourceCarrierId);
        Assert.state(Objects.nonNull(sourceLot),
                     Errors.create().key(MessageIdList.SOURCE_IS_EMPTY).build());
        if (!(SorterEnum.Constant.SRC_MERGE.equals(jobType) || SorterEnum.Check.isOffLineCheckT7Code(jobType)
                || SorterEnum.Check.isOfflineReadMaterialId(jobType))) {
            checkWaitJobs(sourceCarrier.getInstanceRrn(), targetCarrier.getInstanceRrn(), 0L, null);
            Assert.isFalse(!LotStatus.HOLD.equals(sourceLot.getLotStatus()) &&
                                   !LotStatus.isRunCardHold(sourceLot.getLotStatus()),
                           Errors.create().key(MessageIdList.LOT_MUST_HOLD).build());
        }

        // 若jobType为OFFLINE_CHECK_T7CODE 时单独check
        if (SorterEnum.Check.isOffLineCheckT7Code(jobType)) {
            checkWaitJobs(sourceCarrier.getInstanceRrn(), targetCarrier.getInstanceRrn(), 0L, null);
            Lot checkLot = sourceLot;
            if (LotStatus.isRunCardHold(checkLot.getLotStatus())) {
                checkLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(checkLot.getLotId()));
            }
            if (!LotStatus.isRunCardSpecialStatus(checkLot.getLotStatus())) {
                Assert.isFalse(!LotStatus.HOLD.equals(checkLot.getLotStatus()),
                               Errors.create().key(MessageIdList.LOT_MUST_HOLD).build());
            }
        }

        if (SorterEnum.Constant.MERGE.equals(jobType)) {
            // Lot targetLot = lotQueryService.getLotByCarrierId(targetCarrier.getInstanceId(), facilityRrn);
            Lot targetLot = getLotByCarrierId(targetCarrierId);
            Assert.state(CollectionUtils.isNotEmpty(targetUnits),
                           Errors.create().key(MessageIdList.TARGET_CARRIER_NO_WAFER).build());
            Assert.state(LotStatus.isHold(targetLot.getLotStatus()),
                           Errors.create().key(MessageIdList.LOT_MUST_HOLD).build());

            // Lot childLotId = lotQueryService.getLotByCarrierId(sourceCarrierId, facilityRrn);
            // Lot parentLotId = lotQueryService.getLotByCarrierId(targetCarrierId, facilityRrn);
            // 检查合批规则
            lotService.canMergeLotCheck(sourceLot.getLotId(), targetLot.getLotId());

        } else if (SorterEnum.Constant.SRC_MERGE.equals(jobType)) {
            //先检查是不是RC的批次
            Assert.isFalse(!lotRunCardQueryService.checkLotInRunCard(sourceLot.getLotRrn()),
                           Errors.create().key(MessageIdList.NOT_RC_LOT).content("{} is not a RunCard Lot!")
                                 .args(sourceLot.getLotId()).build());
            if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {
                sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
            }

            Lot targetLot = getLotByCarrierId(targetCarrierId);
            Assert.state(targetLot != null,
                         Errors.create().key(MessageIdList.TARGET_CARRIER_NO_WAFER).build());
            // Lot targetLot = lotQueryService.getLotByCarrierId(targetCarrier.getInstanceId(), facilityRrn);//Lot Id为母批的ID
            //先检查是不是RC的批次
            Assert.isFalse(!lotRunCardQueryService.checkLotInRunCard(targetLot.getLotRrn()),
                           Errors.create().key(MessageIdList.NOT_RC_LOT).content("{} is not a RunCard Lot!")
                                 .args(targetLot.getLotId()).build());
            if (LotStatus.isRunCardHold(targetLot.getLotStatus())) {
                targetLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(targetLot.getLotId()));
            }

            Assert.isFalse((targetUnits == null || targetUnits.isEmpty()),
                           Errors.create().key(MessageIdList.TARGET_CARRIER_NO_WAFER).build());
            Assert.isFalse(!LotStatus.RUNCARD_FINISH.equals(targetLot.getLotStatus()),
                           Errors.create().content("Lot status must be WAITMERGE!")
                                 .key(MessageIdList.LOT_MUST_WAIT_MERGE).build());

            // Lot childLotId = lotQueryService.getLotByCarrierId(sourceCarrierId, facilityRrn);
            // Lot parentLotId = lotQueryService.getLotByCarrierId(targetCarrierId, facilityRrn);
            String[] lotIdArr = buildChildAndParentLotId4RunCard(sourceLot.getLotRrn(), sourceLot.getLotId());
            String childLotId = lotIdArr[0];
            String parentLotId = lotIdArr[1];
            Assert.isFalse(!StringUtils.equalsIgnoreCase(sourceLot.getLotId(), childLotId),
                           Errors.create().key(MessageIdList.PARENT_IN_TARGET).content(
                                   "The carrier where the parent Lot is located should be placed at the " +
                                           "position of the Target Carrier!").build());
            Assert.isFalse(!LotStatus.RUNCARD_FINISH.equals(sourceLot.getLotStatus()),
                           Errors.create().content("Lot status must be WAITMERGE!")
                                 .key(MessageIdList.LOT_MUST_WAIT_MERGE).build());
            //SRC的Merge需要母批放在Target的位置上,所以不需要check Target的SortJob,因为可能会存在多个
            checkWaitJobs(sourceCarrier.getInstanceRrn(), 0L, 0L, null);

            Map<String, Object> childLotMap = lotRunCardQueryService.getRCLotInfoMap(childLotId);
            Map<String, Object> parentLotMap = lotRunCardQueryService.getRCLotInfoMap(parentLotId);

            //check flip type
            String childFlipType = MapUtils.getString(childLotMap, "flipType");
            String parentFlipType = MapUtils.getString(parentLotMap, "flipType");
            this.checkFlip4ChildAndParentLot(parentFlipType, childFlipType);
            this.checkQTime(targetLot.getLotRrn(), sourceLot.getLotRrn());
            sourceLot.setCreatedPlanLotRrn(MapUtils.getLong(parentLotMap, "lotRrn"));
        } else {
            Assert.isFalse((targetUnits != null && !targetUnits.isEmpty()),
                           Errors.create().key(MessageIdList.TARGET_CARRIER_NOT_EMPTY).build());
        }
        return sourceLot;
    }

    /**
     * 校验源晶舟和目标晶舟以及晶舟中的批次的相关卡控
     * 1 检查源晶舟与目标晶舟是否存在于同一个SortJob中。
     * 2 检查晶舟中的批次是否为HOLD状态。
     * 3 检查晶舟中的批次是否存在指定的HoldCode。
     * 4 分别检查 源晶舟、目标晶舟、目标晶舟2是否存在SortJob。
     *
     * @param sortJobForm
     */
    private Lot checkSourceAndTargetInfo(SortJobForm sortJobForm) {

        SortJobBean sortJob = sorterQueryService.getSortJobByCarrier(sortJobForm.getSourceCarrierRrn(),
                                                                     sortJobForm.getTargetCarrierRrn(),
                                                                     sortJobForm.getTargetCarrierRrn2());
        if (sortJobForm.isRcType()) {
            Integer splitSeq = (splitSeq = sortJobForm.getSplitSeq()) != null ? splitSeq : -1;
            if (sortJob.isEmpty() && CollectionUtils.isNotEmpty(sortJob.getSorterBeans())) {
                List<SorterBean> subJobs = sortJob.getSorterBeans();
                for (SorterBean sb : subJobs) {
                    Assert.isFalse(splitSeq == Integer.parseInt(sb.getJsonAttributeData2()),
                                   Errors.create().key(MessageIdList.SRC_CHILD_LOT_IN_SORT).build());
                }
            }
        } else {
            Assert.isFalse(sortJob.isEmpty(), Errors.create().key(MessageIdList.CARRIER_HAVE_JOB).build());
        }

        // 检查批次状态
        Lot sourceLot = getLotByCarrierId(sortJobForm.getSourceCarrierId());
        Assert.state(Objects.nonNull(sourceLot),
                     Errors.create().key(MessageIdList.SOURCE_IS_EMPTY).build());

        //1、先检查批次的状态是否为hold
        if (sortJobForm.isRcType()) {
            Assert.isFalse(!LotStatus.RUNCARD_HOLD.equals(sourceLot.getLotStatus()),
                           Errors.create().key(MessageIdList.LOT_MUST_HOLD).build());
        } else {
            if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {
                sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
            }
            if (!SorterEnum.Check.isSpecialStatusForRcSortJob(sortJobForm.getJobType(), sourceLot.getLotStatus())) {
                Assert.isFalse(!LotStatus.HOLD.equals(sourceLot.getLotStatus()) &&
                                       !LotStatus.isRunCardHold(sourceLot.getLotStatus()),
                               Errors.create().key(MessageIdList.LOT_MUST_HOLD).build());
            }
        }

        if (SorterEnum.Check.isFlip(sortJobForm.getJobType()) || SorterEnum.Check.isSrcExchange(sortJobForm.getJobType())) {
            if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {
                sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
            }
        }
        // 若 jobType 为 OFFLINE_CHECK_T7CODE、SRC_EXCHANGE、SRC_SPECIAL_EXCHANGE 且 lot状态为runcard内特殊状态
        // 则不需要校验hold code
        if (!SorterEnum.Check.isSpecialStatusForRcSortJob(sortJobForm.getJobType(), sourceLot.getLotStatus())) {
            //2、检查HoldCode
            List<Map> holdInfos = wipQueryService.getHoldReasons(sourceLot.getLotRrn());
            boolean holdCodeFlag = false;
            for (Map map : holdInfos) {
                String holdCold = MapUtils.getString(map, "reasonCode", StringUtils.EMPTY);
                if (sortJobForm.isSortHoldCode(holdCold)) {
                    holdCodeFlag = true;
                    break;
                }
            }
            Assert.isFalse(!holdCodeFlag, Errors.create().key(MessageIdList.HOLD_CODE_NOT_FOUND).build());
        }

        //3、是否存在未执行的Sort任务
        if (sortJobForm.isRcType()) {
            //SRC的类型不用检查源晶舟是否已经存在SortJob了
            checkWaitJobs(-1, sortJobForm.getTargetCarrierRrn(), sortJobForm.getTargetCarrierRrn2(), null);
        } else {
            checkWaitJobs(sortJobForm.getSourceCarrierRrn(), sortJobForm.getTargetCarrierRrn(),
                          sortJobForm.getTargetCarrierRrn2(), null);
        }

        return sourceLot;
    }

    private SortJobBean buildAutoMonitorSortInfo(Carrier sourceCarrier, Carrier targetCarrier,
                                                 SortJobForm sortJobForm) {

        if (targetCarrier != null && targetCarrier.isNotEmpty()) {
            sorterService.checkCarrierStatus(targetCarrier);
        }
        // 检查目标晶舟是否是空的
        Assert.state(carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                     Errors.create().key(MessageIdList.TARGET_BE_USED).build());

        checkWaitJobs(sortJobForm.getSourceCarrierRrn(), sortJobForm.getTargetCarrierRrn(),
                      sortJobForm.getTargetCarrierRrn2(), null);

        List<MonitorCarrierMapping> carrierMappings = lotAutoMonitorInqService.getMonitorCarrierMappings(
                targetCarrier.getInstanceRrn());
        List<SorterDetailBean> targetUnitList = new ArrayList<>();
        for (MonitorCarrierMapping carrierMapping : carrierMappings) {
            targetUnitList.add(new SorterDetailBean(carrierMapping.getLotRrn(), carrierMapping.getLotId(),
                                                    carrierMapping.getUnitRrn(), carrierMapping.getUnitId(),
                                                    carrierMapping.getSourcePosition(), carrierMapping.getPosition()));
        }

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.AUTO_MONITOR,
                                                sourceCarrier, targetCarrier, targetUnitList);
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    private Map<String, Object> buildUnitMapByAutoMonitor(long position, String unitId, Long unitRrn, String lotId) {
        Map<String, Object> unitMap = new HashMap<>();
        unitMap.put("position", position);
        unitMap.put("unitId", unitId);
        unitMap.put("unitRrn", unitRrn);
        unitMap.put("lotId", lotId);
        unitMap.put("chooseFlag", "");
        return unitMap;
    }

    private Map ajaxReturn(String message, Collection data) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("message", message);
        resultMap.put("data", data);
        return resultMap;
    }

    private SortJobBean buildMergeSortJob(Carrier sourceCarrier, Carrier targetCarrier, SortJobForm sortJobForm) {
        long facilityRrn = LocalContext.getFacilityRrn();

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sourceCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());
        if (targetCarrier != null) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            // sorterService.checkCarrierStatus(targetCarrier);
        }

        Assert.isFalse(StringUtils.isBlank(sortJobForm.getTargetCarrierId()),
                       Errors.create().key(MessageIdList.TARGET_ID_CAN_NOT_EMPTY).build());
        Assert.isFalse(sourceUnitList.size() > 0, Errors.create().key(MessageIdList.MERGE_NEED_ALL).build());
        // 检查批次是否可以合批(合批规则) 数字小的批次为母批
        Lot childLot = getLotByCarrierId(sortJobForm.getSourceCarrierId());
        Lot parentLot = getLotByCarrierId(sortJobForm.getTargetCarrierId());
        // 检查合批规则
        lotService.canMergeLotCheck(childLot.getLotId(), parentLot.getLotId());

        //新增加的一系列列其他业务的卡控
        //卡控post future hold被hold住的lot
        this.checkPostFutureHold(parentLot.getLotRrn());
        wipQueryService.checkActiveInlineOcapId(parentLot.getLotRrn());
        this.checkChildLotStatusForMerge(childLot);

        //child lot和lot必须都bond或都未bond才允许合批
        this.checkBond(parentLot, childLot);
        //check contamination level
        this.checkContaminationLevel(parentLot, childLot);
        //check Flip
        this.checkFlip4ChildAndParentLot(parentLot, childLot);

        checkSourceAndTargetInfo(sortJobForm);
        //根据源晶舟中的子批的晶圆,来组装sourcePosition和targetPosition
        List<Map> childLotUnitList = wipQueryService.getUnitListByCarrier(sourceCarrier.getInstanceRrn());
        List<SorterDetailBean> exchangeUnitList = buildMergeUnitList(parseSorterDetailBeanList(childLotUnitList),
                                                                     targetUnitList);

        //Runcard的merge卡控
        List<Map> masterLotRunCardCreatedTimeList = lotRunCardQueryService.getSumbitAndActiveRunCardCreateTime(
                parentLot.getLotRrn());
        List sourceListValues = wipQueryService.getUnitListByLot(parentLot.getLotRrn());
        Map item = new HashMap();
        item.put("units", childLotUnitList);
        item.put("childLotId", childLot.getLotId());
        this.sourceAndChidUnits(sourceListValues, item, masterLotRunCardCreatedTimeList);
        // 判断是否可以合批
        this.checkMergeAvailable(parentLot, childLot);
        this.checkLotStepForMerge(parentLot, childLot);

        // check product version
        checkProductVersionForMerge(parentLot, childLot);
        // check process version
        checkProcessVersionForMerge(parentLot, childLot);

        List childLots = new ArrayList();
        Map childLotMap = new HashMap();
        childLotMap.put("childLotId", childLot.getLotId());
        childLots.add(childLotMap);
        childLotsBeforeIntoServiceCheck(childLots);
        lotBeforeIntoServiceCheck(parentLot, "SO");

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.MERGE, sourceCarrier,
                                                targetCarrier, exchangeUnitList);
        sorterBean.setJsonAttributeData4(parentLot.getLotId());
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    private List<SorterDetailBean> buildMergeUnitList(List<SorterDetailBean> sourceUnitList,
                                                      List<SorterDetailBean> targetUnitList) {
        Map<String, Integer> positionMap = new HashMap<>();
        for (SorterDetailBean sdb : targetUnitList) {
            positionMap.put(sdb.getUnitId(), sdb.getTargetPosition());
        }
        for (SorterDetailBean sdb : sourceUnitList) {
            sdb.setTargetPosition(positionMap.getOrDefault(sdb.getUnitId(), sdb.getSourcePosition()));
        }
        return sourceUnitList;
    }

    private SortJobBean buildSplitSortJob(Carrier sourceCarrier, Carrier targetCarrier, SortJobForm sortJobForm) {

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sourceCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());
        if (targetCarrier != null && targetCarrier.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            sorterService.checkCarrierStatus(targetCarrier);
        }
        Assert.state(!lotAutoMonitorInqService.checkMonitorCarrierUsed(targetCarrier.getInstanceRrn()),
                     Errors.create().key(MessageIdList.AUTOMONITOR_CARRIER_INUSED).build());

        Assert.isFalse(sourceUnitList.isEmpty(), Errors.create().key(MessageIdList.PARENT_LOT_LEAST_ONE).build());
        // 检查目标晶舟是否是空的
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                       Errors.create().key(MessageIdList.TARGET_BE_USED).build());

        Lot sourceLot = getLotByCarrierId(sourceCarrier.getInstanceId());

        checkLotForSplit(sourceLot, "");

        checkSourceAndTargetInfo(sortJobForm);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.SPLIT, sourceCarrier,
                                                targetCarrier, targetUnitList);
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    private SortJobBean buildPilotSplitSortJob(Carrier sourceCarrier, Carrier targetCarrier, SortJobForm sortJobForm) {
        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sourceCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());
        if (targetCarrier != null && targetCarrier.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            sorterService.checkCarrierStatus(targetCarrier);
        }

        Assert.isFalse(sourceUnitList.isEmpty(), Errors.create().key(MessageIdList.PARENT_LOT_LEAST_ONE).build());
        // 检查目标晶舟是否是空的
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                       Errors.create().key(MessageIdList.TARGET_BE_USED).build());

        Lot sourceLot = getLotByCarrierId(sourceCarrier.getInstanceId());

        // 永久分批flag初始化提示
        Assert.isFalse((sourceLot == null) || (sourceLot.getLotRrn() <= 0),
                       Errors.create().key(MessageIdList.SCRAPLOT_LOTID_ERROR).content("Lot id not find!").build());
        // Bonding临时分批卡控
        Assert.isFalse(isSapphireLot(sourceLot) && LocationNames.TEMPORARY.equalsIgnoreCase(""),
                       Errors.create().key(MessageIdList.BOND_LOT_CAN_NOT_SPLIT).build());
        // check the lot status by product typess
        /*if (!lot.getLotStatus().equalsIgnoreCase(LotStatus.HOLD)) {
            throw new WebException("lot.spilt_not_hold", "This lot is not in hold");
        }*/
        Assert.isTrue(LotStatus.isWaitingOrHold(sourceLot.getLotStatus()),
                      Errors.create().key(MessageIdList.LOT_MUST_W_H).build());

        Assert.isFalse(diffBatchQueryService.checkLotInBatch(sourceLot.getLotRrn()),
                       Errors.create().key(MessageIdList.LOT_IN_BATCH).content("Lot in batch!").build());

        wipQueryService.checkActiveInlineOcapId(sourceLot.getLotRrn());

        checkSourceAndTargetInfo(sortJobForm);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.PILOT_SPLIT,
                                                sourceCarrier, targetCarrier, targetUnitList);
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    /**
     * 组装 交换晶舟 的SortJob
     *
     * @param sourceCarrier 源晶舟
     * @param targetCarrier 目标晶舟
     * @param sortJobForm   SortJobForm
     * @return SortJobBean
     */
    private SortJobBean buildExchangeSortJob(Carrier sourceCarrier, Carrier targetCarrier, SortJobForm sortJobForm) {

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        Assert.isFalse(!sourceUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.SOURCE_CARRIER_HAVE_NO_WAFER).build());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sourceCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());
        if (targetCarrier != null && targetCarrier.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            sorterService.checkCarrierStatus(targetCarrier);
        }
        Assert.state(!lotAutoMonitorInqService.checkMonitorCarrierUsed(targetCarrier.getInstanceRrn()),
                     Errors.create().key(MessageIdList.AUTOMONITOR_CARRIER_INUSED).build());

        Assert.isFalse(sourceUnitList.size() > 0, Errors.create().key(MessageIdList.EXCHANGE_NEED_ALL).build());
        // 检查目标晶舟是否是空的
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                       Errors.create().key(MessageIdList.TARGET_BE_USED).build());

        checkSourceAndTargetInfo(sortJobForm);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.EXCHANGE, sourceCarrier,
                                                targetCarrier, targetUnitList);
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    /**
     * 组装 多Port的交换晶舟 的SortJob
     *
     * @param sourceCarrier  源晶舟
     * @param targetCarrier  目标晶舟
     * @param targetCarrier2 第二个目标晶舟
     * @param sortJobForm    SortJobForm
     * @return SortJobBean
     */
    private SortJobBean buildExchange2PortSortJob(Carrier sourceCarrier, Carrier targetCarrier, Carrier targetCarrier2,
                                                  SortJobForm sortJobForm) {
        //targetCarrier的rrn=0 并且 targetCarrier2==null 表示多Port且都不指定。targetCarrier的rrn>0 并且 targetCarrier2!=null
        // 表示多Port且都指定。
        Assert.isFalse((targetCarrier.getInstanceRrn() <= 0 && targetCarrier2 != null) ||
                               (targetCarrier.getInstanceRrn() > 0 && targetCarrier2 == null),
                       Errors.create().key(MessageIdList.MORE_PORT_NEED_SAME).build());

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        Assert.isFalse(!sourceUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.SOURCE_CARRIER_HAVE_NO_WAFER).build());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());
        // List<SorterDetailBean> targetUnitList2 = parseSorterDetailBeanList(sortJobForm.getTargetListValues2());
        // Assert.isFalse(targetUnitList2.isEmpty(), Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER)
        // .build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sourceCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());
        if (targetCarrier != null && targetCarrier.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            // sorterService.checkCarrierStatus(targetCarrier);
        }

        if (targetCarrier2 != null && targetCarrier2.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier2.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            sorterService.checkCarrierStatus(targetCarrier2);
        }

        Assert.isFalse(sourceUnitList.size() > 0, Errors.create().key(MessageIdList.EXCHANGE_NEED_ALL).build());
        // 检查目标晶舟是否是空的
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                       Errors.create().key(MessageIdList.TARGET_BE_USED).build());
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn2()),
                       Errors.create().key(MessageIdList.TARGET_BE_USED).build());

        //TODO check
        if (targetCarrier2 == null) {
            sortJobForm.setTargetCarrierRrn2(0L);
        }
        checkSourceAndTargetInfo(sortJobForm);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.NORMAL_EXCHANGE,
                                                sourceCarrier, targetCarrier, targetUnitList);
        // SorterBean sorterBean2 = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.NORMAL_EXCHANGE,
        // sourceCarrier, targetCarrier2, targetUnitList2);
        SortJobBean sortJobBean = new SortJobBean();
        // sortJobBean.setSorterBeans(sorterBean, sorterBean2);
        return sortJobBean;
    }

    private SortJobBean buildRunCardSplitSortJob(Carrier sourceCarrier, Carrier targetCarrier,
                                                 SortJobForm sortJobForm) {
        long facilityRrn = LocalContext.getFacilityRrn();

        String realCarrierRrnOfSRC = null;
        String splitSeq;
        Lot sourceLot = getLotByCarrierId(sourceCarrier.getInstanceId());
        if (StringUtils.equalsIgnoreCase(sourceLot.getLotStatus(), LotStatus.RUNCARD_HOLD)) {
            sourceLot = lotQueryService.getLot(sourceLot.getLotId() + RunCardConstants.MAINLOT_ID_IN_MES, facilityRrn);
            realCarrierRrnOfSRC =
                    sourceLot.getCarrierRrn() != null ? sourceLot.getCarrierRrn().toString() : StringUtils.EMPTY;
        }
        LotRunCardStore sourceRunCardStore = lotRunCardQueryService.getSplitRunCardLotStore(sourceLot.getLotRrn());
        Assert.isFalse(!(StringUtils.equalsIgnoreCase(RunCardStoreSubStatus.BANKIN.toString(),
                                                      sourceRunCardStore.getSubStatus()) &&
                               StringUtils.equalsIgnoreCase(LotStatus.RUNCARD_WAITSPLIT, sourceLot.getLotStatus())),
                       Errors.create().key(MessageIdList.SOURCE_LOT_NOT_WAIT_SPIT).build());
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                       Errors.create().key(MessageIdList.TARGET_IS_IN_USE).build());

        splitSeq = sourceRunCardStore.getSplitSeq() != null ? sourceRunCardStore.getSplitSeq()
                                                                                .toString() : sortJobForm.getSplitSeq()
                                                                                                         .toString();

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        Assert.isFalse(sourceUnitList.isEmpty(), Errors.create().key(MessageIdList.PARENT_LOT_LEAST_ONE).build());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());

        if (targetCarrier != null && targetCarrier.isNotEmpty()) {
            Assert.isFalse(SorterEnum.Constant.DUMMY.equals(targetCarrier.getObjectSubtype()),
                           Errors.create().key(MessageIdList.TARGET_CAN_NOT_DUMMY).build());
            sorterService.checkCarrierStatus(targetCarrier);
            Assert.state(!lotAutoMonitorInqService.checkMonitorCarrierUsed(targetCarrier.getInstanceRrn()),
                         Errors.create().key(MessageIdList.AUTOMONITOR_CARRIER_INUSED).build());
        }

        Assert.isFalse(sourceUnitList.isEmpty(), Errors.create().key(MessageIdList.PARENT_LOT_LEAST_ONE).build());
        // 检查目标晶舟是否是空的
        Assert.isFalse(!carrierIsEmpty(sortJobForm.getTargetCarrierRrn()),
                       Errors.create().key(MessageIdList.TARGET_BE_USED).build());

        checkSourceAndTargetInfo(sortJobForm);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.SRC_SPLIT,
                                                sourceCarrier, targetCarrier, targetUnitList);
        sorterBean.setJsonAttributeData1(realCarrierRrnOfSRC);  //realCarrierRrnOfSRC   放在备用字段一
        sorterBean.setJsonAttributeData2(splitSeq);             //splitSeq              放在备用字段二
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    private SortJobBean buildRunCardMergeSortJob(Carrier sourceCarrier, Carrier targetCarrier,
                                                 SortJobForm sortJobForm) {
        long facilityRrn = LocalContext.getFacilityRrn();
        Lot sourceLot = getLotByCarrierId(sourceCarrier.getInstanceId());
        //先检查是不是RC的批次
        Assert.isFalse(!lotRunCardQueryService.checkLotInRunCard(sourceLot.getLotRrn()),
                       Errors.create().key(MessageIdList.NOT_RC_LOT).content("{} is not a RunCard Lot!")
                             .args(sourceLot.getLotId()).build());
        if (LotStatus.isRunCardHold(sourceLot.getLotStatus())) {
            sourceLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(sourceLot.getLotId()));
        }

        Lot targetLot = getLotByCarrierId(targetCarrier.getInstanceId());
        //先检查是不是RC的批次
        Assert.isFalse(!lotRunCardQueryService.checkLotInRunCard(targetLot.getLotRrn()),
                       Errors.create().key(MessageIdList.NOT_RC_LOT).content("{} is not a RunCard Lot!")
                             .args(targetLot.getLotId()).build());
        if (LotStatus.isRunCardHold(targetLot.getLotStatus())) {
            targetLot = lotQueryService.getLot(RunCardUtils.buildMainRcLotId(targetLot.getLotId()));
        }

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());

        Assert.isFalse(!LotStatus.RUNCARD_FINISH.equals(targetLot.getLotStatus()),
                       Errors.create().content("Lot status must be WAITMERGE!").key(MessageIdList.LOT_MUST_WAIT_MERGE)
                             .build());
        Assert.isFalse(targetUnitList.isEmpty(), Errors.create().key(MessageIdList.TARGET_CARRIER_NO_WAFER).build());
        Assert.isFalse(sourceUnitList.size() > 0, Errors.create().key(MessageIdList.SELECT_SOURCE_ALL_WAFER)
                                                        .content("Please select all source wafer!").key("").build());

        String[] lotIdArr = buildChildAndParentLotId4RunCard(sourceLot.getLotRrn(), sourceLot.getLotId());
        String childLotId = lotIdArr[0];
        String parentLotId = lotIdArr[1];
        Assert.isFalse(!StringUtils.equalsIgnoreCase(sourceLot.getLotId(), childLotId),
                       Errors.create().key(MessageIdList.PARENT_IN_TARGET).content(
                               "The carrier where the parent Lot is located should be placed at the position of" +
                                       " the Target Carrier!").build());
        Assert.isFalse(!LotStatus.RUNCARD_FINISH.equals(sourceLot.getLotStatus()),
                       Errors.create().content("Lot status must be WAITMERGE!").key(MessageIdList.LOT_MUST_WAIT_MERGE)
                             .build());
        //SRC的Merge需要母批放在Target的位置上,所以不需要check Target的SortJob,因为可能会存在多个
        checkWaitJobs(sourceCarrier.getInstanceRrn(), 0L, 0L, null);

        Map<String, Object> childLotMap = lotRunCardQueryService.getRCLotInfoMap(childLotId);
        Map<String, Object> parentLotMap = lotRunCardQueryService.getRCLotInfoMap(parentLotId);

        //check flip type
        String childFlipType = MapUtils.getString(childLotMap, "flipType");
        String parentFlipType = MapUtils.getString(parentLotMap, "flipType");
        this.checkFlip4ChildAndParentLot(parentFlipType, childFlipType);
        this.checkQTime(targetLot.getLotRrn(), sourceLot.getLotRrn());

        //根据源晶舟中的子批的晶圆,来组装sourcePosition和targetPosition
        List<Map> childLotUnitList = wipQueryService.getUnitListByCarrier(sourceCarrier.getInstanceRrn());
        List<SorterDetailBean> exchangeUnitList = buildMergeUnitList(parseSorterDetailBeanList(childLotUnitList),
                                                                     targetUnitList);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.SRC_MERGE,
                                                sourceCarrier, targetCarrier, exchangeUnitList);
        sorterBean.setJsonAttributeData1(MapUtils.getString(parentLotMap, "lotId"));
        sorterBean.setJsonAttributeData2(MapUtils.getString(parentLotMap, "lotRrn"));
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    private SortJobBean buildSplitMorePortSortJob(Carrier sourceCarrier, Carrier targetCarrier, Carrier targetCarrier2,
                                                  SortJobForm sortJobForm) {
        long facilityRrn = LocalContext.getFacilityRrn();
        //targetCarrier的rrn=0 并且 targetCarrier2==null 表示多Port且都不指定。targetCarrier的rrn>0 并且 targetCarrier2!=null
        // 表示多Port且都指定。
        Assert.isFalse((targetCarrier.getInstanceRrn() <= 0 && targetCarrier2 != null) ||
                               (targetCarrier.getInstanceRrn() > 0 && targetCarrier2 == null),
                       Errors.create().key(MessageIdList.MORE_PORT_NEED_SAME).build());

        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sortJobForm.getSourceListValues());
        Assert.isFalse(sourceUnitList.isEmpty(), Errors.create().key(MessageIdList.PARENT_LOT_LEAST_ONE).build());
        List<SorterDetailBean> targetUnitList = parseSorterDetailBeanList(sortJobForm.getTargetListValues());
        Assert.isFalse(targetUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.TARGET_CARRIER_HAVE_NO_WAFER).build());
        //TargetCarrier2的晶圆来自于源晶舟中剩余的晶圆
        List<SorterDetailBean> targetUnitList2 = recombinateTargetUnitList2(sourceUnitList);

        Assert.isFalse(targetUnitList.size() != 12,
                       Errors.create().key(MessageIdList.TARGET_CARRIER_NEED_1_TO_12).build());

        if (StringUtils.equalsIgnoreCase(sortJobForm.getJobType(), SorterEnum.Constant.SPLIT_3PORT)) {
            if (targetCarrier.isNotEmpty() && targetCarrier2 != null && targetCarrier2.isNotEmpty()) {
                sorterQueryService.checkPort2CstForLoad(sourceCarrier, targetCarrier, targetCarrier2);
            }
        }

        if (targetCarrier2 == null) {
            sortJobForm.setTargetCarrierRrn2(0L);
        }

        checkSourceAndTargetInfo(sortJobForm);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.SPLIT_3PORT,
                                                sourceCarrier, targetCarrier, targetUnitList);
        SorterBean sorterBean2 = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.SPLIT_3PORT,
                                                 sourceCarrier, targetCarrier2, targetUnitList2);
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean, sorterBean2);
        return sortJobBean;
    }

    /**
     * 组装翻面的SorterJob
     *
     * @param sCarrier
     * @param targetCarrier 预留翻面并交换
     * @param sjf
     * @return
     */
    private SortJobBean buildFlipSideSortInfo(Carrier sCarrier, Carrier targetCarrier, SortJobForm sjf) {
        //TODO
        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sjf.getSourceListValues());
        Assert.isFalse(sourceUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.SOURCE_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());

        Lot sourceLot = checkSourceAndTargetInfo(sjf);
        String flipType = sourceLot.getFlipType();

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.FLIP_SIDE, sCarrier,
                                                sCarrier, sourceUnitList);
        sorterBean.setJsonAttributeData3(flipType + "|" + SorterEnum.Constant.getOpposite(flipType));
        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    /**
     * 组装check t7code 的SorterJob
     *
     * @param sCarrier
     * @param sjf
     * @return
     */
    private SortJobBean buildCheckT7CodeSortInfo(Carrier sCarrier, SortJobForm sjf) {
        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sjf.getSourceListValues());

        Assert.isFalse(sourceUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.SOURCE_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());

        checkSourceAndTargetInfo(sjf);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.OFFLINE_CHECK_T7CODE, sCarrier,
                                                sCarrier, sourceUnitList);

        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    /**
     * 组装read Material Id 的SorterJob
     *
     * @param sCarrier
     * @param sjf
     * @return
     */
    private SortJobBean buildReadMaterialIdSortInfo(Carrier sCarrier, SortJobForm sjf) {
        List<SorterDetailBean> sourceUnitList = parseSorterDetailBeanList(sjf.getSourceListValues());

        Assert.isFalse(sourceUnitList.isEmpty(),
                       Errors.create().key(MessageIdList.SOURCE_CARRIER_HAVE_NO_WAFER).build());

        Assert.isFalse(SorterEnum.Constant.DUMMY.equals(sCarrier.getObjectSubtype()),
                       Errors.create().key(MessageIdList.SOURCE_CAN_NOT_DUMMY).build());

        checkSourceAndTargetInfo(sjf);

        SorterBean sorterBean = buildSorterBean(SorterEnum.Constant.CREATE, SorterEnum.Constant.OFFLINE_READ_MATERIALID, sCarrier,
                                                sCarrier, sourceUnitList);

        SortJobBean sortJobBean = new SortJobBean();
        sortJobBean.setSorterBeans(sorterBean);
        return sortJobBean;
    }

    private List<SorterDetailBean> recombinateTargetUnitList2(List<SorterDetailBean> sourceUnitList) {
        if (CollectionUtils.isEmpty(sourceUnitList)) {
            return new ArrayList<>();
        }
        List<SorterDetailBean> sortJobDetailList = new ArrayList<>();
        SorterDetailBean[] arr = new SorterDetailBean[26];
        for (SorterDetailBean sdb : sourceUnitList) {
            arr[sdb.getSourcePosition()] = sdb;
        }
        int position = 1;
        for (SorterDetailBean sdb : arr) {
            if (sdb != null) {
                sortJobDetailList.add(
                        new SorterDetailBean(sdb.getLotRrn(), sdb.getLotId(), sdb.getUnitRrn(), sdb.getUnitId(),
                                             sdb.getSourcePosition(), position++));
            }
        }
        return sortJobDetailList;
    }

    private SorterBean buildSorterBean(String jobStatus, String jobType, Carrier sourceCarrier, Carrier targetCarrier,
                                       List<SorterDetailBean> unitList) {
        SorterBean sorterBean = new SorterBean();
        sorterBean.setSourceCarrierId(sourceCarrier.getInstanceId());
        sorterBean.setSourceCarrierRrn(sourceCarrier.getInstanceRrn());
        sorterBean.setSourceCarrierMapRrn(sourceCarrier.getCarrierMapRrn());
        sorterBean.setTargetCarrierId(targetCarrier == null ? StringUtils.EMPTY : targetCarrier.getInstanceId());
        sorterBean.setTargetCarrierRrn(targetCarrier == null ? NumberUtils.LONG_ZERO : targetCarrier.getInstanceRrn());
        sorterBean.setTargetCarrierMapRrn(targetCarrier==null?NumberUtils.LONG_ZERO:targetCarrier.getCarrierMapRrn());
        sorterBean.setStatus(jobStatus);
        sorterBean.setJobType(jobType);
        sorterBean.setCreateUser(LocalContext.getUserId());
        sorterBean.setExchangeTotalQty(unitList.size());
        sorterBean.setSorterDetailBeanList(unitList);
        sorterBean.setJsonAttributeData5(StringUtils.EMPTY + sourceCarrier.getCarrierMapRrn());
        return sorterBean;
    }

    private List<UnitBean> parseUnitListAbsenceEmptySlot(String jsonStr) {
        List<UnitBean> unitList = new ArrayList<>();
        List list = JsonUtils.toObject(jsonStr, List.class);
        for (Map<String, Object> unitMap : (List<Map<String, Object>>) list) {
            String unitId = MapUtils.getString(unitMap, "unitId");
            long unitRrn = MapUtils.getLong(unitMap, "unitRrn", 0L);
            String lotId = MapUtils.getString(unitMap, "lotId");
            long lotRrn = MapUtils.getLong(unitMap, "lotRrn", 0L);
            int position = MapUtils.getIntValue(unitMap, "position", 0);
            if (StringUtils.isNotBlank(unitId) && unitRrn > 0 && StringUtils.isNotBlank(lotId) &&
                    (position > 0 && position < 26)) {
                unitList.add(new UnitBean(lotRrn, lotId, unitRrn, unitId, position, position));
            }
        }
        return unitList;
    }

    private String buildCarrierList(String carrierList, long lotRrn) {
        List<Map> list = JsonUtils.toObject(carrierList, List.class);
        List<Map> resultList = new ArrayList<>();
        for (Map map : list) {
            Map resultMap = new HashMap();
            String lotId = MapUtils.getString(map, "lotid");
            resultMap.put("unitId", MapUtils.getString(map, "unitId"));
            resultMap.put("unitRrn", MapUtils.getString(map, "unitRrn"));
            resultMap.put("position", MapUtils.getString(map, "position"));
            if (StringUtils.isNotEmpty(lotId)) {
                resultMap.put("lotId", lotId);
                resultMap.put("lotRrn", lotRrn);
                resultMap.put("chooseFlag", "true");
            } else {
                resultMap.put("lotRrn", "");
                resultMap.put("chooseFlag", "");
                resultMap.put("lotId", "");
            }
            resultList.add(resultMap);
        }
        return JsonUtils.toString(resultList);
    }

}