自动化立体仓库 - WMS系统
#
zwl
昨天 da5d4106e294a229e3bf72939c6b7630e6345d76
src/main/java/com/zy/common/service/CommonService.java
@@ -25,7 +25,9 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
@@ -35,6 +37,7 @@
@Slf4j
@Service
public class CommonService {
    private static final int MIN_SPARE_LOC_COUNT = 2;
    @Autowired
    private WrkMastService wrkMastService;
@@ -126,10 +129,18 @@
     */
    @Transactional
    public StartupDto getLocNo(Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto) {
        return getLocNo(staDescId, sourceStaNo, findLocNoAttributeVo, locTypeDto, null);
    }
    @Transactional
    public StartupDto getLocNo(Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto, List<Integer> recommendRows) {
        try {
            Integer whsType = Utils.GetWhsType(sourceStaNo);
            RowLastno rowLastno = rowLastnoService.selectById(whsType);
            RowLastnoType rowLastnoType = rowLastnoTypeService.selectById(rowLastno.getTypeId());
            if (rowLastnoType.getType() == 2) {
                return getLocNoRun2(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, 0, locTypeDto, 0);
            }
            /**
             * 库型 1: 标准堆垛机库  2: 平库  3: 穿梭板  4: 四向车  5: AGV  0: 未知
             */
@@ -145,7 +156,7 @@
                case 4:
                    return getLocNoRun4(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, 4, locTypeDto, 0);
                case 5:
                    return getLocNoRun5(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, 0, locTypeDto, 0);
                    return getLocNoRun5(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, 0, locTypeDto, recommendRows, 0);
                default:
                    log.error("站点={} 未查询到对应的规则", sourceStaNo);
                    break;
@@ -177,7 +188,7 @@
            int availableLocCount = locMastService.selectCount(new EntityWrapper<LocMast>()
                    .eq("row1", nearRow)
                    .eq("loc_sts", "O")
                    .eq("whs_type", rowLastnoType.getType().longValue()));
                    .eq("whs_type", 1));
            int crnCountO = wrkMastService.selectCount(new EntityWrapper<WrkMast>()
                    .eq("crn_no", crnNo).le("io_type", 100));
            if (availableLocCount - crnCountO <= 2) { // 可以提成常量,比如 MIN_SPARE_SLOTS = 2
@@ -188,6 +199,298 @@
            return Optional.of(new CrnRowInfo(crnNo, nearRow, curRow, rowCount, attempt));
        }
        return Optional.empty();
    }
    private Optional<CrnRowInfo> findBalancedCrnAndNearRow(RowLastno rowLastno, int curRow, int crnNumber, int times,
                                                           FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto,
                                                           RowLastnoType rowLastnoType) {
        int maxScanTimes = Math.max(crnNumber * 2, 1);
        int scanCurRow = curRow;
        Map<Integer, Integer> availableLocCountCache = new HashMap<>();
        Map<Integer, Integer> crnTaskCountCache = new HashMap<>();
        CrnRowInfo bestInfo = null;
        int bestTaskCount = Integer.MAX_VALUE;
        int bestSpareLocCount = Integer.MIN_VALUE;
        int bestOffset = Integer.MAX_VALUE;
        CrnRowInfo fallbackInfo = null;
        int fallbackTaskCount = Integer.MAX_VALUE;
        int fallbackAvailableLocCount = Integer.MIN_VALUE;
        int fallbackOffset = Integer.MAX_VALUE;
        for (int attempt = 0; attempt < maxScanTimes; attempt++) {
            int[] params = Utils.LocNecessaryParameters(rowLastno, scanCurRow, crnNumber);
            scanCurRow = params[1];
            int rowCount = params[0];
            int crnNo = params[2];
            int nearRow = params[3];
            if (attempt < times) {
                continue;
            }
            int availableLocCount = availableLocCountCache.computeIfAbsent(crnNo, key ->
                    countAvailableLocForCrn(rowLastno, rowLastnoType, key, nearRow));
            int crnTaskCount = crnTaskCountCache.computeIfAbsent(crnNo, key ->
                    wrkMastService.selectCount(new EntityWrapper<WrkMast>()
                            .eq("crn_no", key)
                            .le("io_type", 100)));
            int spareLocCount = availableLocCount - crnTaskCount;
            int offset = attempt - times;
            if (availableLocCount > 0 && isBetterCrnCandidate(crnTaskCount, availableLocCount, offset,
                    fallbackTaskCount, fallbackAvailableLocCount, fallbackOffset)) {
                fallbackInfo = new CrnRowInfo(crnNo, nearRow, scanCurRow, rowCount, attempt);
                fallbackTaskCount = crnTaskCount;
                fallbackAvailableLocCount = availableLocCount;
                fallbackOffset = offset;
            }
            if (spareLocCount <= MIN_SPARE_LOC_COUNT) {
                log.warn("{}号堆垛机可用空库位余量不足,降级候选继续保留。尺寸规格:{},轮询次数:{}", crnNo, JSON.toJSONString(locTypeDto), attempt);
                continue;
            }
            if (isBetterCrnCandidate(crnTaskCount, spareLocCount, offset, bestTaskCount, bestSpareLocCount, bestOffset)) {
                bestInfo = new CrnRowInfo(crnNo, nearRow, scanCurRow, rowCount, attempt);
                bestTaskCount = crnTaskCount;
                bestSpareLocCount = spareLocCount;
                bestOffset = offset;
            }
        }
        if (bestInfo != null) {
            return Optional.of(bestInfo);
        }
        if (fallbackInfo != null) {
            log.warn("堆垛机均衡分配未找到满足余量阈值的候选,降级使用仍有空位的堆垛机: crnNo={}", fallbackInfo.getCrnNo());
        }
        return Optional.ofNullable(fallbackInfo);
    }
    private boolean isBetterCrnCandidate(int taskCount, int spareLocCount, int offset,
                                         int bestTaskCount, int bestSpareLocCount, int bestOffset) {
        if (taskCount != bestTaskCount) {
            return taskCount < bestTaskCount;
        }
        if (spareLocCount != bestSpareLocCount) {
            return spareLocCount > bestSpareLocCount;
        }
        return offset < bestOffset;
    }
    private int countAvailableLocForCrn(RowLastno rowLastno, RowLastnoType rowLastnoType, int crnNo, int preferredNearRow) {
        List<Integer> searchRows = getCrnSearchRows(rowLastno, crnNo, preferredNearRow);
        if (searchRows.isEmpty()) {
            return 0;
        }
        return locMastService.selectCount(new EntityWrapper<LocMast>()
                .in("row1", searchRows)
                .eq("loc_sts", "O")
                .eq("whs_type", rowLastnoType.getType().longValue()));
    }
    private int countAvailableSingleExtensionLocForCrn(RowLastno rowLastno, RowLastnoType rowLastnoType, int crnNo, int preferredNearRow, LocTypeDto locTypeDto) {
        List<Integer> searchRows = getCrnSearchRows(rowLastno, crnNo, preferredNearRow);
        if (searchRows.isEmpty()) {
            return 0;
        }
        Wrapper<LocMast> wrapper = new EntityWrapper<LocMast>()
                .in("row1", searchRows)
                .eq("loc_sts", "O")
                .eq("whs_type", rowLastnoType.getType().longValue());
        if (locTypeDto != null && locTypeDto.getLocType1() != null) {
            wrapper.eq("loc_type1", locTypeDto.getLocType1());
        }
        return locMastService.selectCount(wrapper);
    }
    private Optional<CrnRowInfo> findBalancedSingleExtensionCrnAndNearRow(RowLastno rowLastno, int curRow, int crnNumber, int times,
                                                                           LocTypeDto locTypeDto, RowLastnoType rowLastnoType) {
        int maxScanTimes = Math.max(crnNumber * 2, 1);
        int scanCurRow = curRow;
        Map<Integer, Integer> availableLocCountCache = new HashMap<>();
        Map<Integer, Integer> crnTaskCountCache = new HashMap<>();
        CrnRowInfo bestInfo = null;
        int bestTaskCount = Integer.MAX_VALUE;
        int bestSpareLocCount = Integer.MIN_VALUE;
        int bestOffset = Integer.MAX_VALUE;
        CrnRowInfo fallbackInfo = null;
        int fallbackTaskCount = Integer.MAX_VALUE;
        int fallbackAvailableLocCount = Integer.MIN_VALUE;
        int fallbackOffset = Integer.MAX_VALUE;
        for (int attempt = 0; attempt < maxScanTimes; attempt++) {
            int[] params = Utils.LocNecessaryParameters(rowLastno, scanCurRow, crnNumber);
            scanCurRow = params[1];
            int rowCount = params[0];
            int crnNo = params[2];
            int nearRow = params[3];
            if (attempt < times) {
                continue;
            }
            int availableLocCount = availableLocCountCache.computeIfAbsent(crnNo, key ->
                    countAvailableSingleExtensionLocForCrn(rowLastno, rowLastnoType, key, nearRow, locTypeDto));
            int crnTaskCount = crnTaskCountCache.computeIfAbsent(crnNo, key ->
                    wrkMastService.selectCount(new EntityWrapper<WrkMast>()
                            .eq("crn_no", key)
                            .le("io_type", 100)));
            int spareLocCount = availableLocCount - crnTaskCount;
            int offset = attempt - times;
            if (availableLocCount > 0 && isBetterCrnCandidate(crnTaskCount, availableLocCount, offset,
                    fallbackTaskCount, fallbackAvailableLocCount, fallbackOffset)) {
                fallbackInfo = new CrnRowInfo(crnNo, nearRow, scanCurRow, rowCount, attempt);
                fallbackTaskCount = crnTaskCount;
                fallbackAvailableLocCount = availableLocCount;
                fallbackOffset = offset;
            }
            if (spareLocCount <= MIN_SPARE_LOC_COUNT) {
                continue;
            }
            if (isBetterCrnCandidate(crnTaskCount, spareLocCount, offset, bestTaskCount, bestSpareLocCount, bestOffset)) {
                bestInfo = new CrnRowInfo(crnNo, nearRow, scanCurRow, rowCount, attempt);
                bestTaskCount = crnTaskCount;
                bestSpareLocCount = spareLocCount;
                bestOffset = offset;
            }
        }
        if (bestInfo != null) {
            return Optional.of(bestInfo);
        }
        return Optional.ofNullable(fallbackInfo);
    }
    private List<Integer> getCrnSearchRows(RowLastno rowLastno, int crnNo, int preferredNearRow) {
        List<Integer> searchRows = new ArrayList<>();
        addSearchRow(searchRows, preferredNearRow, rowLastno);
        Integer rowSpan = getCrnRowSpan(rowLastno.getTypeId());
        if (rowSpan == null) {
            return searchRows;
        }
        int crnOffset = crnNo - rowLastno.getsCrnNo();
        if (crnOffset < 0) {
            return searchRows;
        }
        int startRow = rowLastno.getsRow() + crnOffset * rowSpan;
        switch (rowLastno.getTypeId()) {
            case 1:
                addSearchRow(searchRows, startRow + 1, rowLastno);
                addSearchRow(searchRows, startRow + 2, rowLastno);
                break;
            case 2:
                addSearchRow(searchRows, startRow, rowLastno);
                addSearchRow(searchRows, startRow + 1, rowLastno);
                break;
            default:
                break;
        }
        return searchRows;
    }
    private Integer getCrnRowSpan(Integer typeId) {
        if (typeId == null) {
            return null;
        }
        switch (typeId) {
            case 1:
                return 4;
            case 2:
                return 2;
            default:
                return null;
        }
    }
    private void addSearchRow(List<Integer> searchRows, Integer row, RowLastno rowLastno) {
        if (row == null) {
            return;
        }
        if (row < rowLastno.getsRow() || row > rowLastno.geteRow()) {
            return;
        }
        if (!searchRows.contains(row)) {
            searchRows.add(row);
        }
    }
    private LocMast findStandardEmptyLoc(RowLastno rowLastno, RowLastnoType rowLastnoType, int crnNo, int nearRow, LocTypeDto locTypeDto) {
        for (Integer searchRow : getCrnSearchRows(rowLastno, crnNo, nearRow)) {
            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                    .eq("row1", searchRow)
                    .eq("loc_sts", "O")
                    .eq("whs_type", rowLastnoType.getType().longValue())
                    .orderBy("lev1", true)
                    .orderBy("bay1", true));
            for (LocMast locMast1 : locMasts) {
                if (!VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
                    continue;
                }
                if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                    String shallowLoc = Utils.getDeepLoc(slaveProperties, locMast1.getLocNo());
                    LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                            .eq("loc_no", shallowLoc)
                            .eq("loc_sts", "O")
                            .eq("whs_type", rowLastnoType.getType().longValue()));
                    if (!Cools.isEmpty(locMast2)) {
                        return locMast2;
                    }
                } else if (!Cools.isEmpty(locMast1)) {
                    return locMast1;
                }
            }
            if (!Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                continue;
            }
            for (LocMast locMast1 : locMasts) {
                if (!VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
                    continue;
                }
                String shallowLoc = Utils.getDeepLoc(slaveProperties, locMast1.getLocNo());
                LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                        .eq("loc_no", shallowLoc)
                        .eq("loc_sts", "O")
                        .eq("whs_type", rowLastnoType.getType().longValue()));
                if (!Cools.isEmpty(locMast2)) {
                    return locMast2;
                }
                locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                        .eq("loc_no", shallowLoc)
                        .eq("loc_sts", "F")
                        .eq("whs_type", rowLastnoType.getType().longValue()));
                if (!Cools.isEmpty(locMast2)) {
                    return locMast1;
                }
                locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                        .eq("loc_no", shallowLoc)
                        .eq("loc_sts", "D")
                        .eq("whs_type", rowLastnoType.getType().longValue()));
                if (!Cools.isEmpty(locMast2)) {
                    return locMast1;
                }
            }
        }
        return null;
    }
    private LocTypeDto buildUpwardCompatibleLocTypeDto(LocTypeDto locTypeDto) {
        if (locTypeDto == null || locTypeDto.getLocType1() == null || locTypeDto.getLocType1() >= 2) {
            return null;
        }
        LocTypeDto compatibleLocTypeDto = new LocTypeDto();
        compatibleLocTypeDto.setLocType1((short) (locTypeDto.getLocType1() + 1));
        compatibleLocTypeDto.setLocType2(locTypeDto.getLocType2());
        compatibleLocTypeDto.setLocType3(locTypeDto.getLocType3());
        compatibleLocTypeDto.setSiteId(locTypeDto.getSiteId());
        return compatibleLocTypeDto;
    }
@@ -241,7 +544,10 @@
        }
        //此程序用于优化堆垛机异常时的运行时间
        Optional<CrnRowInfo> infoOpt = findAvailableCrnAndNearRow(rowLastno, curRow, crnNumber, times, findLocNoAttributeVo, locTypeDto, rowLastnoType);
        Optional<CrnRowInfo> infoOpt = findBalancedCrnAndNearRow(rowLastno, curRow, crnNumber, times, findLocNoAttributeVo, locTypeDto, rowLastnoType);
        if (!infoOpt.isPresent()) {
            infoOpt = findAvailableCrnAndNearRow(rowLastno, curRow, crnNumber, times, findLocNoAttributeVo, locTypeDto, rowLastnoType);
        }
        if (!infoOpt.isPresent()) {
            throw new CoolException("无可用堆垛机");
        }
@@ -370,153 +676,35 @@
            }
        }
        // 开始查找库位 ==============================>>
        // Search empty location ==============================>>
        if (staDescId == 10 && Cools.isEmpty(locMast) && crnNo != 0) {
            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                    .eq("row1", nearRow)
                    .eq("loc_sts", "O").eq("whs_type", rowLastnoType.getType().longValue())
                    .orderBy("lev1", true).orderBy("bay1", false));
            for (LocMast locMast1 : locMasts) {
                if (!VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
                    continue;
                }
                if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                    String shallowLoc = Utils.getDeepLoc(slaveProperties, locMast1.getLocNo());
                    LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                            .eq("loc_no", shallowLoc).eq("loc_sts", "O").eq("whs_type", rowLastnoType.getType().longValue()));
                    if (!Cools.isEmpty(locMast2)) {
                        locMast = locMast2;
                        break;
                    }
                } else {
                    if (!Cools.isEmpty(locMast1)) {
                        locMast = locMast1;
                        break;
                    }
                }
            }
            if (Cools.isEmpty(locMast) && Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                for (LocMast locMast1 : locMasts) {
                    if (!VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
                        continue;
                    }
                    if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                        String shallowLoc = Utils.getDeepLoc(slaveProperties, locMast1.getLocNo());
                        LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                                .eq("loc_no", shallowLoc).eq("loc_sts", "O").eq("whs_type", rowLastnoType.getType().longValue()));
                        if (!Cools.isEmpty(locMast2)) {
                            locMast = locMast2;
                            break;
                        } else {
                            locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                                    .eq("loc_no", shallowLoc).eq("loc_sts", "F").eq("whs_type", rowLastnoType.getType().longValue()));
                            if (!Cools.isEmpty(locMast2)) {
                                locMast = locMast1;
                                break;
                            } else {
                                locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                                        .eq("loc_no", shallowLoc).eq("loc_sts", "D").eq("whs_type", rowLastnoType.getType().longValue()));
                                if (!Cools.isEmpty(locMast2)) {
                                    locMast = locMast1;
                                    break;
                                }
                            }
                        }
                    } else {
                        if (!Cools.isEmpty(locMast1)) {
                            locMast = locMast1;
                            break;
                        }
                    }
                }
            }
            locMast = findStandardEmptyLoc(rowLastno, rowLastnoType, crnNo, nearRow, locTypeDto);
        }
        // 1.按规则查找库位
        if (Cools.isEmpty(locMast) && crnNo != 0) {
            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                    .eq("row1", nearRow)
                    .eq("loc_sts", "O").eq("whs_type", rowLastnoType.getType().longValue())
                    .orderBy("lev1", true).orderBy("bay1", false));
            for (LocMast locMast1 : locMasts) {
                if (!VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
                    continue;
                }
                if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                    String shallowLoc = Utils.getDeepLoc(slaveProperties, locMast1.getLocNo());
                    LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                            .eq("loc_no", shallowLoc).eq("loc_sts", "O").eq("whs_type", rowLastnoType.getType().longValue()));
                    if (!Cools.isEmpty(locMast2)) {
                        locMast = locMast2;
                        break;
                    }
                } else {
                    if (!Cools.isEmpty(locMast1)) {
                        locMast = locMast1;
                        break;
                    }
                }
            }
            if (Cools.isEmpty(locMast) && Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                for (LocMast locMast1 : locMasts) {
                    if (!VersionUtils.locMoveCheckLocTypeComplete(locMast1, locTypeDto)) {
                        continue;
                    }
                    if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                        String shallowLoc = Utils.getDeepLoc(slaveProperties, locMast1.getLocNo());
                        LocMast locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                                .eq("loc_no", shallowLoc).eq("loc_sts", "O").eq("whs_type", rowLastnoType.getType().longValue()));
                        if (!Cools.isEmpty(locMast2)) {
                            locMast = locMast2;
                            break;
                        } else {
                            locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                                    .eq("loc_no", shallowLoc).eq("loc_sts", "F").eq("whs_type", rowLastnoType.getType().longValue()));
                            if (!Cools.isEmpty(locMast2)) {
                                locMast = locMast1;
                                break;
                            } else {
                                locMast2 = locMastService.selectOne(new EntityWrapper<LocMast>()
                                        .eq("loc_no", shallowLoc).eq("loc_sts", "D").eq("whs_type", rowLastnoType.getType().longValue()));
                                if (!Cools.isEmpty(locMast2)) {
                                    locMast = locMast1;
                                    break;
                                }
                            }
                        }
                    } else {
                        if (!Cools.isEmpty(locMast1)) {
                            locMast = locMast1;
                            break;
                        }
                    }
                }
            }
            locMast = findStandardEmptyLoc(rowLastno, rowLastnoType, crnNo, nearRow, locTypeDto);
        }
        if (!Cools.isEmpty(locMast) && !basCrnpService.checkSiteError(crnNo, true)) {
            locMast = null;
        }
        // 递归查询
        // Retry search
        if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
            // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归
            // Scan next aisle first, then retry with upward-compatible locType1.
            if (times < rowCount * 2) {
                times = times + 1;
                return getLocNoRun(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, times);
            }
//            // 2.库位当前所属尺寸无空库位时,调整尺寸参数,向上兼容检索库位
//            if (locTypeDto.getLocType1() < 2) {
//                int i = locTypeDto.getLocType1() + 1;
//                locTypeDto.setLocType1((short)i);
//                return getLocNo(1, staDescId, sourceStaNo, matnr,batch,grade, locTypeDto, 0);
//            }
            log.error("系统没有空库位!!! 尺寸规格: {}, 轮询次数:{}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("没有空库位");
            LocTypeDto compatibleLocTypeDto = buildUpwardCompatibleLocTypeDto(locTypeDto);
            if (compatibleLocTypeDto != null) {
                log.warn("locType1 upward compatibility retry. source={}, target={}", JSON.toJSONString(locTypeDto), JSON.toJSONString(compatibleLocTypeDto));
                return getLocNoRun(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, compatibleLocTypeDto, 0);
            }
            log.error("No empty location found. spec={}, times={}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("\u6ca1\u6709\u7a7a\u5e93\u4f4d");
        }
        String locNo = locMast.getLocNo();
@@ -528,6 +716,112 @@
        startupDto.setSourceStaNo(sourceStaNo);
        startupDto.setLocNo(locNo);
        return startupDto;
    }
    public StartupDto getLocNoRun2(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, int times) {
        int crnNo = 0;
        int nearRow = 0;
        int curRow = 0;
        int rowCount = 0;
        LocMast locMast = null;
        StartupDto startupDto = new StartupDto();
        RowLastno rowLastno = rowLastnoService.selectById(whsType);
        if (Cools.isEmpty(rowLastno)) {
            throw new CoolException("数据异常,请联系管理员===>库位规则未知");
        }
        crnNo = rowLastno.getCurrentRow()/2+1;
        RowLastnoType rowLastnoType = rowLastnoTypeService.selectById(rowLastno.getTypeId());
        if (Cools.isEmpty(rowLastnoType)) {
            throw new CoolException("数据异常,请联系管理员===》库位规则类型未知");
        }
        int crnNumber = rowLastno.getCrnQty();
        curRow = rowLastno.getCurrentRow();
        Wrapper<StaDesc> wrapper = null;
        StaDesc staDesc = null;
        BasDevp staNo = null;
        if (Utils.BooleanWhsTypeSta(rowLastno, staDescId)) {
            wrapper = new EntityWrapper<StaDesc>()
                    .eq("type_no", staDescId)
                    .eq("stn_no", sourceStaNo)
                    .eq("crn_no", crnNo);
            staDesc = staDescService.selectOne(wrapper);
            if (Cools.isEmpty(staDesc)) {
                log.error("type_no={},stn_no={},crn_no={}", staDescId, sourceStaNo, crnNo);
                crnNo = 0;
            } else {
                staNo = basDevpService.selectById(staDesc.getCrnStn());
                if (!staNo.getAutoing().equals("Y")) {
                    log.error("目标站{}不可用", staDesc.getCrnStn());
                    crnNo = 0;
                }
                startupDto.setStaNo(staNo.getDevNo());
            }
        }
        LocMast locMast1 = locMastService.selectOne(new EntityWrapper<LocMast>()
                .eq("crn_no", crnNo)
                .eq("loc_sts", "O")
                .orderBy("lev1")
                .orderBy("bay1")
                .eq("loc_type1",locTypeDto.getLocType1()));
        if (!Cools.isEmpty(locMast1)) {
            locMast=locMast1;
        }
        if (curRow==rowLastno.geteRow()-1) {
            curRow = 1;
        }else{
            curRow = curRow + 2;
        }
        rowLastno.setCurrentRow(curRow);
        rowLastnoService.updateById(rowLastno);
        if (!Cools.isEmpty(locMast) && !basCrnpService.checkSiteError(crnNo, true)) {
            locMast = null;
        }
        if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
            if (times < rowCount * 2) {
                times = times + 1;
                return getLocNoRun2(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, times);
            }
            LocTypeDto compatibleLocTypeDto = buildUpwardCompatibleLocTypeDto(locTypeDto);
            if (compatibleLocTypeDto != null) {
                log.warn("locType1 upward compatibility retry. source={}, target={}", JSON.toJSONString(locTypeDto), JSON.toJSONString(compatibleLocTypeDto));
                return getLocNoRun2(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, compatibleLocTypeDto, 0);
            }
            log.error("No empty location found. spec={}, times={}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("\u6ca1\u6709\u7a7a\u5e93\u4f4d");
        }
        int workNo = getWorkNo(0);
        startupDto.setWorkNo(workNo);
        startupDto.setCrnNo(crnNo);
        startupDto.setSourceStaNo(sourceStaNo);
        startupDto.setLocNo(locMast.getLocNo());
        return startupDto;
    }
    private LocMast findSingleExtensionEmptyLoc(RowLastno rowLastno, int crnNo, int nearRow, RowLastnoType rowLastnoType, LocTypeDto locTypeDto) {
        for (Integer searchRow : getCrnSearchRows(rowLastno, crnNo, nearRow)) {
            List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
                    .eq("row1", searchRow)
                    .eq("loc_sts", "O")
                    .eq("whs_type", rowLastnoType.getType().longValue())
                    .orderBy("bay1", true)
                    .orderBy("lev1", true));
            for (LocMast locMast : locMasts) {
                if (VersionUtils.locMoveCheckLocTypeComplete(locMast, locTypeDto)) {
                    return locMast;
                }
            }
        }
        return null;
    }
    public StartupDto getLocNoRun4(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, int times) {
@@ -928,10 +1222,10 @@
                        continue;
                    }
                    if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                        //获取目标库位所在巷道并排序
                        // ?????????????
//                        List<String> groupOutsideLocCrn = Utils.getGroupOutLocCrn(curRow,nearRow,locMast1.getLocNo(), curRow>nearRow);
                        //获取目标库位所在巷道最浅非空库位
                        // ????????????????
                        LocMast locMast2 = locMastService.selectLocByLocStsPakInF(curRow, nearRow, locMast1, rowLastnoType.getType().longValue());
                        if (Cools.isEmpty(locMast2)) {
                            LocMast locMast3 = locMastService.selectLocByLocStsPakInO(curRow, nearRow, locMast1, rowLastnoType.getType().longValue());
@@ -953,15 +1247,20 @@
            }
        }
        // 递归查询
        // Retry search
        if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
            // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归
            // Scan next aisle first, then retry with upward-compatible locType1.
            if (times < rowCount * 2) {
                times = times + 1;
                return getLocNoRun4(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, times);
            }
            log.error("系统没有空库位!!! 尺寸规格: {}, 轮询次数:{}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("没有空库位");
            LocTypeDto compatibleLocTypeDto = buildUpwardCompatibleLocTypeDto(locTypeDto);
            if (compatibleLocTypeDto != null) {
                log.warn("locType1 upward compatibility retry. source={}, target={}", JSON.toJSONString(locTypeDto), JSON.toJSONString(compatibleLocTypeDto));
                return getLocNoRun4(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, compatibleLocTypeDto, 0);
            }
            log.error("No empty location found. spec={}, times={}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("\u6ca1\u6709\u7a7a\u5e93\u4f4d");
        }
        String locNo = locMast.getLocNo();
@@ -975,7 +1274,7 @@
        return startupDto;
    }
    public StartupDto getLocNoRun5(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, int times) {
    public StartupDto getLocNoRun5(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, List<Integer> recommendRows, int times) {
        // 初始化参数
        int crnNo = 0;      //堆垛机号
@@ -1045,6 +1344,20 @@
        }
        // 开始查找库位 ==============================>>
        if (Cools.isEmpty(locMast) && !Cools.isEmpty(recommendRows)) {
            for (Integer recommendRow : recommendRows) {
                if (Cools.isEmpty(recommendRow)) {
                    continue;
                }
                LocMast recommendLoc = locMastService.queryFreeLocMast(recommendRow, locTypeDto.getLocType1(), rowLastnoType.getType().longValue());
                if (!Cools.isEmpty(recommendLoc) && VersionUtils.locMoveCheckLocTypeComplete(recommendLoc, locTypeDto)) {
                    locMast = recommendLoc;
                    crnNo = recommendLoc.getCrnNo();
                    break;
                }
            }
        }
        if (Cools.isEmpty(locMast) && sourceStaNo != 4006) {//si'lou'p四楼盘点选择区域
                 List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
@@ -1143,7 +1456,7 @@
                            continue;
                        }
                        if (Utils.BooleanWhsTypeStaIoType(rowLastno)) {
                            // 获取目标库位所在巷道最深空库位
                            // ???????????????
                            LocMast locMast2 = locMastService.selectLocByLocStsPakInO(curRow, nearRow, locMast1, rowLastnoType.getType().longValue());
                            if (!Cools.isEmpty(locMast2) && locMast2.getRow1() == curRow) {
                                locMast = locMast2;
@@ -1154,7 +1467,7 @@
                    }
                    if (found) {
                        break; // 找到目标库位后跳出循环
                        break; // ???????????
                    }
                }
            }
@@ -1162,15 +1475,20 @@
        // 递归查询
        // Retry search
        if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
            // 当前巷道无空库位时,递归调整至下一巷道,检索全部巷道无果后,跳出递归
            // Scan next aisle first, then retry with upward-compatible locType1.
            if (times < rowCount * 2) {
                times = times + 1;
                return getLocNoRun5(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, times);
                return getLocNoRun5(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, recommendRows, times);
            }
            log.error("系统没有空库位!!! 尺寸规格: {}, 轮询次数:{}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("没有空库位");
            LocTypeDto compatibleLocTypeDto = buildUpwardCompatibleLocTypeDto(locTypeDto);
            if (compatibleLocTypeDto != null) {
                log.warn("locType1 upward compatibility retry. source={}, target={}", JSON.toJSONString(locTypeDto), JSON.toJSONString(compatibleLocTypeDto));
                return getLocNoRun5(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, compatibleLocTypeDto, recommendRows, 0);
            }
            log.error("No empty location found. spec={}, times={}", JSON.toJSONString(locTypeDto), times);
            throw new CoolException("\u6ca1\u6709\u7a7a\u5e93\u4f4d");
        }
        String locNo = locMast.getLocNo();