自动化立体仓库 - WMS系统
zwl
4 天以前 ac88fa85ea2b39f9c94f080a95406739e64fd7f2
src/main/java/com/zy/common/service/CommonService.java
@@ -538,6 +538,30 @@
        return orderedCrnNos;
    }
    /**
     * 优先按 s_crn_no/e_crn_no 反推真实堆垛机数量。
     *
     * 之前有逻辑错误把 asr_row_lastno.crn_qty 当成轮询游标写回,
     * 导致“堆垛机数量”字段被污染。后续找位不能再直接信任 crn_qty,
     * 否则会少扫堆垛机,表现出来就是任务长期偏向某几台堆垛机。
     */
    private int resolveCrnCount(RowLastno rowLastno) {
        if (rowLastno == null) {
            return 0;
        }
        if (rowLastno.getsCrnNo() != null && rowLastno.geteCrnNo() != null && rowLastno.geteCrnNo() >= rowLastno.getsCrnNo()) {
            return rowLastno.geteCrnNo() - rowLastno.getsCrnNo() + 1;
        }
        if (rowLastno.getCrnQty() != null && rowLastno.getCrnQty() > 0) {
            return rowLastno.getCrnQty();
        }
        Integer rowSpan = getCrnRowSpan(rowLastno.getTypeId());
        if (rowSpan != null && rowSpan > 0 && rowLastno.getsRow() != null && rowLastno.geteRow() != null && rowLastno.geteRow() >= rowLastno.getsRow()) {
            return (rowLastno.geteRow() - rowLastno.getsRow() + 1 + rowSpan - 1) / rowSpan;
        }
        return 0;
    }
    private Integer getCrnStartRow(RowLastno rowLastno, Integer crnNo) {
        if (rowLastno == null || crnNo == null) {
            return null;
@@ -725,29 +749,6 @@
    }
    /**
     * 普通 run2 的推荐排优先阶段。
     *
     * 推荐排只对普通物料生效,空托盘已经切换成“按库区轮询堆垛机”的规则,
     * 因此这里会直接跳过空托盘请求,避免 row 参数把空托盘重新带回旧逻辑。
     */
    private LocMast findRun2RecommendLoc(RowLastno rowLastno, RowLastnoType rowLastnoType, boolean emptyPalletRequest,
                                         List<Integer> recommendRows, LocTypeDto locTypeDto, Integer staDescId,
                                         Integer sourceStaNo, StartupDto startupDto, Integer preferredArea,
                                         List<Integer> triedCrnNos) {
        if (emptyPalletRequest) {
            return null;
        }
        List<Integer> recommendCrnNos = mapRowsToCrnNos(rowLastno, recommendRows);
        if (Cools.isEmpty(recommendCrnNos)) {
            return null;
        }
        LocMast locMast = findRun2EmptyLocByCrnNos(rowLastno, rowLastnoType, recommendCrnNos, locTypeDto,
                staDescId, sourceStaNo, startupDto, preferredArea, "recommend");
        triedCrnNos.addAll(recommendCrnNos);
        return locMast;
    }
    /**
     * 普通物料 run2 找位主流程。
     *
     * 执行顺序:
@@ -764,8 +765,13 @@
                locTypeDto == null || locTypeDto.getLocType1() == null ? null : locTypeDto.getLocType1().intValue(),
                findLocNoAttributeVo == null ? null : findLocNoAttributeVo.getMatnr());
        if (!Cools.isEmpty(stationCrnLocTypes)) {
            return findRun2EmptyLocByCrnLocTypeEntries(rowLastno, rowLastnoType, stationCrnLocTypes,
            // 站点优先级只是“优先尝试”,没有命中时必须继续走默认/库区回退,
            // 否则会把“优先候选无位”误判成“整仓无位”。
            LocMast locMast = findRun2EmptyLocByCrnLocTypeEntries(rowLastno, rowLastnoType, stationCrnLocTypes,
                    locTypeDto, staDescId, sourceStaNo, startupDto, preferredArea, "station-priority");
            if (!Cools.isEmpty(locMast)) {
                return locMast;
            }
        }
        if (preferredArea == null) {
            List<Integer> defaultCrnNos = new ArrayList<>(orderedCrnNos);
@@ -791,11 +797,25 @@
     * 普通物料命中库位后,沿用 run2 原有的全仓轮询游标推进方式。
     */
    private void advanceNormalRun2Cursor(RowLastno rowLastno, int curRow) {
        advanceNormalRun2Cursor(rowLastno, curRow, null, null);
    }
    /**
     * 普通物料游标优先按“本次真正可用的堆垛机集合”推进。
     *
     * 这样即使库区定义里存在不存在的堆垛机,或者路径主数据只覆盖部分堆垛机,
     * 满板任务也会在真实可作业的堆垛机之间轮询,不会因为理论堆垛机号的空洞而长期回落到同一台。
     */
    private void advanceNormalRun2Cursor(RowLastno rowLastno, int curRow, List<Integer> runnableCrnNos, Integer selectedCrnNo) {
        if (rowLastno == null) {
            return;
        }
        int updateCurRow = curRow == 0 ? (rowLastno.getsRow() == null ? 1 : rowLastno.getsRow()) : curRow;
        updateCurRow = getNextRun2CurrentRow(rowLastno, updateCurRow);
        if (!Cools.isEmpty(runnableCrnNos) && selectedCrnNo != null) {
            updateCurRow = getNextRun2CurrentRow(rowLastno, runnableCrnNos, selectedCrnNo, updateCurRow);
        } else {
            updateCurRow = getNextRun2CurrentRow(rowLastno, updateCurRow);
        }
        rowLastno.setCurrentRow(updateCurRow);
        rowLastnoService.updateById(rowLastno);
    }
@@ -826,33 +846,6 @@
                }
            }
        }
        return result;
    }
    private List<Integer> mapRowsToCrnNos(RowLastno rowLastno, List<Integer> rows) {
        List<Integer> result = new ArrayList<>();
        if (rowLastno == null || Cools.isEmpty(rows)) {
            return result;
        }
        LinkedHashSet<Integer> orderedCrnNos = new LinkedHashSet<>();
        Integer rowSpan = getCrnRowSpan(rowLastno.getTypeId());
        if (rowSpan == null || rowSpan <= 0) {
            rowSpan = 2;
        }
        int startCrnNo = rowLastno.getsCrnNo() == null ? 1 : rowLastno.getsCrnNo();
        int endCrnNo = rowLastno.geteCrnNo() == null ? startCrnNo + rowLastno.getCrnQty() - 1 : rowLastno.geteCrnNo();
        int startRow = rowLastno.getsRow() == null ? 1 : rowLastno.getsRow();
        int endRow = rowLastno.geteRow() == null ? Integer.MAX_VALUE : rowLastno.geteRow();
        for (Integer row : rows) {
            if (row == null || row < startRow || row > endRow) {
                continue;
            }
            int crnNo = startCrnNo + (row - startRow) / rowSpan;
            if (crnNo >= startCrnNo && crnNo <= endCrnNo) {
                orderedCrnNos.add(crnNo);
            }
        }
        result.addAll(orderedCrnNos);
        return result;
    }
@@ -1471,7 +1464,7 @@
        }
        int sRow = rowLastno.getsRow();
        int eRow = rowLastno.geteRow();
        int crnNumber = rowLastno.getCrnQty();
        int crnNumber = resolveCrnCount(rowLastno);
        // ===============>>>> 开始执行
@@ -1669,9 +1662,11 @@
     * run2 入库找位主流程。
     *
     * 当前方法只保留“组织流程”和“统一收口”的职责,具体策略拆成独立方法:
     * 1. 普通物料:推荐排优先 -> 站点优先库区/堆垛机 -> 其它库区。
     * 1. 普通物料:按 row_lastno 自身轮询顺序 -> 站点优先库区/堆垛机 -> 其它库区。
     * 2. 空托盘:优先库区 loc_type2=1 -> 其它库区 loc_type2=1 -> loc_type1=2 兼容。
     * 3. 命中库位后分别回写普通物料游标或空托盘库区游标。
     *
     * WCS 传入的推荐排不再参与 run2 选位,避免上游 row 参数把任务重新绑回固定堆垛机。
     */
    public StartupDto getLocNoRun2(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, List<Integer> recommendRows, int times) {
@@ -1691,7 +1686,7 @@
        if (Cools.isEmpty(rowLastnoType)) {
            throw new CoolException("数据异常,请联系管理员===》库位规则类型未知");
        }
        int crnNumber = rowLastno.getCrnQty();
        int crnNumber = resolveCrnCount(rowLastno);
        rowCount = crnNumber;
        curRow = rowLastno.getCurrentRow();
@@ -1701,23 +1696,20 @@
        Run2AreaSearchResult emptyPalletAreaSearchResult = null;
        List<Integer> orderedCrnNos = getOrderedCrnNos(rowLastno, crnNo);
        List<Integer> orderedRunnableCrnNos = getOrderedRunnableRun2CrnNos(rowLastno, staDescId, sourceStaNo, orderedCrnNos);
        List<Integer> triedCrnNos = new ArrayList<>();
        locMast = findRun2RecommendLoc(rowLastno, rowLastnoType, emptyPalletRequest, recommendRows, locTypeDto,
                staDescId, sourceStaNo, startupDto, preferredArea, triedCrnNos);
        if (Cools.isEmpty(locMast)) {
            if (emptyPalletRequest) {
                // 空托盘单独按库区轮询:
                // 1. 当前库区先找 loc_type2=1
                // 2. 当前库区没有,再找其他库区 loc_type2=1
                // 3. 全部 narrow 都没有时,再退到 loc_type1=2
                emptyPalletAreaSearchResult = findEmptyPalletRun2AreaLoc(rowLastno, staDescId, sourceStaNo, startupDto, preferredArea, locTypeDto);
                if (!Cools.isEmpty(emptyPalletAreaSearchResult)) {
                    locMast = emptyPalletAreaSearchResult.locMast;
                }
            } else {
                locMast = findNormalRun2Loc(rowLastno, rowLastnoType, sourceStaNo, staDescId, findLocNoAttributeVo,
                        locTypeDto, startupDto, preferredArea, orderedCrnNos, triedCrnNos);
        if (emptyPalletRequest) {
            // 空托盘单独按库区轮询:
            // 1. 当前库区先找 loc_type2=1
            // 2. 当前库区没有,再找其他库区 loc_type2=1
            // 3. 全部 narrow 都没有时,再退到 loc_type1=2
            emptyPalletAreaSearchResult = findEmptyPalletRun2AreaLoc(rowLastno, staDescId, sourceStaNo, startupDto, preferredArea, locTypeDto);
            if (!Cools.isEmpty(emptyPalletAreaSearchResult)) {
                locMast = emptyPalletAreaSearchResult.locMast;
            }
        } else {
            locMast = findNormalRun2Loc(rowLastno, rowLastnoType, sourceStaNo, staDescId, findLocNoAttributeVo,
                    locTypeDto, startupDto, preferredArea, orderedCrnNos, triedCrnNos);
        }
        if (!Cools.isEmpty(locMast)) {
@@ -1727,7 +1719,7 @@
        if (emptyPalletRequest) {
            advanceEmptyPalletRun2Cursor(emptyPalletAreaSearchResult, locMast);
        } else {
            advanceNormalRun2Cursor(rowLastno, curRow);
            advanceNormalRun2Cursor(rowLastno, curRow, orderedRunnableCrnNos, locMast == null ? null : locMast.getCrnNo());
        }
        if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
@@ -1789,7 +1781,7 @@
        }
        int sRow = rowLastno.getsRow();
        int eRow = rowLastno.geteRow();
        int crnNumber = rowLastno.getCrnQty();
        int crnNumber = resolveCrnCount(rowLastno);
        // ===============>>>> 开始执行
        curRow = rowLastno.getCurrentRow();
@@ -2220,6 +2212,7 @@
    }
    public StartupDto getLocNoRun5(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, List<Integer> recommendRows, int times) {
        // WCS 传入的推荐排不再参与 AGV/平库选位,统一按库位排号自身轮询逻辑找位。
        // 初始化参数
        int crnNo = 0;      //堆垛机号
@@ -2289,20 +2282,6 @@
        }
        // 开始查找库位 ==============================>>
        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;
                }
            }
        }
        Integer preferredArea = findLocNoAttributeVo.getOutArea();