From 2acfc2d2a0e956910c51bd996f443b3cb9bd3dc9 Mon Sep 17 00:00:00 2001
From: zwl <1051256694@qq.com>
Date: 星期日, 22 三月 2026 14:27:21 +0800
Subject: [PATCH] 优化找库位规则

---
 src/main/java/com/zy/common/service/CommonService.java | 1178 ++++++++++++++++++++++++++++++++++++++++------------------
 1 files changed, 809 insertions(+), 369 deletions(-)

diff --git a/src/main/java/com/zy/common/service/CommonService.java b/src/main/java/com/zy/common/service/CommonService.java
index 819e6e9..5f92b5b 100644
--- a/src/main/java/com/zy/common/service/CommonService.java
+++ b/src/main/java/com/zy/common/service/CommonService.java
@@ -7,12 +7,14 @@
 import com.core.common.Cools;
 import com.core.exception.CoolException;
 import com.zy.asrs.entity.*;
+import com.zy.asrs.entity.param.BasCrnDepthRuleRuntimePreviewParam;
 import com.zy.asrs.entity.result.FindLocNoAttributeVo;
 import com.zy.asrs.entity.result.KeyValueVo;
 import com.zy.asrs.service.*;
 import com.zy.asrs.utils.Utils;
 import com.zy.asrs.utils.VersionUtils;
 import com.zy.common.entity.Parameter;
+import com.zy.common.model.CrnDepthRuleProfile;
 import com.zy.common.model.LocTypeDto;
 import com.zy.common.model.Shelves;
 import com.zy.common.model.StartupDto;
@@ -59,6 +61,28 @@
         }
     }
 
+    private static class Run2SearchResult {
+        private final LocMast locMast;
+        private final RowLastno rowLastno;
+        private final List<Integer> runnableCrnNos;
+
+        private Run2SearchResult(LocMast locMast, RowLastno rowLastno, List<Integer> runnableCrnNos) {
+            this.locMast = locMast;
+            this.rowLastno = rowLastno;
+            this.runnableCrnNos = runnableCrnNos;
+        }
+    }
+
+    private static class Run2Cursor {
+        private final int currentRow;
+        private final Integer currentCrnNo;
+
+        private Run2Cursor(int currentRow, Integer currentCrnNo) {
+            this.currentRow = currentRow;
+            this.currentCrnNo = currentCrnNo;
+        }
+    }
+
     @Autowired
     private WrkMastService wrkMastService;
     @Autowired
@@ -81,6 +105,8 @@
     private SlaveProperties slaveProperties;
     @Autowired
     private WrkDetlService wrkDetlService;
+    @Autowired
+    private BasCrnDepthRuleService basCrnDepthRuleService;
 
     /**
      * 鐢熸垚宸ヤ綔鍙�
@@ -206,25 +232,91 @@
      *
      * 绌烘墭鐩樼殑搴撲綅绛栫暐鏈変袱娈碉細
      * 1. 棣栬疆鍙檺鍒� loc_type2=1锛岃〃绀轰紭鍏堟壘绐勫簱浣嶃��
-     * 2. 棣栬疆涓嶉檺鍒� loc_type1锛岄珮浣庝綅閮藉厑璁稿弬涓庢悳绱€��
+     * 2. loc_type1 楂樺害淇℃伅蹇呴』淇濈暀锛屽悗缁啀鎸変綆浣嶅悜楂樹綅鍏煎銆�
      *
-     * 杩欐牱鍋氱殑鍘熷洜鏄幇鍦哄彛寰勫凡缁忔敼鎴愨�滃厛鎵剧獎搴撲綅鈥濓紝鑰屼笉鏄�滃厛鎵句綆浣嶇獎搴撲綅鈥濄��
-     * 鍥犳杩欓噷浼氫富鍔ㄦ竻绌� locType1锛岄槻姝㈣绔欑偣榛樿鍊煎甫鎴愪綆浣嶄紭鍏堛��
+     * 闈炵┖鎵樼洏鍙繚鐣� loc_type1锛屾弧鎵樻壘浣嶄笉鍐嶄娇鐢� loc_type2/loc_type3 杩囨护銆�
      */
     private LocTypeDto normalizeLocTypeDto(Integer staDescId, FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto) {
+        LocTypeDto normalizedLocTypeDto = locTypeDto == null ? new LocTypeDto() : locTypeDto;
         if (!isEmptyPalletRequest(staDescId, findLocNoAttributeVo)) {
-            return locTypeDto;
+            normalizedLocTypeDto.setLocType2(null);
+            normalizedLocTypeDto.setLocType3(null);
+            return normalizedLocTypeDto;
         }
         if (findLocNoAttributeVo != null && Cools.isEmpty(findLocNoAttributeVo.getMatnr())) {
             findLocNoAttributeVo.setMatnr("emptyPallet");
         }
-        LocTypeDto normalizedLocTypeDto = locTypeDto == null ? new LocTypeDto() : locTypeDto;
-        // 绌烘墭鐩橀杞笉闄愬埗楂樹綆浣嶏紝鍙繚鐣欌�滅獎搴撲綅浼樺厛鈥濈殑绾︽潫銆�
-        normalizedLocTypeDto.setLocType1(null);
         normalizedLocTypeDto.setLocType2((short) 1);
         return normalizedLocTypeDto;
     }
 
+    private LocTypeDto copyLocTypeDto(LocTypeDto locTypeDto) {
+        if (locTypeDto == null) {
+            return null;
+        }
+        LocTypeDto copied = new LocTypeDto();
+        copied.setLocType1(locTypeDto.getLocType1());
+        copied.setLocType2(locTypeDto.getLocType2());
+        copied.setLocType3(locTypeDto.getLocType3());
+        copied.setSiteId(locTypeDto.getSiteId());
+        return copied;
+    }
+
+    /**
+     * 绌烘墭鐩樺浐瀹氭寜 4 娈靛紡鎵句綅锛�
+     * 1. 涓ユ牸楂樺害 + narrow
+     * 2. 涓ユ牸楂樺害 + any locType2
+     * 3. 鍚戜笂鍏煎楂樺害 + narrow
+     * 4. 鍚戜笂鍏煎楂樺害 + any locType2
+     */
+    private List<LocTypeDto> buildEmptyPalletSearchLocTypes(LocTypeDto locTypeDto) {
+        LinkedHashSet<LocTypeDto> searchLocTypes = new LinkedHashSet<LocTypeDto>();
+        LocTypeDto narrowStrictLocType = copyLocTypeDto(locTypeDto == null ? new LocTypeDto() : locTypeDto);
+        if (narrowStrictLocType != null) {
+            narrowStrictLocType.setLocType2((short) 1);
+            searchLocTypes.add(narrowStrictLocType);
+            LocTypeDto openStrictLocType = copyLocTypeDto(narrowStrictLocType);
+            openStrictLocType.setLocType2(null);
+            searchLocTypes.add(openStrictLocType);
+
+            LocTypeDto narrowCompatibleLocType = buildUpwardCompatibleLocTypeDto(narrowStrictLocType);
+            if (narrowCompatibleLocType != null) {
+                narrowCompatibleLocType.setLocType2((short) 1);
+                searchLocTypes.add(narrowCompatibleLocType);
+                LocTypeDto openCompatibleLocType = copyLocTypeDto(narrowCompatibleLocType);
+                openCompatibleLocType.setLocType2(null);
+                searchLocTypes.add(openCompatibleLocType);
+            }
+        }
+        return new ArrayList<LocTypeDto>(searchLocTypes);
+    }
+
+    private String buildEmptyPalletStageCode(LocTypeDto baseLocTypeDto, LocTypeDto stageLocTypeDto) {
+        boolean compatibleHeight = baseLocTypeDto != null
+                && baseLocTypeDto.getLocType1() != null
+                && stageLocTypeDto != null
+                && stageLocTypeDto.getLocType1() != null
+                && !baseLocTypeDto.getLocType1().equals(stageLocTypeDto.getLocType1());
+        boolean narrowOnly = stageLocTypeDto != null
+                && stageLocTypeDto.getLocType2() != null
+                && stageLocTypeDto.getLocType2() == 1;
+        return (compatibleHeight ? "compatible-height" : "strict-height")
+                + "-"
+                + (narrowOnly ? "narrow-only" : "all-locType2");
+    }
+
+    /**
+     * 鍒ゆ柇褰撳墠瑙勬牸鏄惁灞炰簬婊℃墭鎵句綅銆�
+     *
+     * 婊℃墭鍙弬鑰� loc_type1锛屼絾浠嶉渶鏄惧紡鎺掗櫎 loc_type2=1 鐨勭獎搴撲綅銆�
+     */
+    private boolean isFullPalletLocTypeSearch(LocTypeDto locTypeDto) {
+        return locTypeDto != null && locTypeDto.getLocType2() == null && locTypeDto.getLocType3() == null;
+    }
+
+    /**
+     * 鎶� locType 鏉′欢杩藉姞鍒板簱浣嶆煡璇㈡潯浠堕噷銆�
+     */
     private Wrapper<LocMast> applyLocTypeFilters(Wrapper<LocMast> wrapper, LocTypeDto locTypeDto, boolean includeLocType1) {
         if (wrapper == null || locTypeDto == null) {
             return wrapper;
@@ -232,15 +324,21 @@
         if (includeLocType1 && locTypeDto.getLocType1() != null && locTypeDto.getLocType1() > 0) {
             wrapper.eq("loc_type1", locTypeDto.getLocType1());
         }
+        if (isFullPalletLocTypeSearch(locTypeDto)) {
+            wrapper.eq("loc_type2", 0);
+        }
         if (locTypeDto.getLocType2() != null && locTypeDto.getLocType2() > 0) {
             wrapper.eq("loc_type2", locTypeDto.getLocType2());
         }
-        if (locTypeDto.getLocType3() != null && locTypeDto.getLocType3() > 0) {
-            wrapper.eq("loc_type3", locTypeDto.getLocType3());
-        }
+//        if (locTypeDto.getLocType3() != null && locTypeDto.getLocType3() > 0) {
+//            wrapper.eq("loc_type3", locTypeDto.getLocType3());
+//        }
         return wrapper;
     }
 
+    /**
+     * 瑙f瀽鏈鎵句綅搴斾紭鍏堜娇鐢ㄧ殑搴撳尯锛岀珯鐐圭粦瀹氫紭鍏堜簬鎺ュ彛浼犲弬銆�
+     */
     private Integer resolvePreferredArea(Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo) {
         BasDevp sourceStation = basDevpService.selectById(sourceStaNo);
         Integer stationArea = parseArea(sourceStation == null ? null : sourceStation.getArea());
@@ -254,6 +352,9 @@
         return null;
     }
 
+    /**
+     * 鎶婄珯鐐圭淮鎶ょ殑搴撳尯鍊肩粺涓�瑙f瀽鎴� 1/2/3銆�
+     */
     private Integer parseArea(String area) {
         if (Cools.isEmpty(area)) {
             return null;
@@ -280,6 +381,9 @@
         return null;
     }
 
+    /**
+     * 璇诲彇 AGV 鍚勫簱鍖哄搴旂殑鎺掗厤缃��
+     */
     private String getAgvAreaRowsConfig(Integer area) {
         Parameter parameter = Parameter.get();
         if (parameter == null || area == null) {
@@ -297,6 +401,9 @@
         }
     }
 
+    /**
+     * 瑙f瀽 AGV 鎸囧畾搴撳尯鐨勬帓椤哄簭锛岀己閰嶇疆鏃跺洖閫�鏃ч粯璁ゆ帓銆�
+     */
     private List<Integer> getAgvAreaRows(Integer area, RowLastno rowLastno) {
         List<Integer> configuredRows = parseAgvRows(getAgvAreaRowsConfig(area), rowLastno);
         if (!configuredRows.isEmpty()) {
@@ -305,6 +412,9 @@
         return getLegacyAgvRows(rowLastno);
     }
 
+    /**
+     * 姹囨�� AGV 鎵�鏈夊簱鍖虹殑鍥為��鎺掗『搴忋��
+     */
     private List<Integer> getAgvFallbackRows(RowLastno rowLastno) {
         LinkedHashSet<Integer> rows = new LinkedHashSet<>();
         for (int area = 1; area <= 3; area++) {
@@ -314,6 +424,9 @@
         return new ArrayList<>(rows);
     }
 
+    /**
+     * 鎶� AGV 搴撳尯鎺掗厤缃В鏋愭垚鏈夊簭鎺掑彿鍒楄〃銆�
+     */
     private List<Integer> parseAgvRows(String configValue, RowLastno rowLastno) {
         List<Integer> rows = new ArrayList<>();
         if (rowLastno == null || Cools.isEmpty(configValue)) {
@@ -350,6 +463,9 @@
         return rows;
     }
 
+    /**
+     * 杩斿洖 AGV 鏃ч�昏緫浣跨敤鐨勯粯璁ゆ帓椤哄簭銆�
+     */
     private List<Integer> getLegacyAgvRows(RowLastno rowLastno) {
         List<Integer> rows = new ArrayList<>();
         if (rowLastno == null) {
@@ -371,6 +487,9 @@
         return rows;
     }
 
+    /**
+     * 浠呭湪鎺掑彿钀藉湪浠撳簱鑼冨洿鍐呮椂杩藉姞鍒� AGV 鎺掑垪琛ㄣ��
+     */
     private void addAgvRow(LinkedHashSet<Integer> rows, Integer row, RowLastno rowLastno) {
         if (rows == null || row == null || rowLastno == null) {
             return;
@@ -381,6 +500,9 @@
         rows.add(row);
     }
 
+    /**
+     * 瀹夊叏瑙f瀽鏁存暟閰嶇疆锛屽け璐ユ椂杩斿洖 null銆�
+     */
     private Integer safeParseInt(String value) {
         if (Cools.isEmpty(value)) {
             return null;
@@ -392,6 +514,9 @@
         }
     }
 
+    /**
+     * 杩斿洖 AGV 鍚勫簱鍖哄搴旂殑 bay 鑼冨洿銆�
+     */
     private int[] getAgvAreaBayRange(Integer area) {
         if (area == null) {
             return new int[]{1, 19};
@@ -408,6 +533,9 @@
         }
     }
 
+    /**
+     * 鎸夋寚瀹氭帓鍜� bay 鑼冨洿椤哄簭鎼滅储 AGV 鍙敤搴撲綅銆�
+     */
     private LocMast findAgvLocByRows(RowLastno rowLastno, RowLastnoType rowLastnoType, List<Integer> rows,
                                      int startBay, int endBay, int curRow, int nearRow,
                                      LocTypeDto locTypeDto, boolean useDeepCheck) {
@@ -442,6 +570,10 @@
         }
         return null;
     }
+
+    /**
+     * 璇诲彇 run2 鍚勫簱鍖哄搴旂殑鎺掗厤缃紝缂哄け鏃跺鐢� AGV 搴撳尯閰嶇疆銆�
+     */
     private String getRun2AreaRowsConfig(Integer area) {
         Parameter parameter = Parameter.get();
         if (parameter == null || area == null) {
@@ -464,6 +596,9 @@
         return Cools.isEmpty(run2Config) ? getAgvAreaRowsConfig(area) : run2Config;
     }
 
+    /**
+     * 瑙f瀽 run2 鎸囧畾搴撳尯鐨勬帓椤哄簭锛岀己閰嶇疆鏃跺洖閫�鏃ч粯璁ゆ帓銆�
+     */
     private List<Integer> getRun2AreaRows(Integer area, RowLastno rowLastno) {
         List<Integer> configuredRows = parseAgvRows(getRun2AreaRowsConfig(area), rowLastno);
         if (!configuredRows.isEmpty()) {
@@ -472,6 +607,9 @@
         return getLegacyAgvRows(rowLastno);
     }
 
+    /**
+     * 姹囨�� run2 鍏跺畠搴撳尯鐨勫洖閫�鎺掗『搴忋��
+     */
     private List<Integer> getRun2FallbackRows(RowLastno rowLastno) {
         LinkedHashSet<Integer> rows = new LinkedHashSet<>();
         for (int area = 1; area <= 3; area++) {
@@ -481,29 +619,103 @@
         return new ArrayList<>(rows);
     }
 
+    /**
+     * 瑙f瀽 run2 鐨勮捣濮嬪爢鍨涙満鍙凤紝浼樺厛浣跨敤 currentCrnNo銆�
+     */
     private Integer resolveRun2CrnNo(RowLastno rowLastno) {
         if (rowLastno == null) {
             return null;
         }
-        Integer currentRow = rowLastno.getCurrentRow();
+        Integer startCrnNo = getRun2StartCrnNo(rowLastno);
+        Integer endCrnNo = getRun2EndCrnNo(rowLastno);
+        Integer currentCrnNo = rowLastno.getCurrentCrnNo();
+        if (currentCrnNo == null || currentCrnNo <= 0 || currentCrnNo < startCrnNo) {
+            return startCrnNo;
+        }
+        if (endCrnNo != null && currentCrnNo > endCrnNo) {
+            return startCrnNo;
+        }
+        return currentCrnNo;
+    }
+
+    private Integer getRun2StartCrnNo(RowLastno rowLastno) {
+        if (rowLastno == null || rowLastno.getsCrnNo() == null) {
+            return 1;
+        }
+        return rowLastno.getsCrnNo();
+    }
+
+    private Integer getRun2EndCrnNo(RowLastno rowLastno) {
+        if (rowLastno == null) {
+            return null;
+        }
+        Integer startCrnNo = getRun2StartCrnNo(rowLastno);
+        if (rowLastno.geteCrnNo() != null && rowLastno.geteCrnNo() >= startCrnNo) {
+            return rowLastno.geteCrnNo();
+        }
+        int crnCount = resolveCrnCount(rowLastno);
+        if (crnCount <= 0) {
+            return startCrnNo;
+        }
+        return startCrnNo + crnCount - 1;
+    }
+
+    /**
+     * 鍏煎鏃� currentRow 娓告爣锛屾妸鎺掑彿鎹㈢畻鎴愬搴旂殑鍫嗗灈鏈哄彿銆�
+     */
+    private Integer resolveRun2CrnNoByCurrentRow(RowLastno rowLastno, Integer currentRow) {
+        if (rowLastno == null) {
+            return null;
+        }
         Integer rowSpan = getCrnRowSpan(rowLastno.getTypeId());
         if (rowSpan == null || rowSpan <= 0) {
             rowSpan = 2;
         }
         int startRow = rowLastno.getsRow() == null ? 1 : rowLastno.getsRow();
-        int startCrnNo = rowLastno.getsCrnNo() == null ? 1 : rowLastno.getsCrnNo();
-        if (currentRow == null) {
+        int startCrnNo = getRun2StartCrnNo(rowLastno);
+        if (currentRow == null || currentRow <= 0) {
             return startCrnNo;
         }
         int offset = Math.max(currentRow - startRow, 0) / rowSpan;
         int crnNo = startCrnNo + offset;
-        Integer endCrnNo = rowLastno.geteCrnNo();
+        Integer endCrnNo = getRun2EndCrnNo(rowLastno);
         if (endCrnNo != null && crnNo > endCrnNo) {
             return startCrnNo;
         }
         return crnNo;
     }
 
+    private Integer getNextSequentialRun2CrnNo(RowLastno rowLastno, Integer currentCrnNo) {
+        if (rowLastno == null) {
+            return null;
+        }
+        Integer startCrnNo = getRun2StartCrnNo(rowLastno);
+        Integer endCrnNo = getRun2EndCrnNo(rowLastno);
+        if (currentCrnNo == null || currentCrnNo < startCrnNo) {
+            return startCrnNo;
+        }
+        if (endCrnNo != null && currentCrnNo >= endCrnNo) {
+            return startCrnNo;
+        }
+        return currentCrnNo + 1;
+    }
+
+    /**
+     * 瑙f瀽 run2 涓嬩竴杞簲浠庡摢鍙板爢鍨涙満寮�濮嬨��
+     */
+    private Integer getNextRun2CurrentCrnNo(RowLastno rowLastno, List<Integer> runnableCrnNos, Integer selectedCrnNo) {
+        if (!Cools.isEmpty(runnableCrnNos) && selectedCrnNo != null) {
+            int index = runnableCrnNos.indexOf(selectedCrnNo);
+            if (index >= 0) {
+                return runnableCrnNos.get((index + 1) % runnableCrnNos.size());
+            }
+        }
+        return getNextSequentialRun2CrnNo(rowLastno, resolveRun2CrnNo(rowLastno));
+    }
+
+    /**
+     * 鍦ㄦ暣浠撹疆璇㈡ā寮忎笅鎺ㄨ繘鍒颁笅涓�鍙板爢鍨涙満瀵瑰簲鐨勮捣濮嬫帓銆�
+     */
     private int getNextRun2CurrentRow(RowLastno rowLastno, int currentRow) {
         Integer rowSpan = getCrnRowSpan(rowLastno.getTypeId());
         if (rowSpan == null || rowSpan <= 0) {
@@ -518,6 +730,9 @@
         return currentRow + rowSpan;
     }
 
+    /**
+     * 浠庢寚瀹氳捣鐐瑰紑濮嬬敓鎴愬畬鏁寸殑鍫嗗灈鏈鸿疆璇㈤『搴忋��
+     */
     private List<Integer> getOrderedCrnNos(RowLastno rowLastno, Integer startCrnNo) {
         List<Integer> orderedCrnNos = new ArrayList<>();
         if (rowLastno == null) {
@@ -579,6 +794,26 @@
     }
 
     /**
+     * 鎸夆�滃彲鍏� + 鑷姩杩炵嚎 + 鏃犳晠闅溾�濆垽鏂爢鍨涙満鏄惁鍦ㄧ嚎鍙垎閰嶃��
+     */
+    private boolean isCrnActive(Integer crnNo) {
+        if (crnNo == null) {
+            return false;
+        }
+        BasCrnp basCrnp = basCrnpService.selectById(crnNo);
+        if (Cools.isEmpty(basCrnp)) {
+            return false;
+        }
+        if (!"Y".equalsIgnoreCase(basCrnp.getInEnable())) {
+            return false;
+        }
+        if (basCrnp.getCrnSts() == null || basCrnp.getCrnSts() != 3) {
+            return false;
+        }
+        return basCrnp.getCrnErr() == null || basCrnp.getCrnErr() == 0;
+    }
+
+    /**
      * 鍒ゆ柇鏌愬彴鍫嗗灈鏈烘槸鍚﹀彲浠ュ弬涓� run2 鍏ュ簱鎵句綅銆�
      *
      * routeRequired=true:
@@ -593,20 +828,7 @@
     }
 
     private boolean canRun2CrnAcceptPakin(RowLastno rowLastno, Integer staDescId, Integer sourceStaNo, Integer crnNo, boolean routeRequired) {
-        if (crnNo == null) {
-            return false;
-        }
-        BasCrnp basCrnp = basCrnpService.selectById(crnNo);
-        if (Cools.isEmpty(basCrnp)) {
-            return false;
-        }
-        if (!"Y".equals(basCrnp.getInEnable())) {
-            return false;
-        }
-        if (basCrnp.getCrnSts() != null && basCrnp.getCrnSts() != 3) {
-            return false;
-        }
-        if (basCrnp.getCrnErr() != null && basCrnp.getCrnErr() != 0) {
+        if (!isCrnActive(crnNo)) {
             return false;
         }
         if (!routeRequired) {
@@ -619,11 +841,7 @@
                 .eq("type_no", staDescId)
                 .eq("stn_no", sourceStaNo)
                 .eq("crn_no", crnNo));
-        if (Cools.isEmpty(staDesc)) {
-            return false;
-        }
-        BasDevp targetSta = basDevpService.selectById(staDesc.getCrnStn());
-        return !Cools.isEmpty(targetSta) && "Y".equals(targetSta.getAutoing());
+        return !Cools.isEmpty(staDesc) && !Cools.isEmpty(staDesc.getCrnStn());
     }
 
     /**
@@ -662,6 +880,33 @@
         return nextRow == null ? getNextRun2CurrentRow(rowLastno, currentRow) : nextRow;
     }
 
+    private Run2Cursor getNextRun2Cursor(RowLastno rowLastno, List<Integer> runnableCrnNos, Integer selectedCrnNo, int currentRow) {
+        if (rowLastno == null) {
+            return null;
+        }
+        Integer nextCrnNo = getNextRun2CurrentCrnNo(rowLastno, runnableCrnNos, selectedCrnNo);
+        Integer nextRow = nextCrnNo == null ? null : getCrnStartRow(rowLastno, nextCrnNo);
+        if (nextRow == null) {
+            int baseRow = currentRow == 0 ? (rowLastno.getsRow() == null ? 1 : rowLastno.getsRow()) : currentRow;
+            nextRow = getNextRun2CurrentRow(rowLastno, baseRow);
+            nextCrnNo = resolveRun2CrnNoByCurrentRow(rowLastno, nextRow);
+        }
+        return new Run2Cursor(nextRow, nextCrnNo);
+    }
+
+    private void updateRun2Cursor(RowLastno rowLastno, List<Integer> runnableCrnNos, Integer selectedCrnNo, int currentRow) {
+        if (rowLastno == null) {
+            return;
+        }
+        Run2Cursor nextCursor = getNextRun2Cursor(rowLastno, runnableCrnNos, selectedCrnNo, currentRow);
+        if (nextCursor == null) {
+            return;
+        }
+        rowLastno.setCurrentRow(nextCursor.currentRow);
+        rowLastno.setCurrentCrnNo(nextCursor.currentCrnNo);
+        rowLastnoService.updateById(rowLastno);
+    }
+
     /**
      * 鏋勯�犵┖鎵樼洏璺ㄥ簱鍖烘悳绱㈤『搴忥細
      * 鍏堝綋鍓嶅簱鍖猴紝鍐嶄緷娆¤ˉ瓒冲叾瀹冨簱鍖猴紝閬垮厤閲嶅銆�
@@ -675,6 +920,124 @@
             areaOrder.add(area);
         }
         return new ArrayList<>(areaOrder);
+    }
+
+    /**
+     * 棰勮 run2 褰撳墠浼氬弬涓庣殑搴撳尯銆佸爢鍨涙満椤哄簭鍜屾繁娴呮帓鐢诲儚锛屼笉钀戒换鍔℃。銆�
+     */
+    public Map<String, Object> previewRun2Allocation(BasCrnDepthRuleRuntimePreviewParam param) {
+        if (param == null || param.getStaDescId() == null || param.getSourceStaNo() == null) {
+            throw new CoolException("璺緞ID鍜屾簮绔欎笉鑳戒负绌�");
+        }
+        FindLocNoAttributeVo findLocNoAttributeVo = new FindLocNoAttributeVo();
+        findLocNoAttributeVo.setMatnr(param.getMatnr());
+        findLocNoAttributeVo.setOutArea(param.getOutArea());
+
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1(param.getLocType1());
+        locTypeDto.setLocType2(param.getLocType2());
+        locTypeDto.setLocType3(param.getLocType3());
+        locTypeDto = normalizeLocTypeDto(param.getStaDescId(), findLocNoAttributeVo, locTypeDto);
+
+        Integer whsType = Utils.GetWhsType(param.getSourceStaNo());
+        RowLastno rowLastno = rowLastnoService.selectById(whsType);
+        if (Cools.isEmpty(rowLastno)) {
+            throw new CoolException("鏈壘鍒颁粨搴撹疆璇㈣鍒�");
+        }
+        RowLastnoType rowLastnoType = rowLastnoTypeService.selectById(rowLastno.getTypeId());
+        Integer preferredArea = resolvePreferredArea(param.getSourceStaNo(), findLocNoAttributeVo);
+        boolean emptyPalletRequest = isEmptyPalletRequest(param.getStaDescId(), findLocNoAttributeVo);
+
+        Map<String, Object> result = new HashMap<String, Object>();
+        result.put("whsType", whsType);
+        result.put("preferredArea", preferredArea);
+        result.put("emptyPallet", emptyPalletRequest);
+        result.put("locType", locTypeDto);
+        result.put("stationPriorityEntries", Utils.getStationStorageAreaName(
+                param.getSourceStaNo(),
+                locTypeDto == null || locTypeDto.getLocType1() == null ? null : locTypeDto.getLocType1().intValue(),
+                findLocNoAttributeVo.getMatnr()));
+
+        List<Integer> orderedCrnNos = getOrderedCrnNos(rowLastno, resolveRun2CrnNo(rowLastno));
+        List<Integer> runnableCrnNos = getOrderedRunnableRun2CrnNos(rowLastno, param.getStaDescId(), param.getSourceStaNo(), orderedCrnNos);
+        result.put("orderedCrnNos", orderedCrnNos);
+        result.put("runnableCrnNos", runnableCrnNos);
+
+        if (emptyPalletRequest) {
+            List<Integer> areaSearchOrder = buildAreaSearchOrder(preferredArea);
+            List<Map<String, Object>> searchStages = new ArrayList<Map<String, Object>>();
+            for (LocTypeDto stageLocTypeDto : buildEmptyPalletSearchLocTypes(locTypeDto)) {
+                List<Map<String, Object>> areaPreviews = new ArrayList<Map<String, Object>>();
+                for (Integer area : areaSearchOrder) {
+                    RowLastno areaRowLastno = getAreaRowLastno(area, rowLastno);
+                    RowLastnoType areaRowLastnoType = rowLastnoTypeService.selectById(areaRowLastno.getTypeId());
+                    List<Integer> areaOrderedCrnNos = getOrderedCrnNos(areaRowLastno, resolveRun2CrnNo(areaRowLastno));
+                    List<Integer> areaRunnableCrnNos = getOrderedRunnableRun2CrnNos(areaRowLastno, param.getStaDescId(),
+                            param.getSourceStaNo(), areaOrderedCrnNos, false);
+                    Map<String, Object> areaItem = new HashMap<String, Object>();
+                    areaItem.put("area", area);
+                    areaItem.put("orderedCrnNos", areaOrderedCrnNos);
+                    areaItem.put("runnableCrnNos", areaRunnableCrnNos);
+                    areaItem.put("profiles", buildRun2ProfilePreview(areaRowLastno, areaRowLastnoType, areaOrderedCrnNos,
+                            param.getStaDescId(), param.getSourceStaNo(), stageLocTypeDto));
+                    areaPreviews.add(areaItem);
+                }
+                Map<String, Object> stageItem = new HashMap<String, Object>();
+                stageItem.put("stageCode", buildEmptyPalletStageCode(locTypeDto, stageLocTypeDto));
+                stageItem.put("locType", stageLocTypeDto);
+                stageItem.put("areaSearchOrder", areaSearchOrder);
+                stageItem.put("areaPreviews", areaPreviews);
+                searchStages.add(stageItem);
+            }
+            result.put("areaSearchOrder", areaSearchOrder);
+            result.put("searchStages", searchStages);
+            return result;
+        }
+
+        if (preferredArea != null) {
+            List<Integer> preferredCrnNos = filterCrnNosByRows(rowLastno, orderedCrnNos, getRun2AreaRows(preferredArea, rowLastno));
+            result.put("candidateCrnNos", preferredCrnNos);
+            result.put("profiles", buildRun2ProfilePreview(rowLastno, rowLastnoType, preferredCrnNos,
+                    param.getStaDescId(), param.getSourceStaNo(), locTypeDto));
+            result.put("areaMode", "preferred-area-only");
+            return result;
+        }
+
+        result.put("candidateCrnNos", orderedCrnNos);
+        result.put("profiles", buildRun2ProfilePreview(rowLastno, rowLastnoType, orderedCrnNos,
+                param.getStaDescId(), param.getSourceStaNo(), locTypeDto));
+        result.put("areaMode", "warehouse-round-robin");
+        return result;
+    }
+
+    /**
+     * 缁勮鏌愭壒鍫嗗灈鏈虹殑杩愯鏃剁敾鍍忛瑙堟暟鎹��
+     */
+    private List<Map<String, Object>> buildRun2ProfilePreview(RowLastno rowLastno, RowLastnoType rowLastnoType, List<Integer> crnNos,
+                                                              Integer staDescId, Integer sourceStaNo, LocTypeDto locTypeDto) {
+        List<Map<String, Object>> profiles = new ArrayList<Map<String, Object>>();
+        if (Cools.isEmpty(crnNos)) {
+            return profiles;
+        }
+        for (Integer crnNo : crnNos) {
+            CrnDepthRuleProfile profile = basCrnDepthRuleService.resolveProfile(rowLastno, crnNo, getCrnStartRow(rowLastno, crnNo));
+            Map<String, Object> item = new HashMap<String, Object>();
+            item.put("crnNo", crnNo);
+            item.put("active", isCrnActive(crnNo));
+            item.put("targetStaNo", resolveTargetStaNo(rowLastno, staDescId, sourceStaNo, crnNo));
+            item.put("layoutType", profile == null ? null : profile.getLayoutType());
+            item.put("source", profile == null ? null : profile.getSource());
+            item.put("searchRows", profile == null ? null : profile.getSearchRows());
+            item.put("shallowRows", profile == null ? null : profile.getShallowRows());
+            item.put("deepRows", profile == null ? null : profile.getDeepRows());
+            LocMast firstMatchLoc = findConfiguredEmptyLocForCrn(rowLastno, rowLastnoType, crnNo,
+                    getCrnStartRow(rowLastno, crnNo), locTypeDto);
+            item.put("firstMatchLocNo", firstMatchLoc == null ? null : firstMatchLoc.getLocNo());
+            item.put("assignableLocCount", countAssignableLocForCrn(rowLastno, rowLastnoType, crnNo,
+                    getCrnStartRow(rowLastno, crnNo) == null ? 0 : getCrnStartRow(rowLastno, crnNo), locTypeDto));
+            profiles.add(item);
+        }
+        return profiles;
     }
 
     /**
@@ -696,15 +1059,29 @@
      * 绌烘墭鐩� run2 涓撶敤鎼滅储閾捐矾銆�
      *
      * 鎵ц椤哄簭锛�
-     * 1. 鍏堟寜绔欑偣缁戝畾搴撳尯鎵� loc_type2=1銆�
-     * 2. 褰撳墠搴撳尯娌℃湁锛屽啀鎸夊叾瀹冨簱鍖虹户缁壘 loc_type2=1銆�
+     * 1. 鍏堟寜鍥哄畾瑙勬牸闃舵鏋勯�� 4 娈靛紡 locType 鍥為��椤哄簭銆�
+     * 2. 姣忎釜瑙勬牸闃舵閮芥寜鈥滃綋鍓嶅簱鍖� -> 鍏跺畠搴撳尯鈥濈殑椤哄簭鎼滅储銆�
      * 3. 姣忎釜搴撳尯鍐呴儴閮芥寜璇ュ簱鍖鸿嚜宸辩殑 rowLastno/currentRow 鍋氳疆璇㈠潎鍒嗐��
      *
      * 杩欓噷鏁呮剰涓嶅鐢ㄦ櫘閫� run2 鐨勨�滄帹鑽愭帓 -> 褰撳墠搴撳尯鎺� -> 鍏跺畠鎺掆�濋�昏緫锛�
      * 鍥犱负绌烘墭鐩樼殑涓氬姟鍙e緞宸茬粡鍒囨崲鎴愨�滄寜搴撳尯鎵惧爢鍨涙満鈥濓紝涓嶆槸鎸夋帹鑽愭帓鎵惧贩閬撱��
      */
+    private Run2AreaSearchResult findEmptyPalletRun2Loc(RowLastno defaultRowLastno, Integer staDescId, Integer sourceStaNo,
+                                                        StartupDto startupDto, Integer preferredArea, LocTypeDto locTypeDto) {
+        for (LocTypeDto stageLocTypeDto : buildEmptyPalletSearchLocTypes(locTypeDto)) {
+            String stageCode = buildEmptyPalletStageCode(locTypeDto, stageLocTypeDto);
+            Run2AreaSearchResult searchResult = findEmptyPalletRun2AreaLoc(defaultRowLastno, staDescId, sourceStaNo,
+                    startupDto, preferredArea, stageLocTypeDto, stageCode);
+            if (!Cools.isEmpty(searchResult) && !Cools.isEmpty(searchResult.locMast)) {
+                return searchResult;
+            }
+        }
+        return null;
+    }
+
     private Run2AreaSearchResult findEmptyPalletRun2AreaLoc(RowLastno defaultRowLastno, Integer staDescId, Integer sourceStaNo,
-                                                            StartupDto startupDto, Integer preferredArea, LocTypeDto locTypeDto) {
+                                                            StartupDto startupDto, Integer preferredArea, LocTypeDto locTypeDto,
+                                                            String stageCode) {
         for (Integer area : buildAreaSearchOrder(preferredArea)) {
             RowLastno areaRowLastno = getAreaRowLastno(area, defaultRowLastno);
             if (Cools.isEmpty(areaRowLastno)) {
@@ -718,14 +1095,13 @@
             List<Integer> orderedAreaCrnNos = getOrderedCrnNos(areaRowLastno, areaStartCrnNo);
             // 绌烘墭鐩樿法搴撳尯鏃跺彧绛涜澶囦富妗e拰鏁呴殰鐘舵�侊紝涓嶅己渚濊禆 sta_desc銆�
             List<Integer> runnableAreaCrnNos = getOrderedRunnableRun2CrnNos(areaRowLastno, staDescId, sourceStaNo, orderedAreaCrnNos, false);
-            List<Integer> candidateCrnNos = Cools.isEmpty(runnableAreaCrnNos) ? orderedAreaCrnNos : runnableAreaCrnNos;
-            if (Cools.isEmpty(candidateCrnNos)) {
+            if (Cools.isEmpty(runnableAreaCrnNos)) {
                 continue;
             }
-            LocMast locMast = findRun2EmptyLocByCrnNos(areaRowLastno, areaRowLastnoType, candidateCrnNos, locTypeDto,
-                    staDescId, sourceStaNo, startupDto, area, "empty-pallet-area-" + area, false);
+            LocMast locMast = findRun2EmptyLocByCrnNos(areaRowLastno, areaRowLastnoType, runnableAreaCrnNos, locTypeDto,
+                    staDescId, sourceStaNo, startupDto, area, stageCode + "-area-" + area, false);
             if (!Cools.isEmpty(locMast)) {
-                return new Run2AreaSearchResult(locMast, areaRowLastno, candidateCrnNos);
+                return new Run2AreaSearchResult(locMast, areaRowLastno, runnableAreaCrnNos);
             }
         }
         return null;
@@ -740,40 +1116,8 @@
             return;
         }
         RowLastno updateRowLastno = searchResult.rowLastno;
-        int updateCurRow = updateRowLastno.getCurrentRow() == null || updateRowLastno.getCurrentRow() == 0
-                ? (updateRowLastno.getsRow() == null ? 1 : updateRowLastno.getsRow())
-                : updateRowLastno.getCurrentRow();
-        updateCurRow = getNextRun2CurrentRow(updateRowLastno, searchResult.runnableCrnNos, locMast.getCrnNo(), updateCurRow);
-        updateRowLastno.setCurrentRow(updateCurRow);
-        rowLastnoService.updateById(updateRowLastno);
-    }
-
-    /**
-     * 鏅�� run2 鐨勬帹鑽愭帓浼樺厛闃舵銆�
-     *
-     * 鎺ㄨ崘鎺掑彧瀵规櫘閫氱墿鏂欑敓鏁堬紝绌烘墭鐩樺凡缁忓垏鎹㈡垚鈥滄寜搴撳尯杞鍫嗗灈鏈衡�濈殑瑙勫垯锛�
-     * 鍥犳杩欓噷浼氱洿鎺ヨ烦杩囩┖鎵樼洏璇锋眰锛岄伩鍏� row 鍙傛暟鎶婄┖鎵樼洏閲嶆柊甯﹀洖鏃ч�昏緫銆�
-     *
-     * 鍙﹀锛屽崟鎺掓帹鑽愪笉鍐嶄綔涓衡�滃己缁戝畾鍗曞彴鍫嗗灈鏈衡�濆鐞嗐��
-     * 鐜板満涓婃父缁忓父鍙紶涓�涓帹鑽愭帓锛屼緥濡� row=[1]锛屽鏋滆繖閲岀洿鎺ョ煭璺懡涓紝
-     * 婊℃澘浠诲姟灏变細闀挎湡鍘嬪湪鍚屼竴鍙板爢鍨涙満涓娿�傜幇鍦ㄥ彧鏈夊綋鎺ㄨ崘鎺掕兘瑕嗙洊澶氬彴鍫嗗灈鏈烘椂锛�
-     * 鎵嶆妸瀹冨綋浣滅湡姝g殑浼樺厛鍊欓�夐泦鍚堛��
-     */
-    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) || recommendCrnNos.size() <= 1) {
-            return null;
-        }
-        LocMast locMast = findRun2EmptyLocByCrnNos(rowLastno, rowLastnoType, recommendCrnNos, locTypeDto,
-                staDescId, sourceStaNo, startupDto, preferredArea, "recommend");
-        triedCrnNos.addAll(recommendCrnNos);
-        return locMast;
+        int currentRow = updateRowLastno.getCurrentRow() == null ? 0 : updateRowLastno.getCurrentRow();
+        updateRun2Cursor(updateRowLastno, searchResult.runnableCrnNos, locMast.getCrnNo(), currentRow);
     }
 
     /**
@@ -782,48 +1126,42 @@
      * 鎵ц椤哄簭锛�
      * 1. 鍏堢湅绔欑偣鏄惁閰嶇疆浜嗏�滃爢鍨涙満 + 搴撲綅绫诲瀷鈥濅紭鍏堢骇銆�
      * 2. 娌¢厤搴撳尯鏃讹紝鐩存帴鎸夊綋鍓� run2 杞椤哄簭鎵句綅銆�
-     * 3. 閰嶄簡搴撳尯鏃讹紝鍏堝湪褰撳墠搴撳尯鎵撅紝鍐嶅洖閫�鍒板叾瀹冨簱鍖恒��
+     * 3. 閰嶄簡搴撳尯鏃讹紝鍙湪褰撳墠搴撳尯鏌ユ壘锛屾櫘閫氭墭鐩樹笉璺ㄥ簱鍖哄厹搴曘��
      */
-    private LocMast findNormalRun2Loc(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer sourceStaNo,
-                                      Integer staDescId, FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto,
-                                      StartupDto startupDto, Integer preferredArea, List<Integer> orderedCrnNos,
-                                      List<Integer> triedCrnNos) {
+    private Run2SearchResult findNormalRun2Loc(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer sourceStaNo,
+                                               Integer staDescId, FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto,
+                                               StartupDto startupDto, Integer preferredArea, List<Integer> orderedCrnNos) {
         List<Map<String, Integer>> stationCrnLocTypes = Utils.getStationStorageAreaName(
                 sourceStaNo,
                 locTypeDto == null || locTypeDto.getLocType1() == null ? null : locTypeDto.getLocType1().intValue(),
                 findLocNoAttributeVo == null ? null : findLocNoAttributeVo.getMatnr());
         if (!Cools.isEmpty(stationCrnLocTypes)) {
+            // 绔欑偣浼樺厛绾у彧鏄�滀紭鍏堝皾璇曗�濓紝娌℃湁鍛戒腑鏃跺繀椤荤户缁蛋榛樿/搴撳尯鍥為��锛�
+            // 鍚﹀垯浼氭妸鈥滀紭鍏堝�欓�夋棤浣嶁�濊鍒ゆ垚鈥滄暣浠撴棤浣嶁�濄��
             LocMast locMast = findRun2EmptyLocByCrnLocTypeEntries(rowLastno, rowLastnoType, stationCrnLocTypes,
                     locTypeDto, staDescId, sourceStaNo, startupDto, preferredArea, "station-priority");
             if (!Cools.isEmpty(locMast)) {
-                return locMast;
+                return new Run2SearchResult(locMast, rowLastno,
+                        getOrderedRunnableRun2CrnNos(rowLastno, staDescId, sourceStaNo, extractCrnNos(stationCrnLocTypes)));
             }
         }
+        List<Integer> candidateCrnNos;
         if (preferredArea == null) {
-            List<Integer> defaultCrnNos = new ArrayList<>(orderedCrnNos);
-            defaultCrnNos.removeAll(triedCrnNos);
-            return findRun2EmptyLocByCrnNos(rowLastno, rowLastnoType, defaultCrnNos, locTypeDto,
-                    staDescId, sourceStaNo, startupDto, preferredArea, "default");
+            candidateCrnNos = new ArrayList<>(orderedCrnNos);
+        } else {
+            candidateCrnNos = filterCrnNosByRows(rowLastno, orderedCrnNos, getRun2AreaRows(preferredArea, rowLastno));
         }
-        List<Integer> preferredCrnNos = filterCrnNosByRows(rowLastno, orderedCrnNos, getRun2AreaRows(preferredArea, rowLastno));
-        preferredCrnNos.removeAll(triedCrnNos);
-        LocMast locMast = findRun2EmptyLocByCrnNos(rowLastno, rowLastnoType, preferredCrnNos, locTypeDto,
-                staDescId, sourceStaNo, startupDto, preferredArea, "preferred-area");
-        if (!Cools.isEmpty(locMast)) {
-            return locMast;
-        }
-        List<Integer> fallbackCrnNos = filterCrnNosByRows(rowLastno, orderedCrnNos, getRun2FallbackRows(rowLastno));
-        fallbackCrnNos.removeAll(triedCrnNos);
-        fallbackCrnNos.removeAll(preferredCrnNos);
-        return findRun2EmptyLocByCrnNos(rowLastno, rowLastnoType, fallbackCrnNos, locTypeDto,
-                staDescId, sourceStaNo, startupDto, preferredArea, "fallback-area");
+        List<Integer> runnableCrnNos = getOrderedRunnableRun2CrnNos(rowLastno, staDescId, sourceStaNo, candidateCrnNos);
+        LocMast locMast = findRun2EmptyLocByCrnNos(rowLastno, rowLastnoType, candidateCrnNos, locTypeDto,
+                staDescId, sourceStaNo, startupDto, preferredArea, preferredArea == null ? "default" : "preferred-area");
+        return new Run2SearchResult(locMast, rowLastno, runnableCrnNos);
     }
 
     /**
      * 鏅�氱墿鏂欏懡涓簱浣嶅悗锛屾部鐢� run2 鍘熸湁鐨勫叏浠撹疆璇㈡父鏍囨帹杩涙柟寮忋��
      */
     private void advanceNormalRun2Cursor(RowLastno rowLastno, int curRow) {
-        advanceNormalRun2Cursor(rowLastno, curRow, null, null);
+        updateRun2Cursor(rowLastno, null, null, curRow);
     }
 
     /**
@@ -833,19 +1171,12 @@
      * 婊℃澘浠诲姟涔熶細鍦ㄧ湡瀹炲彲浣滀笟鐨勫爢鍨涙満涔嬮棿杞锛屼笉浼氬洜涓虹悊璁哄爢鍨涙満鍙风殑绌烘礊鑰岄暱鏈熷洖钀藉埌鍚屼竴鍙般��
      */
     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;
-        if (!Cools.isEmpty(runnableCrnNos) && selectedCrnNo != null) {
-            updateCurRow = getNextRun2CurrentRow(rowLastno, runnableCrnNos, selectedCrnNo, updateCurRow);
-        } else {
-            updateCurRow = getNextRun2CurrentRow(rowLastno, updateCurRow);
-        }
-        rowLastno.setCurrentRow(updateCurRow);
-        rowLastnoService.updateById(rowLastno);
+        updateRun2Cursor(rowLastno, runnableCrnNos, selectedCrnNo, curRow);
     }
 
+    /**
+     * 鏍规嵁鎺掕寖鍥存妸鏁翠粨鍫嗗灈鏈洪『搴忚鍓负鏌愪釜搴撳尯鍐呯殑鍫嗗灈鏈洪泦鍚堛��
+     */
     private List<Integer> filterCrnNosByRows(RowLastno rowLastno, List<Integer> orderedCrnNos, List<Integer> rows) {
         if (Cools.isEmpty(rows)) {
             return new ArrayList<>(orderedCrnNos);
@@ -875,33 +1206,9 @@
         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;
-    }
-
+    /**
+     * 瑙f瀽褰撳墠婧愮珯鍒扮洰鏍囧爢鍨涙満鐨勫叆搴撶洰鏍囩珯鍙枫��
+     */
     private Integer resolveTargetStaNo(RowLastno rowLastno, Integer staDescId, Integer sourceStaNo, Integer crnNo) {
         if (!Utils.BooleanWhsTypeSta(rowLastno, staDescId)) {
             return null;
@@ -915,8 +1222,8 @@
             return null;
         }
         BasDevp staNo = basDevpService.selectById(staDesc.getCrnStn());
-        if (Cools.isEmpty(staNo) || !"Y".equals(staNo.getAutoing())) {
-            log.error("鐩爣绔檣}涓嶅彲鐢�", staDesc.getCrnStn());
+        if (Cools.isEmpty(staNo)) {
+            log.error("鐩爣绔檣}涓嶅瓨鍦�", staDesc.getCrnStn());
             return null;
         }
         return staNo.getDevNo();
@@ -959,44 +1266,60 @@
         List<Integer> routeBlockedCrns = new ArrayList<>();
         List<Integer> noEmptyCrns = new ArrayList<>();
         List<Integer> locTypeBlockedCrns = new ArrayList<>();
-        for (Integer candidateCrnNo : candidateCrnNos) {
-            if (candidateCrnNo == null || !basCrnpService.checkSiteError(candidateCrnNo, true)) {
-                crnErrorCrns.add(candidateCrnNo);
-                continue;
-            }
-            Integer targetStaNo = resolveTargetStaNo(rowLastno, staDescId, sourceStaNo, candidateCrnNo);
-            if (routeRequired && Utils.BooleanWhsTypeSta(rowLastno, staDescId) && targetStaNo == null) {
-                routeBlockedCrns.add(candidateCrnNo);
-                continue;
-            }
-            Wrapper<LocMast> openWrapper = new EntityWrapper<LocMast>()
-                    .eq("crn_no", candidateCrnNo)
-                    .eq("loc_sts", "O");
-            applyLocTypeFilters(openWrapper, locTypeDto, true);
-            openWrapper.orderBy("lev1").orderBy("bay1");
-            LocMast anyOpenLoc = locMastService.selectOne(openWrapper);
-            if (Cools.isEmpty(anyOpenLoc)) {
-                noEmptyCrns.add(candidateCrnNo);
-                continue;
-            }
-            Wrapper<LocMast> wrapper = new EntityWrapper<LocMast>()
-                    .eq("crn_no", candidateCrnNo)
-                    .eq("loc_sts", "O");
-            applyLocTypeFilters(wrapper, locTypeDto, true);
-            wrapper.orderBy("lev1").orderBy("bay1");
-            LocMast candidateLoc = locMastService.selectOne(wrapper);
-            if (Cools.isEmpty(candidateLoc) || (locTypeDto != null && !VersionUtils.locMoveCheckLocTypeComplete(candidateLoc, locTypeDto))) {
-                locTypeBlockedCrns.add(candidateCrnNo);
-                continue;
-            }
-            if (targetStaNo != null) {
-                startupDto.setStaNo(targetStaNo);
-            }
-            return candidateLoc;
-        }
-        logRun2NoMatch(stage, sourceStaNo, preferredArea, candidateCrnNos, locTypeDto, crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
-        return null;
+        return findRun2EmptyLocByCrnNosRecursively(rowLastno, rowLastnoType, candidateCrnNos, locTypeDto,
+                staDescId, sourceStaNo, startupDto, preferredArea, stage, routeRequired, 0,
+                crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
     }
+
+    private LocMast findRun2EmptyLocByCrnNosRecursively(RowLastno rowLastno, RowLastnoType rowLastnoType,
+                                                        List<Integer> candidateCrnNos, LocTypeDto locTypeDto,
+                                                        Integer staDescId, Integer sourceStaNo, StartupDto startupDto,
+                                                        Integer preferredArea, String stage, boolean routeRequired, int index,
+                                                        List<Integer> crnErrorCrns, List<Integer> routeBlockedCrns,
+                                                        List<Integer> noEmptyCrns, List<Integer> locTypeBlockedCrns) {
+        if (index >= candidateCrnNos.size()) {
+            logRun2NoMatch(stage, sourceStaNo, preferredArea, candidateCrnNos, locTypeDto,
+                    crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
+            return null;
+        }
+        Integer candidateCrnNo = candidateCrnNos.get(index);
+        if (!isCrnActive(candidateCrnNo)) {
+            crnErrorCrns.add(candidateCrnNo);
+            return findRun2EmptyLocByCrnNosRecursively(rowLastno, rowLastnoType, candidateCrnNos, locTypeDto,
+                    staDescId, sourceStaNo, startupDto, preferredArea, stage, routeRequired, index + 1,
+                    crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
+        }
+        Integer targetStaNo = routeRequired ? resolveTargetStaNo(rowLastno, staDescId, sourceStaNo, candidateCrnNo) : null;
+        if (routeRequired && Utils.BooleanWhsTypeSta(rowLastno, staDescId) && targetStaNo == null) {
+            routeBlockedCrns.add(candidateCrnNo);
+            return findRun2EmptyLocByCrnNosRecursively(rowLastno, rowLastnoType, candidateCrnNos, locTypeDto,
+                    staDescId, sourceStaNo, startupDto, preferredArea, stage, routeRequired, index + 1,
+                    crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
+        }
+        Integer preferredNearRow = getCrnStartRow(rowLastno, candidateCrnNo);
+        LocMast candidateLoc = findConfiguredEmptyLocForCrn(rowLastno, rowLastnoType, candidateCrnNo,
+                preferredNearRow, locTypeDto);
+        if (Cools.isEmpty(candidateLoc)) {
+            int availableLocCount = countAssignableLocForCrn(rowLastno, rowLastnoType, candidateCrnNo,
+                    preferredNearRow == null ? 0 : preferredNearRow, locTypeDto);
+            if (availableLocCount <= 0) {
+                noEmptyCrns.add(candidateCrnNo);
+            } else {
+                locTypeBlockedCrns.add(candidateCrnNo);
+            }
+            return findRun2EmptyLocByCrnNosRecursively(rowLastno, rowLastnoType, candidateCrnNos, locTypeDto,
+                    staDescId, sourceStaNo, startupDto, preferredArea, stage, routeRequired, index + 1,
+                    crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
+        }
+        if (targetStaNo != null) {
+            startupDto.setStaNo(targetStaNo);
+        }
+        return candidateLoc;
+    }
+
+    /**
+     * 鎸夌珯鐐归厤缃殑鈥滃爢鍨涙満 + 楂樹綆绫诲瀷鈥濅紭鍏堢骇鎵句綅锛屽苟濂楃敤缁熶竴鍧囪 绛栫暐銆�
+     */
     private LocMast findRun2EmptyLocByCrnLocTypeEntries(RowLastno rowLastno, RowLastnoType rowLastnoType,
                                                         List<Map<String, Integer>> crnLocTypeEntries, LocTypeDto locTypeDto,
                                                         Integer staDescId, Integer sourceStaNo, StartupDto startupDto,
@@ -1031,7 +1354,7 @@
         Integer candidateCrnNo = crnLocTypeEntry == null ? null : crnLocTypeEntry.get("crnNo");
         Short candidateLocType1 = crnLocTypeEntry == null || crnLocTypeEntry.get("locType1") == null
                 ? null : crnLocTypeEntry.get("locType1").shortValue();
-        if (candidateCrnNo == null || !basCrnpService.checkSiteError(candidateCrnNo, true)) {
+        if (!isCrnActive(candidateCrnNo)) {
             crnErrorCrns.add(candidateCrnNo);
             return findRun2EmptyLocByCrnLocTypeEntriesRecursively(rowLastno, rowLastnoType, crnLocTypeEntries, locTypeDto,
                     staDescId, sourceStaNo, startupDto, preferredArea, stage, index + 1, candidateCrnNos,
@@ -1045,15 +1368,17 @@
                     crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
         }
         LocTypeDto searchLocTypeDto = buildRun2SearchLocTypeDto(locTypeDto, candidateLocType1);
-        LocMast candidateLoc = findRun2OrderedEmptyLocByCrnLocType(rowLastnoType, candidateCrnNo, candidateLocType1, searchLocTypeDto);
+        Integer preferredNearRow = getCrnStartRow(rowLastno, candidateCrnNo);
+        LocMast candidateLoc = findConfiguredEmptyLocForCrn(rowLastno, rowLastnoType, candidateCrnNo,
+                preferredNearRow, searchLocTypeDto);
         if (Cools.isEmpty(candidateLoc)) {
-            noEmptyCrns.add(candidateCrnNo);
-            return findRun2EmptyLocByCrnLocTypeEntriesRecursively(rowLastno, rowLastnoType, crnLocTypeEntries, locTypeDto,
-                    staDescId, sourceStaNo, startupDto, preferredArea, stage, index + 1, candidateCrnNos,
-                    crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
-        }
-        if (searchLocTypeDto != null && !VersionUtils.locMoveCheckLocTypeComplete(candidateLoc, searchLocTypeDto)) {
-            locTypeBlockedCrns.add(candidateCrnNo);
+            int availableLocCount = countAssignableLocForCrn(rowLastno, rowLastnoType, candidateCrnNo,
+                    preferredNearRow == null ? 0 : preferredNearRow, searchLocTypeDto);
+            if (availableLocCount <= 0) {
+                noEmptyCrns.add(candidateCrnNo);
+            } else {
+                locTypeBlockedCrns.add(candidateCrnNo);
+            }
             return findRun2EmptyLocByCrnLocTypeEntriesRecursively(rowLastno, rowLastnoType, crnLocTypeEntries, locTypeDto,
                     staDescId, sourceStaNo, startupDto, preferredArea, stage, index + 1, candidateCrnNos,
                     crnErrorCrns, routeBlockedCrns, noEmptyCrns, locTypeBlockedCrns);
@@ -1064,6 +1389,9 @@
         return candidateLoc;
     }
 
+    /**
+     * 浠庣珯鐐逛紭鍏堢骇閰嶇疆涓娊鍙栦笉閲嶅鐨勫爢鍨涙満椤哄簭銆�
+     */
     private List<Integer> extractCrnNos(List<Map<String, Integer>> crnLocTypeEntries) {
         LinkedHashSet<Integer> orderedCrnNos = new LinkedHashSet<>();
         if (Cools.isEmpty(crnLocTypeEntries)) {
@@ -1078,6 +1406,9 @@
         return new ArrayList<>(orderedCrnNos);
     }
 
+    /**
+     * 鐢ㄧ珯鐐逛紭鍏堢骇閲岀殑 locType1 瑕嗙洊鏈鏌ヨ鐨勯珮搴︽潯浠躲��
+     */
     private LocTypeDto buildRun2SearchLocTypeDto(LocTypeDto locTypeDto, Short candidateLocType1) {
         if (locTypeDto == null && candidateLocType1 == null) {
             return null;
@@ -1095,6 +1426,9 @@
         return searchLocTypeDto;
     }
 
+    /**
+     * 鎸夌珯鐐逛紭鍏堥厤缃洿鎺ユ煡鏌愬彴鍫嗗灈鏈轰笂鐨勭涓�涓彲鐢ㄧ┖搴撲綅銆�
+     */
     private LocMast findRun2OrderedEmptyLocByCrnLocType(RowLastnoType rowLastnoType, Integer candidateCrnNo,
                                                         Short candidateLocType1, LocTypeDto locTypeDto) {
         if (candidateCrnNo == null) {
@@ -1232,27 +1566,252 @@
         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;
+    /**
+     * 鎺ㄥ搴撲綅涓绘。鏌ヨ鏃跺簲浣跨敤鐨� whsType銆�
+     */
+    private Long resolveLocWhsType(RowLastno rowLastno, RowLastnoType rowLastnoType) {
+        if (rowLastno != null && rowLastno.getWhsType() != null) {
+            return rowLastno.getWhsType().longValue();
         }
-        return locMastService.selectCount(new EntityWrapper<LocMast>()
-                .in("row1", searchRows)
-                .eq("loc_sts", "O")
-                .eq("whs_type", rowLastnoType.getType().longValue()));
+        if (rowLastnoType != null && rowLastnoType.getType() != null) {
+            return rowLastnoType.getType().longValue();
+        }
+        return null;
     }
 
-    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;
+    /**
+     * 鍒ゆ柇搴撲綅鏄惁婊¤冻鏈瑙勬牸绾︽潫銆�
+     */
+    private boolean matchesLocType(LocMast locMast, LocTypeDto locTypeDto) {
+        if (locMast != null && isFullPalletLocTypeSearch(locTypeDto) && locMast.getLocType2() != null
+                && locMast.getLocType2() == 1) {
+            return false;
+        }
+        return locTypeDto == null || VersionUtils.locMoveCheckLocTypeComplete(locMast, locTypeDto);
+    }
+
+    /**
+     * 鏌ヨ鏌愪竴鎺掍笂鐨勬墍鏈夌┖搴撲綅锛屽苟鎸夊崟浼�/鍙屼几绛栫暐鎺掑簭銆�
+     */
+    private List<LocMast> findOpenLocsByRow(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer row,
+                                            Integer crnNo, LocTypeDto locTypeDto, boolean singleExtension) {
+        List<LocMast> result = new ArrayList<LocMast>();
+        if (row == null) {
+            return result;
         }
         Wrapper<LocMast> wrapper = new EntityWrapper<LocMast>()
-                .in("row1", searchRows)
+//                .eq("row1", row)
                 .eq("loc_sts", "O");
+        if (crnNo != null) {
+            wrapper.eq("crn_no", crnNo);
+        }
         applyLocTypeFilters(wrapper, locTypeDto, true);
-        return locMastService.selectCount(wrapper);
+        if (singleExtension) {
+            wrapper.orderBy("lev1", true).orderBy("bay1", true);
+        } else {
+            wrapper.orderBy("lev1", true).orderBy("bay1", true);
+        }
+        List<LocMast> locMasts = locMastService.selectList(wrapper);
+        for (LocMast locMast : locMasts) {
+            if (matchesLocType(locMast, locTypeDto)) {
+                result.add(locMast);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 鎸夋帓銆佸垪銆佸眰绮剧‘瀹氫綅鏌愪釜搴撲綅浣嶇疆涓婄殑鐘舵�佽褰曘��
+     */
+    private LocMast findLocByPosition(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer crnNo,
+                                      Integer row, Integer bay, Integer lev, String... statuses) {
+        if (row == null || bay == null || lev == null) {
+            return null;
+        }
+        EntityWrapper<LocMast> wrapper = new EntityWrapper<LocMast>();
+        wrapper.eq("row1", row);
+        wrapper.eq("bay1", bay);
+        wrapper.eq("lev1", lev);
+        if (crnNo != null) {
+            wrapper.eq("crn_no", crnNo);
+        }
+        Long whsType = resolveLocWhsType(rowLastno, rowLastnoType);
+        if (whsType != null) {
+            wrapper.eq("whs_type", whsType);
+        }
+        if (statuses != null && statuses.length > 0) {
+            if (statuses.length == 1) {
+                wrapper.eq("loc_sts", statuses[0]);
+            } else {
+                wrapper.in("loc_sts", statuses);
+            }
+        }
+        return locMastService.selectOne(wrapper);
+    }
+
+    /**
+     * 鍦ㄤ竴瀵规祬鎺�/娣辨帓涔嬮棿閫夋嫨鐪熸鍙姇鏀剧殑鐩爣搴撲綅銆�
+     */
+    private LocMast findPairAssignableLoc(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer crnNo,
+                                          Integer shallowRow, Integer deepRow, LocTypeDto locTypeDto) {
+        List<LocMast> shallowOpenLocs = findOpenLocsByRow(rowLastno, rowLastnoType, shallowRow, crnNo, locTypeDto, false);
+        if (Cools.isEmpty(shallowOpenLocs)) {
+            return null;
+        }
+        if (deepRow == null) {
+            return shallowOpenLocs.get(0);
+        }
+        for (LocMast shallowLoc : shallowOpenLocs) {
+            LocMast deepOpenLoc = findLocByPosition(rowLastno, rowLastnoType, crnNo, deepRow, shallowLoc.getBay1(), shallowLoc.getLev1(), "O");
+            if (!Cools.isEmpty(deepOpenLoc) && matchesLocType(deepOpenLoc, locTypeDto)) {
+                return deepOpenLoc;
+            }
+        }
+        for (LocMast shallowLoc : shallowOpenLocs) {
+            LocMast deepBlockingLoc = findLocByPosition(rowLastno, rowLastnoType, crnNo, deepRow, shallowLoc.getBay1(), shallowLoc.getLev1(), "F", "D");
+            if (!Cools.isEmpty(deepBlockingLoc)) {
+                return shallowLoc;
+            }
+            if (findLocByPosition(rowLastno, rowLastnoType, crnNo, deepRow, shallowLoc.getBay1(), shallowLoc.getLev1()) == null) {
+                return shallowLoc;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鎸夋煇鍙板爢鍨涙満鐨勬繁娴呮帓鐢诲儚鎼滅储绗竴涓彲鍒嗛厤绌哄簱浣嶃��
+     */
+    private LocMast findConfiguredEmptyLocForCrn(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer crnNo,
+                                                 Integer preferredNearRow, LocTypeDto locTypeDto) {
+        if (rowLastno == null || crnNo == null) {
+            return null;
+        }
+        CrnDepthRuleProfile profile = basCrnDepthRuleService.resolveProfile(rowLastno, crnNo, preferredNearRow);
+        if (profile == null || Cools.isEmpty(profile.getSearchRows())) {
+            return null;
+        }
+        LinkedHashSet<Integer> processedShallowRows = new LinkedHashSet<Integer>();
+        boolean singleExtension = profile.isSingleExtension();
+        for (Integer searchRow : profile.getSearchRows()) {
+            if (searchRow == null) {
+                continue;
+            }
+            if (!singleExtension) {
+                if (profile.isShallowRow(searchRow)) {
+                    if (!processedShallowRows.add(searchRow)) {
+                        continue;
+                    }
+                    LocMast candidateLoc = findPairAssignableLoc(rowLastno, rowLastnoType, crnNo, searchRow,
+                            profile.getPairedDeepRow(searchRow), locTypeDto);
+                    if (!Cools.isEmpty(candidateLoc)) {
+                        return candidateLoc;
+                    }
+                    continue;
+                }
+                if (profile.isDeepRow(searchRow)) {
+                    Integer shallowRow = profile.getPairedShallowRow(searchRow);
+                    if (shallowRow != null) {
+                        if (!processedShallowRows.add(shallowRow)) {
+                            continue;
+                        }
+                        LocMast candidateLoc = findPairAssignableLoc(rowLastno, rowLastnoType, crnNo, shallowRow,
+                                searchRow, locTypeDto);
+                        if (!Cools.isEmpty(candidateLoc)) {
+                            return candidateLoc;
+                        }
+                        continue;
+                    }
+                }
+            }
+            List<LocMast> locMasts = findOpenLocsByRow(rowLastno, rowLastnoType, searchRow, crnNo, locTypeDto, singleExtension);
+            if (!Cools.isEmpty(locMasts)) {
+                return locMasts.get(0);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 缁熻鏌愬彴鍫嗗灈鏈哄綋鍓嶇敾鍍忎笅鍙弬涓庡垎閰嶇殑绌哄簱浣嶆暟閲忋��
+     */
+    private int countAssignableLocForCrn(RowLastno rowLastno, RowLastnoType rowLastnoType, int crnNo, int preferredNearRow, LocTypeDto locTypeDto) {
+        CrnDepthRuleProfile profile = basCrnDepthRuleService.resolveProfile(rowLastno, crnNo, preferredNearRow);
+        if (profile == null || Cools.isEmpty(profile.getSearchRows())) {
+            return 0;
+        }
+        int count = 0;
+        LinkedHashSet<Integer> processedShallowRows = new LinkedHashSet<Integer>();
+        boolean singleExtension = profile.isSingleExtension();
+        for (Integer searchRow : profile.getSearchRows()) {
+            if (searchRow == null) {
+                continue;
+            }
+            if (!singleExtension) {
+                if (profile.isShallowRow(searchRow)) {
+                    if (!processedShallowRows.add(searchRow)) {
+                        continue;
+                    }
+                    count += countAssignablePairLocs(rowLastno, rowLastnoType, crnNo, searchRow,
+                            profile.getPairedDeepRow(searchRow), locTypeDto);
+                    continue;
+                }
+                if (profile.isDeepRow(searchRow)) {
+                    Integer shallowRow = profile.getPairedShallowRow(searchRow);
+                    if (shallowRow != null) {
+                        if (!processedShallowRows.add(shallowRow)) {
+                            continue;
+                        }
+                        count += countAssignablePairLocs(rowLastno, rowLastnoType, crnNo, shallowRow, searchRow, locTypeDto);
+                        continue;
+                    }
+                }
+            }
+            count += findOpenLocsByRow(rowLastno, rowLastnoType, searchRow, crnNo, locTypeDto, singleExtension).size();
+        }
+        return count;
+    }
+
+    /**
+     * 缁熻涓�瀵规祬鎺�/娣辨帓涓婄殑鍙垎閰嶅簱浣嶆暟閲忋��
+     */
+    private int countAssignablePairLocs(RowLastno rowLastno, RowLastnoType rowLastnoType, Integer crnNo,
+                                        Integer shallowRow, Integer deepRow, LocTypeDto locTypeDto) {
+        List<LocMast> shallowOpenLocs = findOpenLocsByRow(rowLastno, rowLastnoType, shallowRow, crnNo, locTypeDto, false);
+        if (Cools.isEmpty(shallowOpenLocs)) {
+            return 0;
+        }
+        if (deepRow == null) {
+            return shallowOpenLocs.size();
+        }
+        int count = 0;
+        for (LocMast shallowLoc : shallowOpenLocs) {
+            LocMast deepOpenLoc = findLocByPosition(rowLastno, rowLastnoType, crnNo, deepRow, shallowLoc.getBay1(), shallowLoc.getLev1(), "O");
+            if (!Cools.isEmpty(deepOpenLoc) && matchesLocType(deepOpenLoc, locTypeDto)) {
+                count++;
+                continue;
+            }
+            LocMast deepBlockingLoc = findLocByPosition(rowLastno, rowLastnoType, crnNo, deepRow, shallowLoc.getBay1(), shallowLoc.getLev1(), "F", "D");
+            if (!Cools.isEmpty(deepBlockingLoc) ||
+                    findLocByPosition(rowLastno, rowLastnoType, crnNo, deepRow, shallowLoc.getBay1(), shallowLoc.getLev1()) == null) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * 缁熻鏌愬彴鍫嗗灈鏈烘墍鏈夊彲鐢ㄧ┖搴撲綅鏁伴噺锛屼笉闄勫甫瑙勬牸杩囨护銆�
+     */
+    private int countAvailableLocForCrn(RowLastno rowLastno, RowLastnoType rowLastnoType, int crnNo, int preferredNearRow) {
+        return countAssignableLocForCrn(rowLastno, rowLastnoType, crnNo, preferredNearRow, null);
+    }
+
+    /**
+     * 缁熻鏌愬彴鍗曚几鍫嗗灈鏈哄湪褰撳墠瑙勬牸绾︽潫涓嬬殑鍙敤绌哄簱浣嶆暟閲忋��
+     */
+    private int countAvailableSingleExtensionLocForCrn(RowLastno rowLastno, RowLastnoType rowLastnoType, int crnNo, int preferredNearRow, LocTypeDto locTypeDto) {
+        return countAssignableLocForCrn(rowLastno, rowLastnoType, crnNo, preferredNearRow, locTypeDto);
     }
 
     private Optional<CrnRowInfo> findBalancedSingleExtensionCrnAndNearRow(RowLastno rowLastno, int curRow, int crnNumber, int times,
@@ -1316,35 +1875,20 @@
         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;
+        CrnDepthRuleProfile profile = basCrnDepthRuleService.resolveProfile(rowLastno, crnNo, preferredNearRow);
+        if (profile == null || Cools.isEmpty(profile.getSearchRows())) {
+            return new ArrayList<Integer>();
         }
-
-        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;
+        return new ArrayList<Integer>(profile.getSearchRows());
     }
 
+    /**
+     * 鎸夊簱鍨嬭繑鍥炴瘡鍙板爢鍨涙満鍗犵敤鐨勬帓璺ㄥ害銆�
+     */
     private Integer getCrnRowSpan(Integer typeId) {
         if (typeId == null) {
             return null;
@@ -1359,6 +1903,9 @@
         }
     }
 
+    /**
+     * 鍚戞悳绱㈡帓鍒楄〃杩藉姞涓�涓悎娉曚笖涓嶉噸澶嶇殑鎺掑彿銆�
+     */
     private void addSearchRow(List<Integer> searchRows, Integer row, RowLastno rowLastno) {
         if (row == null) {
             return;
@@ -1371,67 +1918,16 @@
         }
     }
 
+    /**
+     * run/run2 鏍囧噯鍫嗗灈鏈虹粺涓�鐨勭┖搴撲綅鏌ヨ鍏ュ彛銆�
+     */
     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;
+        return findConfiguredEmptyLocForCrn(rowLastno, rowLastnoType, crnNo, nearRow, locTypeDto);
     }
 
+    /**
+     * 鏋勯�犲彧鎻愬崌 locType1 鐨勫悜涓婂吋瀹硅鏍笺��
+     */
     private LocTypeDto buildUpwardCompatibleLocTypeDto(LocTypeDto locTypeDto) {
         if (locTypeDto == null || locTypeDto.getLocType1() == null || locTypeDto.getLocType1() >= 2) {
             return null;
@@ -1445,40 +1941,12 @@
     }
 
     /**
-     * 绌烘墭鐩樺吋瀹瑰洖閫�瑙勫垯锛�
-     * 褰撻杞� loc_type2=1 娌℃湁鍛戒腑绌哄簱浣嶆椂锛屽厑璁搁��鍖栧埌楂樹綅绌哄簱浣� loc_type1=2銆�
-     *
-     * 娉ㄦ剰杩欓噷涓嶄細缁х画淇濈暀 locType2=1銆�
-     * 涔熷氨鏄绗簩杞槸鈥滈珮浣嶄紭鍏堝厹搴曗�濓紝鑰屼笉鏄�滈珮浣嶇獎搴撲綅鍏滃簳鈥濄��
-     * 杩欐槸鎸夌収鐜板満鏈�鏂板彛寰勫疄鐜扮殑锛氱獎搴撲綅浼樺厛锛岀獎搴撲綅娌℃湁鏃跺啀鎵鹃珮浣嶇┖搴撲綅銆�
-     */
-    private LocTypeDto buildEmptyPalletCompatibleLocTypeDto(LocTypeDto locTypeDto) {
-        if (locTypeDto == null || locTypeDto.getLocType2() == null || locTypeDto.getLocType2() != 1) {
-            return null;
-        }
-        LocTypeDto compatibleLocTypeDto = new LocTypeDto();
-        compatibleLocTypeDto.setLocType1((short) 2);
-        compatibleLocTypeDto.setLocType3(locTypeDto.getLocType3());
-        compatibleLocTypeDto.setSiteId(locTypeDto.getSiteId());
-        return compatibleLocTypeDto;
-    }
-
-    /**
      * 缁熶竴灏佽鎵惧簱浣嶅け璐ュ悗鐨勫吋瀹归噸璇曢『搴忋��
      *
-     * 绌烘墭鐩橈細
-     * 鍏堟寜 loc_type2=1 鏌ユ壘锛屽け璐ュ悗閫�鍒� loc_type1=2銆�
-     *
-     * 闈炵┖鎵樼洏锛�
-     * 缁存寔鍘熻鍒欙紝浣庝綅澶辫触鍚庡啀鍚戦珮浣嶅吋瀹广��
+     * 鍏煎瑙勫垯鍥哄畾涓猴細
+     * 鍙厑璁� loc_type1 浣庝綅鍚戦珮浣嶅吋瀹癸紝loc_type2/loc_type3 涓嶅弬涓庢弧鎵樻壘浣嶃��
      */
     private LocTypeDto buildRetryCompatibleLocTypeDto(Integer staDescId, FindLocNoAttributeVo findLocNoAttributeVo, LocTypeDto locTypeDto) {
-        if (isEmptyPalletRequest(staDescId, findLocNoAttributeVo)) {
-            LocTypeDto emptyPalletCompatibleLocTypeDto = buildEmptyPalletCompatibleLocTypeDto(locTypeDto);
-            if (emptyPalletCompatibleLocTypeDto != null) {
-                return emptyPalletCompatibleLocTypeDto;
-            }
-        }
         return buildUpwardCompatibleLocTypeDto(locTypeDto);
     }
 
@@ -1715,16 +2183,16 @@
      * 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) {
 
         int crnNo = 0;
         int nearRow = 0;
-        int curRow = 0;
-        int rowCount = 0;
         LocMast locMast = null;
 
         StartupDto startupDto = new StartupDto();
@@ -1737,33 +2205,25 @@
         if (Cools.isEmpty(rowLastnoType)) {
             throw new CoolException("鏁版嵁寮傚父锛岃鑱旂郴绠$悊鍛�===銆嬪簱浣嶈鍒欑被鍨嬫湭鐭�");
         }
-        int crnNumber = resolveCrnCount(rowLastno);
-        rowCount = crnNumber;
-
-        curRow = rowLastno.getCurrentRow();
+        int curRow = rowLastno.getCurrentRow() == null ? 0 : rowLastno.getCurrentRow();
         crnNo = resolveRun2CrnNo(rowLastno);
         Integer preferredArea = findLocNoAttributeVo.getOutArea();
         boolean emptyPalletRequest = isEmptyPalletRequest(staDescId, findLocNoAttributeVo);
         Run2AreaSearchResult emptyPalletAreaSearchResult = null;
+        Run2SearchResult normalRun2SearchResult = 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) {
+            emptyPalletAreaSearchResult = findEmptyPalletRun2Loc(rowLastno, staDescId, sourceStaNo, startupDto, preferredArea, locTypeDto);
+            if (!Cools.isEmpty(emptyPalletAreaSearchResult)) {
+                locMast = emptyPalletAreaSearchResult.locMast;
+            }
+        } else {
+            normalRun2SearchResult = findNormalRun2Loc(rowLastno, rowLastnoType, sourceStaNo, staDescId, findLocNoAttributeVo,
+                    locTypeDto, startupDto, preferredArea, orderedCrnNos);
+            if (normalRun2SearchResult != null) {
+                locMast = normalRun2SearchResult.locMast;
             }
         }
 
@@ -1773,22 +2233,25 @@
         }
         if (emptyPalletRequest) {
             advanceEmptyPalletRun2Cursor(emptyPalletAreaSearchResult, locMast);
-        } else {
-            advanceNormalRun2Cursor(rowLastno, curRow, orderedRunnableCrnNos, locMast == null ? null : locMast.getCrnNo());
+        } else if (!Cools.isEmpty(locMast)) {
+            List<Integer> cursorCrnNos = normalRun2SearchResult == null || Cools.isEmpty(normalRun2SearchResult.runnableCrnNos)
+                    ? orderedRunnableCrnNos
+                    : normalRun2SearchResult.runnableCrnNos;
+            advanceNormalRun2Cursor(rowLastno, curRow, cursorCrnNos, locMast.getCrnNo());
         }
 
         if (Cools.isEmpty(locMast) || !locMast.getLocSts().equals("O")) {
-            if (!emptyPalletRequest && times < rowCount * 2) {
-                times = times + 1;
-                return getLocNoRun2(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, locTypeDto, recommendRows, times);
+            if (emptyPalletRequest) {
+                log.error("No empty location found. spec={}, preferredArea={}, nearRow={}",
+                        JSON.toJSONString(locTypeDto), preferredArea, nearRow);
+                throw new CoolException("娌℃湁绌哄簱浣�");
             }
             LocTypeDto compatibleLocTypeDto = buildRetryCompatibleLocTypeDto(staDescId, findLocNoAttributeVo, locTypeDto);
             if (compatibleLocTypeDto != null) {
-                // 绗竴杞叏閮ㄥ爢鍨涙満閮芥病鎵惧埌鏃讹紝鍐嶈繘鍏ヨ鏍煎吋瀹归噸璇曪紝涓嶅湪鍗曚釜鍫嗗灈鏈哄唴灞�閮ㄩ��鍖栥��
                 log.warn("locType compatibility retry. source={}, target={}", JSON.toJSONString(locTypeDto), JSON.toJSONString(compatibleLocTypeDto));
                 return getLocNoRun2(whsType, staDescId, sourceStaNo, findLocNoAttributeVo, moveCrnNo, compatibleLocTypeDto, recommendRows, 0);
             }
-            log.error("No empty location found. spec={}, times={}, preferredArea={}, nearRow={}", JSON.toJSONString(locTypeDto), times, preferredArea, nearRow);
+            log.error("No empty location found. spec={}, preferredArea={}, nearRow={}", JSON.toJSONString(locTypeDto), preferredArea, nearRow);
             throw new CoolException("娌℃湁绌哄簱浣�");
         }
 
@@ -1799,21 +2262,11 @@
         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;
+        return findConfiguredEmptyLocForCrn(rowLastno, rowLastnoType, crnNo, nearRow, locTypeDto);
     }
 
     public StartupDto getLocNoRun4(Integer whsType, Integer staDescId, Integer sourceStaNo, FindLocNoAttributeVo findLocNoAttributeVo, Integer moveCrnNo, LocTypeDto locTypeDto, int times) {
@@ -2267,6 +2720,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;      //鍫嗗灈鏈哄彿
@@ -2336,20 +2790,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();
 

--
Gitblit v1.9.1