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/webapp/static/js/rowLastno/rowLastno.js                               |    2 
 src/test/java/com/zy/common/service/CommonServiceLocTypeStrategyTest.java      |  165 ++
 src/main/resources/mapper/BasCrnDepthRuleMapper.xml                            |   21 
 src/main/webapp/static/js/basDevp/basDevp.js                                   |   27 
 src/main/java/com/zy/common/web/WcsController.java                             |   11 
 pom.xml                                                                        |    5 
 src/main/java/com/zy/asrs/service/BasCrnDepthRuleService.java                  |   22 
 src/main/webapp/views/basCrnDepthRule/basCrnDepthRule.html                     |  223 +++
 src/main/java/com/zy/asrs/utils/Utils.java                                     |   52 
 src/main/java/com/zy/asrs/entity/BasCrnDepthRule.java                          |  146 ++
 src/main/java/com/zy/common/model/CrnDepthRuleProfile.java                     |   54 
 src/main/java/com/zy/common/service/CommonService.java                         | 1090 +++++++++++++-----
 src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleTemplateParam.java       |   17 
 src/main/java/com/zy/asrs/mapper/BasCrnDepthRuleMapper.java                    |   11 
 src/main/resources/sql/20260322_row_lastno_current_crn_no.sql                  |   11 
 src/main/webapp/static/js/basCrnDepthRule/basCrnDepthRule.js                   |  304 +++++
 src/main/java/com/zy/asrs/service/impl/BasCrnpServiceImpl.java                 |   16 
 src/test/java/com/zy/common/service/CommonServiceRun2AllocationTest.java       |  329 +++++
 src/main/java/com/zy/asrs/controller/BasCrnDepthRuleController.java            |  214 +++
 src/test/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImplTest.java     |  157 ++
 src/main/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImpl.java         |  471 ++++++++
 src/main/java/com/zy/asrs/entity/RowLastno.java                                |   15 
 src/main/webapp/views/basDevp/basDevp_detail.html                              |    7 
 src/main/resources/sql/20260321_bas_crn_depth_rule.sql                         |   30 
 src/main/webapp/views/rowLastno/rowLastno_detail.html                          |    6 
 src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleRuntimePreviewParam.java |   21 
 26 files changed, 3,095 insertions(+), 332 deletions(-)

diff --git a/pom.xml b/pom.xml
index 992785a..405cb39 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,6 +117,11 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-webflux</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/com/zy/asrs/controller/BasCrnDepthRuleController.java b/src/main/java/com/zy/asrs/controller/BasCrnDepthRuleController.java
new file mode 100644
index 0000000..c97085c
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/BasCrnDepthRuleController.java
@@ -0,0 +1,214 @@
+package com.zy.asrs.controller;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.baomidou.mybatisplus.mapper.Wrapper;
+import com.baomidou.mybatisplus.plugins.Page;
+import com.core.annotations.ManagerAuth;
+import com.core.common.BaseRes;
+import com.core.common.Cools;
+import com.core.common.DateUtils;
+import com.core.common.R;
+import com.zy.asrs.entity.BasCrnDepthRule;
+import com.zy.asrs.entity.param.BasCrnDepthRuleRuntimePreviewParam;
+import com.zy.asrs.entity.param.BasCrnDepthRuleTemplateParam;
+import com.zy.asrs.service.BasCrnDepthRuleService;
+import com.zy.common.service.CommonService;
+import com.zy.common.web.BaseController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class BasCrnDepthRuleController extends BaseController {
+
+    @Autowired
+    private BasCrnDepthRuleService basCrnDepthRuleService;
+    @Autowired
+    private CommonService commonService;
+
+    /**
+     * 鏌ヨ鍗曟潯鍫嗗灈鏈烘繁娴呰鍒欍��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/{id}/auth")
+    @ManagerAuth
+    public R get(@PathVariable("id") Long id) {
+        return R.ok(basCrnDepthRuleService.selectById(id));
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ鍫嗗灈鏈烘繁娴呰鍒欏垪琛ㄣ��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/list/auth")
+    @ManagerAuth
+    public R list(@RequestParam(defaultValue = "1") Integer curr,
+                  @RequestParam(defaultValue = "10") Integer limit,
+                  @RequestParam(required = false) String orderByField,
+                  @RequestParam(required = false) String orderByType,
+                  @RequestParam(required = false) String condition,
+                  @RequestParam Map<String, Object> param) {
+        EntityWrapper<BasCrnDepthRule> wrapper = new EntityWrapper<BasCrnDepthRule>();
+        excludeTrash(param);
+        convert(param, wrapper);
+        allLike(BasCrnDepthRule.class, param.keySet(), wrapper, condition);
+        if (!Cools.isEmpty(orderByField)) {
+            wrapper.orderBy(humpToLine(orderByField), "asc".equals(orderByType));
+        } else {
+            wrapper.orderBy("whs_type", true).orderBy("crn_no", true);
+        }
+        return R.ok(basCrnDepthRuleService.selectPage(new Page<BasCrnDepthRule>(curr, limit), wrapper));
+    }
+
+    /**
+     * 鎶婂墠绔煡璇㈠弬鏁拌浆鎹㈡垚 MyBatis Plus 鐨勬ā绯�/鏃堕棿鑼冨洿鏉′欢銆�
+     */
+    private <T> void convert(Map<String, Object> map, EntityWrapper<T> wrapper) {
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            String val = String.valueOf(entry.getValue());
+            if (val.contains(RANGE_TIME_LINK)) {
+                String[] dates = val.split(RANGE_TIME_LINK);
+                wrapper.ge(entry.getKey(), DateUtils.convert(dates[0]));
+                wrapper.le(entry.getKey(), DateUtils.convert(dates[1]));
+            } else {
+                wrapper.like(entry.getKey(), val);
+            }
+        }
+    }
+
+    /**
+     * 鏂板涓�鏉″爢鍨涙満娣辨祬瑙勫垯銆�
+     */
+    @RequestMapping(value = "/basCrnDepthRule/add/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欐坊鍔�")
+    public R add(BasCrnDepthRule basCrnDepthRule) {
+        basCrnDepthRuleService.validateRule(basCrnDepthRule);
+        Date now = new Date();
+        basCrnDepthRule.setCreateBy(getUserId());
+        basCrnDepthRule.setCreateTime(now);
+        basCrnDepthRule.setUpdateBy(getUserId());
+        basCrnDepthRule.setUpdateTime(now);
+        basCrnDepthRuleService.insert(basCrnDepthRule);
+        return R.ok("淇濆瓨鎴愬姛");
+    }
+
+    /**
+     * 淇敼宸叉湁鍫嗗灈鏈烘繁娴呰鍒欙紝骞朵繚鐣欏垱寤哄璁″瓧娈点��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/update/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欎慨鏀�")
+    public R update(BasCrnDepthRule basCrnDepthRule) {
+        if (Cools.isEmpty(basCrnDepthRule) || basCrnDepthRule.getId() == null) {
+            return R.error();
+        }
+        basCrnDepthRuleService.validateRule(basCrnDepthRule);
+        BasCrnDepthRule dbRule = basCrnDepthRuleService.selectById(basCrnDepthRule.getId());
+        if (dbRule == null) {
+            return R.error("瑙勫垯涓嶅瓨鍦�");
+        }
+        basCrnDepthRule.setCreateBy(dbRule.getCreateBy());
+        basCrnDepthRule.setCreateTime(dbRule.getCreateTime());
+        basCrnDepthRule.setUpdateBy(getUserId());
+        basCrnDepthRule.setUpdateTime(new Date());
+        basCrnDepthRuleService.updateById(basCrnDepthRule);
+        return R.ok("淇敼瀹屾垚");
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鍫嗗灈鏈烘繁娴呰鍒欍��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/delete/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欏垹闄�")
+    public R delete(@RequestParam String param) {
+        List<BasCrnDepthRule> list = JSONArray.parseArray(param, BasCrnDepthRule.class);
+        if (Cools.isEmpty(list)) {
+            return R.error();
+        }
+        for (BasCrnDepthRule entity : list) {
+            if (entity != null && entity.getId() != null) {
+                basCrnDepthRuleService.deleteById(entity.getId());
+            }
+        }
+        return R.ok();
+    }
+
+    /**
+     * 瀵煎嚭褰撳墠鏌ヨ鏉′欢鍛戒腑鐨勮鍒欐暟鎹��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/export/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欏鍑�")
+    public R export(@RequestBody JSONObject param) {
+        List<String> fields = JSONObject.parseArray(param.getJSONArray("fields").toJSONString(), String.class);
+        EntityWrapper<BasCrnDepthRule> wrapper = new EntityWrapper<BasCrnDepthRule>();
+        Map<String, Object> map = excludeTrash(param.getJSONObject("basCrnDepthRule"));
+        convert(map, wrapper);
+        List<BasCrnDepthRule> list = basCrnDepthRuleService.selectList(wrapper);
+        return R.ok(exportSupport(list, fields));
+    }
+
+    /**
+     * 鎸夋ā鏉块瑙堟煇浠撳簱鑼冨洿鍐呯殑榛樿娣辨祬瑙勫垯銆�
+     */
+    @RequestMapping(value = "/basCrnDepthRule/templatePreview/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欐ā鏉块瑙�")
+    public R templatePreview(BasCrnDepthRuleTemplateParam param) {
+        return R.ok(basCrnDepthRuleService.previewTemplate(param));
+    }
+
+    /**
+     * 鎸夋ā鏉挎壒閲忕敓鎴愭垨瑕嗙洊鏌愪粨搴撶殑鍫嗗灈鏈烘繁娴呰鍒欍��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/templateGenerate/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欐ā鏉跨敓鎴�")
+    public R templateGenerate(BasCrnDepthRuleTemplateParam param) {
+        basCrnDepthRuleService.saveTemplate(param, getUserId());
+        return R.ok("妯℃澘鐢熸垚瀹屾垚");
+    }
+
+    /**
+     * 棰勮鏌愮珯鐐瑰綋鍓嶇殑搴撳尯銆佽澶囧拰娣辨祬鎺掑弬涓庨『搴忋��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/runtimePreview/auth")
+    @ManagerAuth(memo = "鍫嗗灈鏈烘繁娴呰鍒欒繍琛岄瑙�")
+    public R runtimePreview(BasCrnDepthRuleRuntimePreviewParam param) {
+        return R.ok(commonService.previewRun2Allocation(param));
+    }
+
+    /**
+     * 涓哄墠绔嚜鍔ㄥ畬鎴愭彁渚涜鍒欎富閿煡璇€��
+     */
+    @RequestMapping(value = "/basCrnDepthRuleQuery/auth")
+    @ManagerAuth
+    public R query(String condition) {
+        EntityWrapper<BasCrnDepthRule> wrapper = new EntityWrapper<BasCrnDepthRule>();
+        wrapper.like("crn_no", condition);
+        Page<BasCrnDepthRule> page = basCrnDepthRuleService.selectPage(new Page<BasCrnDepthRule>(0, 10), wrapper);
+        List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
+        for (BasCrnDepthRule rule : page.getRecords()) {
+            Map<String, Object> map = new HashMap<String, Object>();
+            map.put("id", rule.getId());
+            map.put("value", rule.getWhsType() + "-" + rule.getCrnNo());
+            result.add(map);
+        }
+        return R.ok(result);
+    }
+
+    /**
+     * 妫�鏌ユ煇涓瓧娈靛�兼槸鍚﹀凡瀛樺湪锛岀敤浜庡墠绔噸澶嶆牎楠屻��
+     */
+    @RequestMapping(value = "/basCrnDepthRule/check/column/auth")
+    @ManagerAuth
+    public R query(@RequestBody JSONObject param) {
+        Wrapper<BasCrnDepthRule> wrapper = new EntityWrapper<BasCrnDepthRule>()
+                .eq(humpToLine(String.valueOf(param.get("key"))), param.get("val"));
+        if (null != basCrnDepthRuleService.selectOne(wrapper)) {
+            return R.parse(BaseRes.REPEAT).add(getComment(BasCrnDepthRule.class, String.valueOf(param.get("key"))));
+        }
+        return R.ok();
+    }
+}
diff --git a/src/main/java/com/zy/asrs/entity/BasCrnDepthRule.java b/src/main/java/com/zy/asrs/entity/BasCrnDepthRule.java
new file mode 100644
index 0000000..5520d61
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/BasCrnDepthRule.java
@@ -0,0 +1,146 @@
+package com.zy.asrs.entity;
+
+import com.baomidou.mybatisplus.annotations.TableField;
+import com.baomidou.mybatisplus.annotations.TableId;
+import com.baomidou.mybatisplus.annotations.TableName;
+import com.baomidou.mybatisplus.enums.IdType;
+import com.core.common.Cools;
+import com.core.common.SpringUtils;
+import com.zy.asrs.service.BasWhsService;
+import com.zy.system.entity.User;
+import com.zy.system.service.UserService;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@TableName("asr_bas_crn_depth_rule")
+public class BasCrnDepthRule implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "浠撳簱绫诲瀷")
+    @TableField("whs_type")
+    private Integer whsType;
+
+    @ApiModelProperty(value = "鍫嗗灈鏈哄彿")
+    @TableField("crn_no")
+    private Integer crnNo;
+
+    @ApiModelProperty(value = "甯冨眬绫诲瀷 1:鍗曚几 2:鍙屼几")
+    @TableField("layout_type")
+    private Integer layoutType;
+
+    @ApiModelProperty(value = "鎼滅储鎺掗『搴廋SV")
+    @TableField("search_rows_csv")
+    private String searchRowsCsv;
+
+    @ApiModelProperty(value = "娴呭簱浣嶆帓CSV")
+    @TableField("shallow_rows_csv")
+    private String shallowRowsCsv;
+
+    @ApiModelProperty(value = "娣卞簱浣嶆帓CSV")
+    @TableField("deep_rows_csv")
+    private String deepRowsCsv;
+
+    @ApiModelProperty(value = "鍚敤鐘舵�� 1:鍚敤 0:绂佺敤")
+    private Integer enabled;
+
+    @ApiModelProperty(value = "澶囨敞")
+    private String memo;
+
+    @ApiModelProperty(value = "鍒涘缓浜哄憳")
+    @TableField("create_by")
+    private Long createBy;
+
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+    @TableField("create_time")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @ApiModelProperty(value = "淇敼浜哄憳")
+    @TableField("update_by")
+    private Long updateBy;
+
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @TableField("update_time")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    public String getWhsType$() {
+        BasWhsService service = SpringUtils.getBean(BasWhsService.class);
+        BasWhs basWhs = service.selectById(this.whsType);
+        if (!Cools.isEmpty(basWhs)) {
+            return String.valueOf(basWhs.getWhsDesc());
+        }
+        return null;
+    }
+
+    public String getLayoutType$() {
+        if (this.layoutType == null) {
+            return null;
+        }
+        switch (this.layoutType) {
+            case 1:
+                return "鍗曚几";
+            case 2:
+                return "鍙屼几";
+            default:
+                return String.valueOf(this.layoutType);
+        }
+    }
+
+    public String getEnabled$() {
+        if (this.enabled == null) {
+            return null;
+        }
+        switch (this.enabled) {
+            case 1:
+                return "鍚敤";
+            case 0:
+                return "绂佺敤";
+            default:
+                return String.valueOf(this.enabled);
+        }
+    }
+
+    public String getCreateBy$() {
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.selectById(this.createBy);
+        if (!Cools.isEmpty(user)) {
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+    public String getCreateTime$() {
+        if (Cools.isEmpty(this.createTime)) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getUpdateBy$() {
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.selectById(this.updateBy);
+        if (!Cools.isEmpty(user)) {
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+    public String getUpdateTime$() {
+        if (Cools.isEmpty(this.updateTime)) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+}
diff --git a/src/main/java/com/zy/asrs/entity/RowLastno.java b/src/main/java/com/zy/asrs/entity/RowLastno.java
index e4dedf1..8be0bc9 100644
--- a/src/main/java/com/zy/asrs/entity/RowLastno.java
+++ b/src/main/java/com/zy/asrs/entity/RowLastno.java
@@ -44,6 +44,13 @@
     private Integer currentRow;
 
     /**
+     * 褰撳墠鍫嗗灈鏈哄彿
+     */
+    @ApiModelProperty(value= "褰撳墠鍫嗗灈鏈哄彿")
+    @TableField("current_crn_no")
+    private Integer currentCrnNo;
+
+    /**
      * 璧峰鎺掑彿
      */
     @ApiModelProperty(value= "璧峰鎺掑彿")
@@ -226,6 +233,14 @@
         this.currentRow = currentRow;
     }
 
+    public Integer getCurrentCrnNo() {
+        return currentCrnNo;
+    }
+
+    public void setCurrentCrnNo(Integer currentCrnNo) {
+        this.currentCrnNo = currentCrnNo;
+    }
+
     public Integer getsRow() {
         return sRow;
     }
diff --git a/src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleRuntimePreviewParam.java b/src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleRuntimePreviewParam.java
new file mode 100644
index 0000000..3d56eef
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleRuntimePreviewParam.java
@@ -0,0 +1,21 @@
+package com.zy.asrs.entity.param;
+
+import lombok.Data;
+
+@Data
+public class BasCrnDepthRuleRuntimePreviewParam {
+
+    private Integer staDescId;
+
+    private Integer sourceStaNo;
+
+    private Integer outArea;
+
+    private String matnr;
+
+    private Short locType1;
+
+    private Short locType2;
+
+    private Short locType3;
+}
diff --git a/src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleTemplateParam.java b/src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleTemplateParam.java
new file mode 100644
index 0000000..177b896
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/param/BasCrnDepthRuleTemplateParam.java
@@ -0,0 +1,17 @@
+package com.zy.asrs.entity.param;
+
+import lombok.Data;
+
+@Data
+public class BasCrnDepthRuleTemplateParam {
+
+    private Integer whsType;
+
+    private Integer startCrnNo;
+
+    private Integer endCrnNo;
+
+    private Integer enabled;
+
+    private String memo;
+}
diff --git a/src/main/java/com/zy/asrs/mapper/BasCrnDepthRuleMapper.java b/src/main/java/com/zy/asrs/mapper/BasCrnDepthRuleMapper.java
new file mode 100644
index 0000000..321600a
--- /dev/null
+++ b/src/main/java/com/zy/asrs/mapper/BasCrnDepthRuleMapper.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.mapper;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import com.zy.asrs.entity.BasCrnDepthRule;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface BasCrnDepthRuleMapper extends BaseMapper<BasCrnDepthRule> {
+}
diff --git a/src/main/java/com/zy/asrs/service/BasCrnDepthRuleService.java b/src/main/java/com/zy/asrs/service/BasCrnDepthRuleService.java
new file mode 100644
index 0000000..8bbabfa
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/BasCrnDepthRuleService.java
@@ -0,0 +1,22 @@
+package com.zy.asrs.service;
+
+import com.baomidou.mybatisplus.service.IService;
+import com.zy.asrs.entity.BasCrnDepthRule;
+import com.zy.asrs.entity.RowLastno;
+import com.zy.asrs.entity.param.BasCrnDepthRuleTemplateParam;
+import com.zy.common.model.CrnDepthRuleProfile;
+
+import java.util.List;
+
+public interface BasCrnDepthRuleService extends IService<BasCrnDepthRule> {
+
+    void validateRule(BasCrnDepthRule rule);
+
+    BasCrnDepthRule findEnabledRule(Integer whsType, Integer crnNo);
+
+    CrnDepthRuleProfile resolveProfile(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow);
+
+    List<BasCrnDepthRule> previewTemplate(BasCrnDepthRuleTemplateParam param);
+
+    void saveTemplate(BasCrnDepthRuleTemplateParam param, Long userId);
+}
diff --git a/src/main/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImpl.java
new file mode 100644
index 0000000..9ca5ae0
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImpl.java
@@ -0,0 +1,471 @@
+package com.zy.asrs.service.impl;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.baomidou.mybatisplus.service.impl.ServiceImpl;
+import com.core.common.Cools;
+import com.core.exception.CoolException;
+import com.zy.asrs.entity.BasCrnDepthRule;
+import com.zy.asrs.entity.RowLastno;
+import com.zy.asrs.entity.param.BasCrnDepthRuleTemplateParam;
+import com.zy.asrs.mapper.BasCrnDepthRuleMapper;
+import com.zy.asrs.service.BasCrnDepthRuleService;
+import com.zy.asrs.service.RowLastnoService;
+import com.zy.asrs.utils.Utils;
+import com.zy.common.model.CrnDepthRuleProfile;
+import com.zy.common.properties.SlaveProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+@Service("basCrnDepthRuleService")
+public class BasCrnDepthRuleServiceImpl extends ServiceImpl<BasCrnDepthRuleMapper, BasCrnDepthRule> implements BasCrnDepthRuleService {
+
+    private static final String PROFILE_SOURCE_CONFIG = "CONFIG";
+    private static final String PROFILE_SOURCE_LEGACY = "LEGACY";
+
+    @Autowired
+    private RowLastnoService rowLastnoService;
+    @Autowired
+    private SlaveProperties slaveProperties;
+
+    /**
+     * 鏍¢獙骞舵爣鍑嗗寲鍗曟潯鍫嗗灈鏈烘繁娴呰鍒欍��
+     */
+    @Override
+    public void validateRule(BasCrnDepthRule rule) {
+        if (rule == null || rule.getWhsType() == null || rule.getCrnNo() == null) {
+            throw new CoolException("浠撳簱鍜屽爢鍨涙満涓嶈兘涓虹┖");
+        }
+        if (!Integer.valueOf(1).equals(rule.getLayoutType()) && !Integer.valueOf(2).equals(rule.getLayoutType())) {
+            throw new CoolException("甯冨眬绫诲瀷鍙厑璁� 1=鍗曚几 鎴� 2=鍙屼几");
+        }
+        List<Integer> searchRows = parseRows(rule.getSearchRowsCsv());
+        List<Integer> shallowRows = parseRows(rule.getShallowRowsCsv());
+        List<Integer> deepRows = parseRows(rule.getDeepRowsCsv());
+        if (searchRows.isEmpty()) {
+            throw new CoolException("鎼滅储鎺掗『搴忎笉鑳戒负绌�");
+        }
+        if (Integer.valueOf(1).equals(rule.getLayoutType())) {
+            if (shallowRows.isEmpty()) {
+                shallowRows = new ArrayList<Integer>(searchRows);
+            }
+            if (!deepRows.isEmpty()) {
+                throw new CoolException("鍗曚几瑙勫垯涓嶅厑璁稿~鍐欐繁搴撲綅鎺�");
+            }
+        } else {
+            if (shallowRows.isEmpty()) {
+                throw new CoolException("鍙屼几瑙勫垯蹇呴』濉啓娴呭簱浣嶆帓");
+            }
+            if (deepRows.isEmpty()) {
+                throw new CoolException("鍙屼几瑙勫垯蹇呴』濉啓娣卞簱浣嶆帓");
+            }
+        }
+        rule.setSearchRowsCsv(joinRows(searchRows));
+        rule.setShallowRowsCsv(joinRows(shallowRows));
+        rule.setDeepRowsCsv(joinRows(deepRows));
+        if (rule.getEnabled() == null) {
+            rule.setEnabled(1);
+        }
+    }
+
+    /**
+     * 鏌ヨ鏌愪粨搴撴煇鍫嗗灈鏈哄綋鍓嶅惎鐢ㄧ殑娣辨祬瑙勫垯銆�
+     */
+    @Override
+    public BasCrnDepthRule findEnabledRule(Integer whsType, Integer crnNo) {
+        if (whsType == null || crnNo == null) {
+            return null;
+        }
+        return this.selectOne(new EntityWrapper<BasCrnDepthRule>()
+                .eq("whs_type", whsType)
+                .eq("crn_no", crnNo)
+                .eq("enabled", 1));
+    }
+
+    /**
+     * 瑙f瀽杩愯鏃朵娇鐢ㄧ殑娣辨祬鎺掔敾鍍忥紝浼樺厛璧伴厤缃紝缂哄け鏃跺洖閫�鏃ч�昏緫銆�
+     */
+    @Override
+    public CrnDepthRuleProfile resolveProfile(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) {
+        if (rowLastno == null || crnNo == null) {
+            return new CrnDepthRuleProfile();
+        }
+        BasCrnDepthRule rule = findEnabledRule(rowLastno.getWhsType(), crnNo);
+        if (!Cools.isEmpty(rule)) {
+            return buildProfileFromRule(rule);
+        }
+        return buildLegacyProfile(rowLastno, crnNo, preferredNearRow);
+    }
+
+    /**
+     * 鎸夋棫閫昏緫棰勮涓�鎵瑰爢鍨涙満鐨勯粯璁ゆ繁娴呰鍒欐ā鏉裤��
+     */
+    @Override
+    public List<BasCrnDepthRule> previewTemplate(BasCrnDepthRuleTemplateParam param) {
+        if (param == null || param.getWhsType() == null) {
+            throw new CoolException("浠撳簱涓嶈兘涓虹┖");
+        }
+        RowLastno rowLastno = rowLastnoService.selectById(param.getWhsType());
+        if (Cools.isEmpty(rowLastno)) {
+            throw new CoolException("鏈壘鍒颁粨搴撳搴旂殑杞瑙勫垯");
+        }
+        int startCrnNo = param.getStartCrnNo() == null ? defaultStartCrnNo(rowLastno) : param.getStartCrnNo();
+        int endCrnNo = param.getEndCrnNo() == null ? defaultEndCrnNo(rowLastno) : param.getEndCrnNo();
+        if (startCrnNo <= 0 || endCrnNo < startCrnNo) {
+            throw new CoolException("鍫嗗灈鏈鸿寖鍥撮敊璇�");
+        }
+        List<BasCrnDepthRule> rules = new ArrayList<BasCrnDepthRule>();
+        for (int crnNo = startCrnNo; crnNo <= endCrnNo; crnNo++) {
+            CrnDepthRuleProfile profile = buildLegacyProfile(rowLastno, crnNo, null);
+            BasCrnDepthRule rule = new BasCrnDepthRule();
+            rule.setWhsType(param.getWhsType());
+            rule.setCrnNo(crnNo);
+            rule.setLayoutType(profile.getLayoutType());
+            rule.setSearchRowsCsv(joinRows(profile.getSearchRows()));
+            rule.setShallowRowsCsv(joinRows(profile.getShallowRows()));
+            rule.setDeepRowsCsv(joinRows(profile.getDeepRows()));
+            rule.setEnabled(param.getEnabled() == null ? 1 : param.getEnabled());
+            rule.setMemo(param.getMemo());
+            rules.add(rule);
+        }
+        return rules;
+    }
+
+    /**
+     * 鎸夋ā鏉挎壒閲忎繚瀛樿鍒欙紝宸插瓨鍦ㄥ垯瑕嗙洊锛屼笉瀛樺湪鍒欐柊澧炪��
+     */
+    @Override
+    public void saveTemplate(BasCrnDepthRuleTemplateParam param, Long userId) {
+        List<BasCrnDepthRule> previewRules = previewTemplate(param);
+        Date now = new Date();
+        for (BasCrnDepthRule previewRule : previewRules) {
+            validateRule(previewRule);
+            BasCrnDepthRule exists = this.selectOne(new EntityWrapper<BasCrnDepthRule>()
+                    .eq("whs_type", previewRule.getWhsType())
+                    .eq("crn_no", previewRule.getCrnNo()));
+            if (exists == null) {
+                previewRule.setCreateBy(userId);
+                previewRule.setCreateTime(now);
+                previewRule.setUpdateBy(userId);
+                previewRule.setUpdateTime(now);
+                this.insert(previewRule);
+                continue;
+            }
+            previewRule.setId(exists.getId());
+            previewRule.setCreateBy(exists.getCreateBy());
+            previewRule.setCreateTime(exists.getCreateTime());
+            previewRule.setUpdateBy(userId);
+            previewRule.setUpdateTime(now);
+            this.updateById(previewRule);
+        }
+    }
+
+    /**
+     * 鎶婃暟鎹簱瑙勫垯杞崲鎴愮粺涓�鐨勮繍琛屾椂鐢诲儚瀵硅薄銆�
+     */
+    private CrnDepthRuleProfile buildProfileFromRule(BasCrnDepthRule rule) {
+        CrnDepthRuleProfile profile = new CrnDepthRuleProfile();
+        profile.setWhsType(rule.getWhsType());
+        profile.setCrnNo(rule.getCrnNo());
+        profile.setLayoutType(rule.getLayoutType());
+        profile.setSource(PROFILE_SOURCE_CONFIG);
+        profile.setSearchRows(normalizeRows(parseRows(rule.getSearchRowsCsv())));
+        profile.setShallowRows(normalizeRows(parseRows(rule.getShallowRowsCsv())));
+        profile.setDeepRows(normalizeRows(parseRows(rule.getDeepRowsCsv())));
+        if (profile.getSearchRows().isEmpty()) {
+            if (!profile.getShallowRows().isEmpty()) {
+                profile.setSearchRows(new ArrayList<Integer>(profile.getShallowRows()));
+            } else {
+                profile.setSearchRows(new ArrayList<Integer>(profile.getDeepRows()));
+            }
+        }
+        if (profile.isSingleExtension() && profile.getShallowRows().isEmpty()) {
+            profile.setShallowRows(new ArrayList<Integer>(profile.getSearchRows()));
+        }
+        buildPairRows(profile);
+        return profile;
+    }
+
+    /**
+     * 鐢ㄦ棫鐨� row_lastno + slaveProperties 瑙勫垯鏋勫缓杩愯鏃剁敾鍍忋��
+     */
+    private CrnDepthRuleProfile buildLegacyProfile(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) {
+        CrnDepthRuleProfile profile = new CrnDepthRuleProfile();
+        profile.setWhsType(rowLastno.getWhsType());
+        profile.setCrnNo(crnNo);
+        profile.setLayoutType(resolveLegacyLayoutType(rowLastno));
+        profile.setSource(PROFILE_SOURCE_LEGACY);
+        profile.setSearchRows(getLegacySearchRows(rowLastno, crnNo, preferredNearRow));
+        if (profile.isSingleExtension()) {
+            profile.setShallowRows(new ArrayList<Integer>(profile.getSearchRows()));
+            profile.setDeepRows(new ArrayList<Integer>());
+            return profile;
+        }
+        List<Integer> shallowRows = new ArrayList<Integer>();
+        List<Integer> deepRows = new ArrayList<Integer>();
+        for (Integer searchRow : profile.getSearchRows()) {
+            if (searchRow == null) {
+                continue;
+            }
+            if (Utils.isShallowLoc(slaveProperties, searchRow)) {
+                shallowRows.add(searchRow);
+                Integer deepRow = safeGetLegacyDeepRow(rowLastno, searchRow);
+                if (deepRow != null) {
+                    deepRows.add(deepRow);
+                }
+            } else {
+                deepRows.add(searchRow);
+            }
+        }
+        profile.setShallowRows(normalizeRows(shallowRows));
+        profile.setDeepRows(normalizeRows(deepRows));
+        buildPairRows(profile);
+        return profile;
+    }
+
+    /**
+     * 寤虹珛娴呮帓涓庢繁鎺掍箣闂寸殑瀵瑰簲鍏崇郴锛屼緵鍙屼几閫変綅鏃堕厤瀵逛娇鐢ㄣ��
+     */
+    private void buildPairRows(CrnDepthRuleProfile profile) {
+        if (profile == null || !profile.isDoubleExtension()) {
+            return;
+        }
+        for (Integer shallowRow : profile.getShallowRows()) {
+            Integer deepRow = findNearestAdjacentRow(shallowRow, profile.getDeepRows());
+            if (deepRow == null) {
+                continue;
+            }
+            profile.getShallowToDeepRow().put(shallowRow, deepRow);
+            profile.getDeepToShallowRow().put(deepRow, shallowRow);
+        }
+    }
+
+    /**
+     * 鍦ㄥ�欓�夋帓涓壘鍒颁笌褰撳墠鎺掕窛绂绘渶杩戠殑閰嶅鎺掋��
+     */
+    private Integer findNearestAdjacentRow(Integer currentRow, List<Integer> candidateRows) {
+        if (currentRow == null || Cools.isEmpty(candidateRows)) {
+            return null;
+        }
+        Integer best = null;
+        int bestDistance = Integer.MAX_VALUE;
+        for (Integer candidateRow : candidateRows) {
+            if (candidateRow == null) {
+                continue;
+            }
+            int distance = Math.abs(candidateRow - currentRow);
+            if (distance < bestDistance) {
+                bestDistance = distance;
+                best = candidateRow;
+            }
+            if (distance == 1) {
+                return candidateRow;
+            }
+        }
+        return best;
+    }
+
+    /**
+     * 鏍规嵁鏃у簱鍨嬭鍒欐帹鏂崟浼�/鍙屼几甯冨眬绫诲瀷銆�
+     */
+    private Integer resolveLegacyLayoutType(RowLastno rowLastno) {
+        if (rowLastno == null) {
+            return 1;
+        }
+        if (rowLastno.getTypeId() != null && rowLastno.getTypeId() == 2) {
+            return 1;
+        }
+        return 2;
+    }
+
+    /**
+     * 鎸夋棫閫昏緫鎺ㄥ鏌愬彴鍫嗗灈鏈虹殑鎼滅储鎺掗『搴忋��
+     */
+    private List<Integer> getLegacySearchRows(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) {
+        List<Integer> searchRows = new ArrayList<Integer>();
+        if (rowLastno == null || crnNo == null) {
+            return searchRows;
+        }
+        addRow(searchRows, preferredNearRow, rowLastno);
+        int rowSpan = getLegacyRowSpan(rowLastno.getTypeId());
+        int startCrnNo = rowLastno.getsCrnNo() == null ? 1 : rowLastno.getsCrnNo();
+        int startRow = rowLastno.getsRow() == null ? 1 : rowLastno.getsRow();
+        int crnOffset = crnNo - startCrnNo;
+        if (crnOffset < 0) {
+            return searchRows;
+        }
+        int crnStartRow = startRow + crnOffset * rowSpan;
+        if (rowLastno.getTypeId() != null && rowLastno.getTypeId() == 2) {
+            addRow(searchRows, crnStartRow, rowLastno);
+            addRow(searchRows, crnStartRow + 1, rowLastno);
+            return searchRows;
+        }
+        addRow(searchRows, crnStartRow + 1, rowLastno);
+        addRow(searchRows, crnStartRow + 2, rowLastno);
+        if (searchRows.isEmpty()) {
+            for (int row = crnStartRow; row < crnStartRow + rowSpan; row++) {
+                addRow(searchRows, row, rowLastno);
+            }
+        }
+        return searchRows;
+    }
+
+    /**
+     * 鐢辨棫娴呮帓瑙勫垯鎺ㄦ柇瀵瑰簲鐨勬繁鎺掋��
+     */
+    private Integer safeGetLegacyDeepRow(RowLastno rowLastno, Integer shallowRow) {
+        if (rowLastno == null || shallowRow == null) {
+            return null;
+        }
+        try {
+            String shallowLocNo = Utils.zerofill(String.valueOf(shallowRow), 2) + "00101";
+            Integer deepRow = Utils.getRow(Utils.getDeepLoc(slaveProperties, shallowLocNo));
+            if (deepRow < rowLastno.getsRow() || deepRow > rowLastno.geteRow()) {
+                return null;
+            }
+            return deepRow;
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    /**
+     * 鍙栨ā鏉跨敓鎴愭椂鐨勯粯璁よ捣濮嬪爢鍨涙満鍙枫��
+     */
+    private Integer defaultStartCrnNo(RowLastno rowLastno) {
+        return rowLastno.getsCrnNo() == null ? 1 : rowLastno.getsCrnNo();
+    }
+
+    /**
+     * 鍙栨ā鏉跨敓鎴愭椂鐨勯粯璁ょ粨鏉熷爢鍨涙満鍙枫��
+     */
+    private Integer defaultEndCrnNo(RowLastno rowLastno) {
+        if (rowLastno.geteCrnNo() != null && rowLastno.geteCrnNo() >= defaultStartCrnNo(rowLastno)) {
+            return rowLastno.geteCrnNo();
+        }
+        if (rowLastno.getCrnQty() != null && rowLastno.getCrnQty() > 0) {
+            return defaultStartCrnNo(rowLastno) + rowLastno.getCrnQty() - 1;
+        }
+        return defaultStartCrnNo(rowLastno);
+    }
+
+    /**
+     * 鎸夋棫搴撳瀷瑙勫垯杩斿洖姣忓彴鍫嗗灈鏈哄崰鐢ㄧ殑鎺掕法搴︺��
+     */
+    private int getLegacyRowSpan(Integer typeId) {
+        if (typeId != null && typeId == 2) {
+            return 2;
+        }
+        return 4;
+    }
+
+    /**
+     * 鍚戞悳绱㈡帓鍒楄〃杩藉姞涓�涓悎娉曚笖涓嶉噸澶嶇殑鎺掑彿銆�
+     */
+    private void addRow(List<Integer> searchRows, Integer row, RowLastno rowLastno) {
+        if (row == null || rowLastno == null) {
+            return;
+        }
+        if (row < rowLastno.getsRow() || row > rowLastno.geteRow()) {
+            return;
+        }
+        if (!searchRows.contains(row)) {
+            searchRows.add(row);
+        }
+    }
+
+    /**
+     * 鍘婚噸骞惰繃婊ら潪娉曟帓鍙枫��
+     */
+    private List<Integer> normalizeRows(List<Integer> rows) {
+        LinkedHashSet<Integer> normalizedRows = new LinkedHashSet<Integer>();
+        if (Cools.isEmpty(rows)) {
+            return new ArrayList<Integer>();
+        }
+        for (Integer row : rows) {
+            if (row != null && row > 0) {
+                normalizedRows.add(row);
+            }
+        }
+        return new ArrayList<Integer>(normalizedRows);
+    }
+
+    /**
+     * 鎶� CSV/鑼冨洿琛ㄨ揪寮忚В鏋愭垚鎺掑彿鍒楄〃銆�
+     */
+    private List<Integer> parseRows(String rowsCsv) {
+        List<Integer> rows = new ArrayList<Integer>();
+        if (Cools.isEmpty(rowsCsv)) {
+            return rows;
+        }
+        String normalized = rowsCsv.replace("锛�", ",")
+                .replace("锛�", ";")
+                .replace("銆�", ",")
+                .replaceAll("\\s+", "");
+        if (normalized.isEmpty()) {
+            return rows;
+        }
+        String[] segments = normalized.split("[,;]");
+        for (String segment : segments) {
+            if (Cools.isEmpty(segment)) {
+                continue;
+            }
+            if (segment.contains("-")) {
+                String[] rangeParts = segment.split("-", 2);
+                Integer startRow = safeParseInt(rangeParts[0]);
+                Integer endRow = safeParseInt(rangeParts[1]);
+                if (startRow == null || endRow == null) {
+                    continue;
+                }
+                int step = startRow <= endRow ? 1 : -1;
+                for (int row = startRow; step > 0 ? row <= endRow : row >= endRow; row += step) {
+                    rows.add(row);
+                }
+                continue;
+            }
+            Integer row = safeParseInt(segment);
+            if (row != null) {
+                rows.add(row);
+            }
+        }
+        return normalizeRows(rows);
+    }
+
+    /**
+     * 瀹夊叏鍦版妸瀛楃涓茶浆鎹负鏁存暟锛屽け璐ユ椂杩斿洖 null銆�
+     */
+    private Integer safeParseInt(String value) {
+        if (Cools.isEmpty(value)) {
+            return null;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException ignored) {
+            return null;
+        }
+    }
+
+    /**
+     * 鎶婃帓鍙峰垪琛ㄥ簭鍒楀寲鎴愰�楀彿鍒嗛殧瀛楃涓层��
+     */
+    private String joinRows(List<Integer> rows) {
+        if (Cools.isEmpty(rows)) {
+            return null;
+        }
+        StringBuilder builder = new StringBuilder();
+        for (Integer row : rows) {
+            if (row == null) {
+                continue;
+            }
+            if (builder.length() > 0) {
+                builder.append(",");
+            }
+            builder.append(row);
+        }
+        return builder.length() == 0 ? null : builder.toString();
+    }
+}
diff --git a/src/main/java/com/zy/asrs/service/impl/BasCrnpServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/BasCrnpServiceImpl.java
index d1a6e17..0814073 100644
--- a/src/main/java/com/zy/asrs/service/impl/BasCrnpServiceImpl.java
+++ b/src/main/java/com/zy/asrs/service/impl/BasCrnpServiceImpl.java
@@ -22,6 +22,9 @@
     @Autowired
     private BasDevpService basDevpService;
 
+    /**
+     * 妫�鏌ュ爢鍨涙満鍩虹鍙敤鐘舵�侊紝涓嶆弧瓒虫椂鐩存帴鎶涘嚭涓氬姟寮傚父銆�
+     */
     @Override
     public BasCrnp checkSiteStatus(Integer crnId) {
         BasCrnp crnp = this.selectById(crnId);
@@ -37,6 +40,9 @@
         return crnp;
     }
 
+    /**
+     * 缁熶竴鏍¢獙鍫嗗灈鏈烘槸鍚﹀彲鍙備笌鍏ュ簱/鍑哄簱鍒嗛厤銆�
+     */
     @Override
     public boolean checkSiteError(Integer crnNo, boolean pakin) {
         BasCrnp crnp = this.selectById(crnNo);
@@ -44,15 +50,13 @@
 //            log.error("{}鍙峰爢鍨涙満涓嶅瓨鍦�", crnNo);
             return false;
         }
-        if (crnp.getCrnErr() != null && crnp.getCrnSts() != 3){
+        if (crnp.getCrnSts() == null || crnp.getCrnSts() != 3) {
             log.error("{}鍙峰爢鍨涙満闈炶嚜鍔ㄨ繛绾跨姸鎬侊紝鏃犳硶浣滀笟!", crnNo);
             return false;
         }
-        if (crnp.getCrnErr() != null) {
-            if (crnp.getCrnErr() != 0) {
-                log.error("{}鍙峰爢鍨涙満寮傚父锛屽紓甯哥爜{}", crnNo, crnp.getCrnErr());
-                return false;
-            }
+        if (crnp.getCrnErr() != null && crnp.getCrnErr() != 0) {
+            log.error("{}鍙峰爢鍨涙満寮傚父锛屽紓甯哥爜{}", crnNo, crnp.getCrnErr());
+            return false;
         }
 
         if (pakin) {
diff --git a/src/main/java/com/zy/asrs/utils/Utils.java b/src/main/java/com/zy/asrs/utils/Utils.java
index 6e13c42..1081c54 100644
--- a/src/main/java/com/zy/asrs/utils/Utils.java
+++ b/src/main/java/com/zy/asrs/utils/Utils.java
@@ -149,9 +149,8 @@
      * <p>澶勭悊瑙勫垯锛�
      * 1. 鍏堟牴鎹叆搴撶珯鐐规煡璇㈡墍灞炲簱鍖恒��
      * 2. 鍏堟彁鍙栬搴撳尯鍐呯殑鍫嗗灈鏈猴紝骞舵寜鍙敤绌哄簱浣嶈繃婊や笉鍙敤鍫嗗灈鏈恒��
-     * 3. 鑻ュ綋鍓嶅簱鍖烘病鏈夋弧瓒虫潯浠剁殑绌哄簱浣嶏紝鍐嶈ˉ鍏呭叾浠栧簱鍖虹殑鍫嗗灈鏈恒��
-     * 4. 褰� {@code locType1 = 1} 鏃讹紝鍏堣繑鍥炰綆搴撲綅鍫嗗灈鏈猴紝鍐嶆妸鍚屾壒鍫嗗灈鏈虹殑楂樺簱浣嶈拷鍔犲埌鍚庨潰銆�
-     * 5. 瀵逛笉瀛樺湪銆佹晠闅溿�佷笉鍙叆浠ュ強鏃犵┖搴撲綅鐨勫爢鍨涙満鐩存帴鍓旈櫎銆�
+     * 3. 褰� {@code locType1 = 1} 鏃讹紝鍏堣繑鍥炰綆搴撲綅鍫嗗灈鏈猴紝鍐嶆妸鍚屾壒鍫嗗灈鏈虹殑楂樺簱浣嶈拷鍔犲埌鍚庨潰銆�
+     * 4. 瀵逛笉瀛樺湪銆佹晠闅溿�佷笉鍙叆浠ュ強鏃犵┖搴撲綅鐨勫爢鍨涙満鐩存帴鍓旈櫎銆�
      *
      * <p>杩欓噷鏄彧璇绘帓搴忓伐鍏凤紝涓嶅啀鍐欏洖 {@code asr_row_lastno.crn_qty}銆�
      * {@code crn_qty} 鍦ㄤ富鏁版嵁閲岃〃绀衡�滃爢鍨涙満鏁伴噺鈥濓紝涓嶈兘鍐嶈鎷挎潵褰撹疆璇㈡父鏍囷紝鍚﹀垯浼氭妸鏁翠粨閰嶇疆鍐欏潖銆�
@@ -185,35 +184,29 @@
         // 鍏堝彇褰撳墠搴撳尯瀵瑰簲鐨勫爢鍨涙満銆�
         List<Integer> preferredCrnNos = getAreaCrnNos(storageArea, rowLastno);
         List<Integer> preferredAvailableCrnNos = getAvailableCrnNos(preferredCrnNos, locType1, emptyPallet, basCrnpService, locMastService);
-        appendCrnLocTypeEntries(result, preferredAvailableCrnNos, locType1, locMastService);
+        appendCrnLocTypeEntries(result, preferredAvailableCrnNos, locType1, emptyPallet, locMastService);
 
-        // 褰撳墠搴撳尯娌℃湁鍙敤瀹归噺鏃讹紝鍐嶈ˉ鍏呭叾浠栧簱鍖哄爢鍨涙満銆�
-        if (!hasAvailableCapacity(preferredCrnNos, locType1, basCrnpService, locMastService)) {
-            List<Integer> otherAreaCrnNos = getOtherAreaCrnNos(storageArea, rowLastno);
-            List<Integer> otherAvailableCrnNos = getAvailableCrnNos(otherAreaCrnNos, locType1, emptyPallet, basCrnpService, locMastService);
-            appendCrnLocTypeEntries(result, otherAvailableCrnNos, locType1, locMastService);
-        }
         return result;
     }
-    private static void appendCrnLocTypeEntries(List<Map<String, Integer>> result, List<Integer> crnNos, Integer locType1, LocMastService locMastService) {
+    private static void appendCrnLocTypeEntries(List<Map<String, Integer>> result, List<Integer> crnNos, Integer locType1, boolean emptyPallet, LocMastService locMastService) {
         Short normalizedLocType1 = normalizeLocType1(locType1);
         if (normalizedLocType1 == null) {
-            appendCrnLocTypeEntries(result, crnNos, (short) 1, locMastService);
-            appendCrnLocTypeEntries(result, crnNos, (short) 2, locMastService);
+            appendCrnLocTypeEntries(result, crnNos, (short) 1, emptyPallet, locMastService);
+            appendCrnLocTypeEntries(result, crnNos, (short) 2, emptyPallet, locMastService);
             return;
         }
-        appendCrnLocTypeEntries(result, crnNos, normalizedLocType1, locMastService);
+        appendCrnLocTypeEntries(result, crnNos, normalizedLocType1, emptyPallet, locMastService);
         if (normalizedLocType1 == 1) {
-            appendCrnLocTypeEntries(result, crnNos, (short) 2, locMastService);
+            appendCrnLocTypeEntries(result, crnNos, (short) 2, emptyPallet, locMastService);
         }
     }
 
-    private static void appendCrnLocTypeEntries(List<Map<String, Integer>> result, List<Integer> crnNos, Short targetLocType1, LocMastService locMastService) {
+    private static void appendCrnLocTypeEntries(List<Map<String, Integer>> result, List<Integer> crnNos, Short targetLocType1, boolean emptyPallet, LocMastService locMastService) {
         if (targetLocType1 == null || Cools.isEmpty(crnNos)) {
             return;
         }
         for (Integer crnNo : crnNos) {
-            if (!hasAvailableLoc(crnNo, targetLocType1, locMastService) || containsCrnLocType(result, crnNo, targetLocType1)) {
+            if (!hasAvailableLoc(crnNo, targetLocType1, emptyPallet, locMastService) || containsCrnLocType(result, crnNo, targetLocType1)) {
                 continue;
             }
             Map<String, Integer> item = new LinkedHashMap<>();
@@ -249,7 +242,7 @@
             if (crnNo == null || !basCrnpService.checkSiteError(crnNo, true)) {
                 continue;
             }
-            if (!hasAvailableLocForRequest(crnNo, locType1, locMastService)) {
+            if (!hasAvailableLocForRequest(crnNo, locType1, emptyPallet, locMastService)) {
                 continue;
             }
             availableCrnNos.add(crnNo);
@@ -274,25 +267,30 @@
         return "Y".equalsIgnoreCase(basCrnp.getEmpIn()) ? 1 : 0;
     }
 
-    private static boolean hasAvailableLocForRequest(Integer crnNo, Integer locType1, LocMastService locMastService) {
+    private static boolean hasAvailableLocForRequest(Integer crnNo, Integer locType1, boolean emptyPallet, LocMastService locMastService) {
         Short normalizedLocType1 = normalizeLocType1(locType1);
         if (normalizedLocType1 == null) {
-            return hasAvailableLoc(crnNo, (short) 1, locMastService) || hasAvailableLoc(crnNo, (short) 2, locMastService);
+            return hasAvailableLoc(crnNo, (short) 1, emptyPallet, locMastService)
+                    || hasAvailableLoc(crnNo, (short) 2, emptyPallet, locMastService);
         }
-        if (hasAvailableLoc(crnNo, normalizedLocType1, locMastService)) {
+        if (hasAvailableLoc(crnNo, normalizedLocType1, emptyPallet, locMastService)) {
             return true;
         }
-        return normalizedLocType1 == 1 && hasAvailableLoc(crnNo, (short) 2, locMastService);
+        return normalizedLocType1 == 1 && hasAvailableLoc(crnNo, (short) 2, emptyPallet, locMastService);
     }
 
-    private static boolean hasAvailableLoc(Integer crnNo, Short locType1, LocMastService locMastService) {
+    private static boolean hasAvailableLoc(Integer crnNo, Short locType1, boolean emptyPallet, LocMastService locMastService) {
         if (crnNo == null || locType1 == null) {
             return false;
         }
-        return locMastService.selectCount(new EntityWrapper<LocMast>()
-                .eq("crn_no", crnNo)
-                .eq("loc_sts", "O")
-                .eq("loc_type1", locType1)) > 0;
+        EntityWrapper<LocMast> wrapper = new EntityWrapper<LocMast>();
+        wrapper.eq("crn_no", crnNo);
+        wrapper.eq("loc_sts", "O");
+        wrapper.eq("loc_type1", locType1);
+        if (!emptyPallet) {
+            wrapper.ne("loc_type2", 1);
+        }
+        return locMastService.selectCount(wrapper) > 0;
     }
 
     private static Short normalizeLocType1(Integer locType1) {
diff --git a/src/main/java/com/zy/common/model/CrnDepthRuleProfile.java b/src/main/java/com/zy/common/model/CrnDepthRuleProfile.java
new file mode 100644
index 0000000..d1e0ff8
--- /dev/null
+++ b/src/main/java/com/zy/common/model/CrnDepthRuleProfile.java
@@ -0,0 +1,54 @@
+package com.zy.common.model;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class CrnDepthRuleProfile {
+
+    private Integer whsType;
+
+    private Integer crnNo;
+
+    private Integer layoutType;
+
+    private String source;
+
+    private List<Integer> searchRows = new ArrayList<Integer>();
+
+    private List<Integer> shallowRows = new ArrayList<Integer>();
+
+    private List<Integer> deepRows = new ArrayList<Integer>();
+
+    private Map<Integer, Integer> shallowToDeepRow = new LinkedHashMap<Integer, Integer>();
+
+    private Map<Integer, Integer> deepToShallowRow = new LinkedHashMap<Integer, Integer>();
+
+    public boolean isSingleExtension() {
+        return Integer.valueOf(1).equals(this.layoutType);
+    }
+
+    public boolean isDoubleExtension() {
+        return Integer.valueOf(2).equals(this.layoutType);
+    }
+
+    public boolean isShallowRow(Integer row) {
+        return row != null && this.shallowRows.contains(row);
+    }
+
+    public boolean isDeepRow(Integer row) {
+        return row != null && this.deepRows.contains(row);
+    }
+
+    public Integer getPairedDeepRow(Integer shallowRow) {
+        return this.shallowToDeepRow.get(shallowRow);
+    }
+
+    public Integer getPairedShallowRow(Integer deepRow) {
+        return this.deepToShallowRow.get(deepRow);
+    }
+}
diff --git a/src/main/java/com/zy/common/service/CommonService.java b/src/main/java/com/zy/common/service/CommonService.java
index 5627380..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,12 +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);
+        int currentRow = updateRowLastno.getCurrentRow() == null ? 0 : updateRowLastno.getCurrentRow();
+        updateRun2Cursor(updateRowLastno, searchResult.runnableCrnNos, locMast.getCrnNo(), currentRow);
     }
 
     /**
@@ -754,12 +1126,11 @@
      * 鎵ц椤哄簭锛�
      * 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(),
@@ -770,34 +1141,27 @@
             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);
     }
 
     /**
@@ -807,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);
@@ -849,6 +1206,9 @@
         return result;
     }
 
+    /**
+     * 瑙f瀽褰撳墠婧愮珯鍒扮洰鏍囧爢鍨涙満鐨勫叆搴撶洰鏍囩珯鍙枫��
+     */
     private Integer resolveTargetStaNo(RowLastno rowLastno, Integer staDescId, Integer sourceStaNo, Integer crnNo) {
         if (!Utils.BooleanWhsTypeSta(rowLastno, staDescId)) {
             return null;
@@ -862,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();
@@ -906,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,
@@ -978,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,
@@ -992,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);
@@ -1011,6 +1389,9 @@
         return candidateLoc;
     }
 
+    /**
+     * 浠庣珯鐐逛紭鍏堢骇閰嶇疆涓娊鍙栦笉閲嶅鐨勫爢鍨涙満椤哄簭銆�
+     */
     private List<Integer> extractCrnNos(List<Map<String, Integer>> crnLocTypeEntries) {
         LinkedHashSet<Integer> orderedCrnNos = new LinkedHashSet<>();
         if (Cools.isEmpty(crnLocTypeEntries)) {
@@ -1025,6 +1406,9 @@
         return new ArrayList<>(orderedCrnNos);
     }
 
+    /**
+     * 鐢ㄧ珯鐐逛紭鍏堢骇閲岀殑 locType1 瑕嗙洊鏈鏌ヨ鐨勯珮搴︽潯浠躲��
+     */
     private LocTypeDto buildRun2SearchLocTypeDto(LocTypeDto locTypeDto, Short candidateLocType1) {
         if (locTypeDto == null && candidateLocType1 == null) {
             return null;
@@ -1042,6 +1426,9 @@
         return searchLocTypeDto;
     }
 
+    /**
+     * 鎸夌珯鐐逛紭鍏堥厤缃洿鎺ユ煡鏌愬彴鍫嗗灈鏈轰笂鐨勭涓�涓彲鐢ㄧ┖搴撲綅銆�
+     */
     private LocMast findRun2OrderedEmptyLocByCrnLocType(RowLastnoType rowLastnoType, Integer candidateCrnNo,
                                                         Short candidateLocType1, LocTypeDto locTypeDto) {
         if (candidateCrnNo == null) {
@@ -1179,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,
@@ -1263,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;
@@ -1306,6 +1903,9 @@
         }
     }
 
+    /**
+     * 鍚戞悳绱㈡帓鍒楄〃杩藉姞涓�涓悎娉曚笖涓嶉噸澶嶇殑鎺掑彿銆�
+     */
     private void addSearchRow(List<Integer> searchRows, Integer row, RowLastno rowLastno) {
         if (row == null) {
             return;
@@ -1318,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;
@@ -1392,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);
     }
 
@@ -1672,8 +2193,6 @@
 
         int crnNo = 0;
         int nearRow = 0;
-        int curRow = 0;
-        int rowCount = 0;
         LocMast locMast = null;
 
         StartupDto startupDto = new StartupDto();
@@ -1686,30 +2205,26 @@
         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<>();
         if (emptyPalletRequest) {
-            // 绌烘墭鐩樺崟鐙寜搴撳尯杞锛�
-            // 1. 褰撳墠搴撳尯鍏堟壘 loc_type2=1
-            // 2. 褰撳墠搴撳尯娌℃湁锛屽啀鎵惧叾浠栧簱鍖� loc_type2=1
-            // 3. 鍏ㄩ儴 narrow 閮芥病鏈夋椂锛屽啀閫�鍒� loc_type1=2
-            emptyPalletAreaSearchResult = findEmptyPalletRun2AreaLoc(rowLastno, staDescId, sourceStaNo, startupDto, preferredArea, locTypeDto);
+            emptyPalletAreaSearchResult = findEmptyPalletRun2Loc(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);
+            normalRun2SearchResult = findNormalRun2Loc(rowLastno, rowLastnoType, sourceStaNo, staDescId, findLocNoAttributeVo,
+                    locTypeDto, startupDto, preferredArea, orderedCrnNos);
+            if (normalRun2SearchResult != null) {
+                locMast = normalRun2SearchResult.locMast;
+            }
         }
 
         if (!Cools.isEmpty(locMast)) {
@@ -1718,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("娌℃湁绌哄簱浣�");
         }
 
@@ -1744,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) {
diff --git a/src/main/java/com/zy/common/web/WcsController.java b/src/main/java/com/zy/common/web/WcsController.java
index a458757..560c2b4 100644
--- a/src/main/java/com/zy/common/web/WcsController.java
+++ b/src/main/java/com/zy/common/web/WcsController.java
@@ -81,10 +81,10 @@
 //            dto1.setTaskPri((int) Math.round(wrkMast1.getIoPri()));
             return R.ok(dto1);
         }
-        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("io_type", 1));
-        if (!Cools.isEmpty(wrkMasts) && wrkMasts.size() > 10) {
-            return R.error("闄愯");
-        }
+//        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("io_type", 1));
+//        if (!Cools.isEmpty(wrkMasts) && wrkMasts.size() > 10) {
+//            return R.error("闄愯");
+//        }
         waitPakins = waitPakinService.selectList(new EntityWrapper<WaitPakin>().eq("zpallet", param.getBarcode()));
         if (Cools.isEmpty(waitPakins)) {
             return R.error("璇峰厛娣诲姞鍏ュ簱閫氱煡妗�");
@@ -126,6 +126,9 @@
         BasDevp sourceStaNo = basDevpService.checkSiteStatus(param.getSourceStaNo(), true);
         sourceStaNo.setLocType1(param.getLocType1());
         LocTypeDto locTypeDto = new LocTypeDto(sourceStaNo);
+        if (waitPakins.get(0).getMatnr().equals("emptyPallet")) {
+            locTypeDto.setLocType2((short) 1);
+        }
 
         StartupDto dto = startupFullPutStore(param.getSourceStaNo(), param.getBarcode(), locTypeDto, waitPakins);
         log.info("WCS鍏ュ簱鎺ュ彛杩斿弬:{},鎵樼洏鐮�:{}", dto, param.getBarcode());
diff --git a/src/main/resources/mapper/BasCrnDepthRuleMapper.xml b/src/main/resources/mapper/BasCrnDepthRuleMapper.xml
new file mode 100644
index 0000000..93df705
--- /dev/null
+++ b/src/main/resources/mapper/BasCrnDepthRuleMapper.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zy.asrs.mapper.BasCrnDepthRuleMapper">
+
+    <resultMap id="BaseResultMap" type="com.zy.asrs.entity.BasCrnDepthRule">
+        <id column="id" property="id"/>
+        <result column="whs_type" property="whsType"/>
+        <result column="crn_no" property="crnNo"/>
+        <result column="layout_type" property="layoutType"/>
+        <result column="search_rows_csv" property="searchRowsCsv"/>
+        <result column="shallow_rows_csv" property="shallowRowsCsv"/>
+        <result column="deep_rows_csv" property="deepRowsCsv"/>
+        <result column="enabled" property="enabled"/>
+        <result column="memo" property="memo"/>
+        <result column="create_by" property="createBy"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_by" property="updateBy"/>
+        <result column="update_time" property="updateTime"/>
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/sql/20260321_bas_crn_depth_rule.sql b/src/main/resources/sql/20260321_bas_crn_depth_rule.sql
new file mode 100644
index 0000000..0d79b3c
--- /dev/null
+++ b/src/main/resources/sql/20260321_bas_crn_depth_rule.sql
@@ -0,0 +1,30 @@
+IF NOT EXISTS (
+    SELECT 1
+    FROM sys.objects
+    WHERE object_id = OBJECT_ID(N'[dbo].[asr_bas_crn_depth_rule]')
+      AND type = N'U'
+)
+BEGIN
+    CREATE TABLE [dbo].[asr_bas_crn_depth_rule] (
+        [id] BIGINT IDENTITY(1,1) NOT NULL,
+        [whs_type] INT NOT NULL,
+        [crn_no] INT NOT NULL,
+        [layout_type] INT NOT NULL,
+        [search_rows_csv] VARCHAR(255) NULL,
+        [shallow_rows_csv] VARCHAR(255) NULL,
+        [deep_rows_csv] VARCHAR(255) NULL,
+        [enabled] INT NOT NULL CONSTRAINT [DF_asr_bas_crn_depth_rule_enabled] DEFAULT ((1)),
+        [memo] VARCHAR(255) NULL,
+        [create_by] BIGINT NULL,
+        [create_time] DATETIME NULL,
+        [update_by] BIGINT NULL,
+        [update_time] DATETIME NULL,
+        CONSTRAINT [PK_asr_bas_crn_depth_rule] PRIMARY KEY CLUSTERED ([id] ASC),
+        CONSTRAINT [UK_asr_bas_crn_depth_rule_whs_crn] UNIQUE NONCLUSTERED ([whs_type] ASC, [crn_no] ASC)
+    );
+
+    EXEC sys.sp_addextendedproperty
+        @name = N'MS_Description', @value = N'鍫嗗灈鏈烘繁娴呭簱浣嶈鍒�',
+        @level0type = N'SCHEMA', @level0name = N'dbo',
+        @level1type = N'TABLE',  @level1name = N'asr_bas_crn_depth_rule';
+END;
diff --git a/src/main/resources/sql/20260322_row_lastno_current_crn_no.sql b/src/main/resources/sql/20260322_row_lastno_current_crn_no.sql
new file mode 100644
index 0000000..36a0b59
--- /dev/null
+++ b/src/main/resources/sql/20260322_row_lastno_current_crn_no.sql
@@ -0,0 +1,11 @@
+IF COL_LENGTH('dbo.asr_row_lastno', 'current_crn_no') IS NULL
+BEGIN
+    ALTER TABLE [dbo].[asr_row_lastno]
+        ADD [current_crn_no] INT NULL;
+
+    EXEC sys.sp_addextendedproperty
+        @name = N'MS_Description', @value = N'褰撳墠鍫嗗灈鏈哄彿',
+        @level0type = N'SCHEMA', @level0name = N'dbo',
+        @level1type = N'TABLE',  @level1name = N'asr_row_lastno',
+        @level2type = N'COLUMN', @level2name = N'current_crn_no';
+END;
diff --git a/src/main/webapp/static/js/basCrnDepthRule/basCrnDepthRule.js b/src/main/webapp/static/js/basCrnDepthRule/basCrnDepthRule.js
new file mode 100644
index 0000000..8980307
--- /dev/null
+++ b/src/main/webapp/static/js/basCrnDepthRule/basCrnDepthRule.js
@@ -0,0 +1,304 @@
+var pageCurr;
+layui.config({
+    base: baseUrl + "/static/layui/lay/modules/"
+}).use(['table', 'form', 'admin'], function () {
+    var table = layui.table;
+    var $ = layui.jquery;
+    var layer = layui.layer;
+    var form = layui.form;
+    var admin = layui.admin;
+
+    tableIns = table.render({
+        elem: '#basCrnDepthRule',
+        headers: {token: localStorage.getItem('token')},
+        url: baseUrl + '/basCrnDepthRule/list/auth',
+        page: true,
+        limit: 15,
+        limits: [15, 30, 50, 100, 200, 500],
+        toolbar: '#toolbar',
+        cellMinWidth: 80,
+        height: 'full-120',
+        cols: [[
+            {type: 'checkbox'}
+            , {field: 'whsType', align: 'center', title: '浠撳簱'}
+            , {field: 'crnNo', align: 'center', title: '鍫嗗灈鏈�'}
+            , {field: 'layoutType$', align: 'center', title: '甯冨眬'}
+            , {field: 'searchRowsCsv', align: 'center', title: '鎼滅储鎺�'}
+            , {field: 'shallowRowsCsv', align: 'center', title: '娴呮帓'}
+            , {field: 'deepRowsCsv', align: 'center', title: '娣辨帓'}
+            , {field: 'enabled$', align: 'center', title: '鍚敤'}
+            , {field: 'memo', align: 'center', title: '澶囨敞'}
+            , {field: 'updateBy$', align: 'center', title: '淇敼浜�', hide: true}
+            , {field: 'updateTime$', align: 'center', title: '淇敼鏃堕棿', hide: true}
+            , {fixed: 'right', title: '鎿嶄綔', align: 'center', toolbar: '#operate', width: 120}
+        ]],
+        request: {
+            pageName: 'curr',
+            pageSize: 'limit'
+        },
+        parseData: function (res) {
+            return {
+                code: res.code,
+                msg: res.msg,
+                count: res.data.total,
+                data: res.data.records
+            };
+        },
+        response: {
+            statusCode: 200
+        },
+        done: function (res, curr) {
+            if (res.code === 403) {
+                top.location.href = baseUrl + "/";
+            }
+            pageCurr = curr;
+            limit();
+        }
+    });
+
+    table.on('sort(basCrnDepthRule)', function (obj) {
+        var searchData = collectSearchData();
+        searchData.orderByField = obj.field;
+        searchData.orderByType = obj.type;
+        tableIns.reload({
+            where: searchData,
+            page: {curr: 1}
+        });
+    });
+
+    table.on('toolbar(basCrnDepthRule)', function (obj) {
+        var checkStatus = table.checkStatus(obj.config.id).data;
+        switch (obj.event) {
+            case 'addData':
+                showEditModel();
+                break;
+            case 'deleteData':
+                if (checkStatus.length === 0) {
+                    layer.msg('璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁', {icon: 2});
+                    return;
+                }
+                del(checkStatus);
+                break;
+            case 'exportData':
+                exportData(obj);
+                break;
+            case 'templatePreview':
+                openTemplateDialog(false);
+                break;
+            case 'templateGenerate':
+                openTemplateDialog(true);
+                break;
+            case 'runtimePreview':
+                openRuntimeDialog();
+                break;
+        }
+    });
+
+    table.on('tool(basCrnDepthRule)', function (obj) {
+        var data = obj.data;
+        switch (obj.event) {
+            case 'edit':
+                showEditModel(data);
+                break;
+            case 'del':
+                del([data]);
+                break;
+        }
+    });
+
+    function collectSearchData() {
+        var searchData = {};
+        $.each($('#search-box [name]').serializeArray(), function () {
+            searchData[this.name] = this.value;
+        });
+        return searchData;
+    }
+
+    function showEditModel(mData) {
+        admin.open({
+            type: 1,
+            area: '900px',
+            title: (mData ? '淇敼' : '鏂板') + '鍫嗗灈鏈烘繁娴呰鍒�',
+            content: $('#editDialog').html(),
+            success: function (layero, dIndex) {
+                form.val('detail', mData || {enabled: 1, layoutType: 2});
+                form.render('select');
+                form.on('submit(editSubmit)', function (data) {
+                    $.ajax({
+                        url: baseUrl + '/basCrnDepthRule/' + (mData ? 'update' : 'add') + '/auth',
+                        headers: {'token': localStorage.getItem('token')},
+                        data: data.field,
+                        method: 'POST',
+                        success: function (res) {
+                            if (res.code === 200) {
+                                layer.close(dIndex);
+                                layer.msg(res.msg, {icon: 1});
+                                tableReload();
+                            } else if (res.code === 403) {
+                                top.location.href = baseUrl + "/";
+                            } else {
+                                layer.msg(res.msg, {icon: 2});
+                            }
+                        }
+                    });
+                    return false;
+                });
+                $(layero).children('.layui-layer-content').css('overflow', 'visible');
+            }
+        });
+    }
+
+    function openTemplateDialog(generate) {
+        admin.open({
+            type: 1,
+            area: '520px',
+            title: generate ? '妯℃澘鐢熸垚' : '妯℃澘棰勮',
+            content: $('#templateDialog').html(),
+            success: function (layero, dIndex) {
+                form.val('templateForm', {enabled: 1});
+                form.render('select');
+                form.on('submit(templateSubmit)', function (data) {
+                    $.ajax({
+                        url: baseUrl + '/basCrnDepthRule/' + (generate ? 'templateGenerate' : 'templatePreview') + '/auth',
+                        headers: {'token': localStorage.getItem('token')},
+                        data: data.field,
+                        method: 'POST',
+                        success: function (res) {
+                            if (res.code === 200) {
+                                if (generate) {
+                                    layer.close(dIndex);
+                                    layer.msg(res.msg, {icon: 1});
+                                    tableReload();
+                                    return;
+                                }
+                                layer.alert('<pre style="white-space: pre-wrap;max-height: 480px;overflow:auto;">' +
+                                    JSON.stringify(res.data, null, 2) + '</pre>', {
+                                    title: '妯℃澘棰勮'
+                                });
+                            } else if (res.code === 403) {
+                                top.location.href = baseUrl + "/";
+                            } else {
+                                layer.msg(res.msg, {icon: 2});
+                            }
+                        }
+                    });
+                    return false;
+                });
+                $(layero).children('.layui-layer-content').css('overflow', 'visible');
+            }
+        });
+    }
+
+    function openRuntimeDialog() {
+        admin.open({
+            type: 1,
+            area: '560px',
+            title: '杩愯棰勮',
+            content: $('#runtimeDialog').html(),
+            success: function (layero) {
+                form.on('submit(runtimeSubmit)', function (data) {
+                    $.ajax({
+                        url: baseUrl + '/basCrnDepthRule/runtimePreview/auth',
+                        headers: {'token': localStorage.getItem('token')},
+                        data: data.field,
+                        method: 'POST',
+                        success: function (res) {
+                            if (res.code === 200) {
+                                layer.alert('<pre style="white-space: pre-wrap;max-height: 520px;overflow:auto;">' +
+                                    JSON.stringify(res.data, null, 2) + '</pre>', {
+                                    title: '杩愯棰勮缁撴灉'
+                                });
+                            } else if (res.code === 403) {
+                                top.location.href = baseUrl + "/";
+                            } else {
+                                layer.msg(res.msg, {icon: 2});
+                            }
+                        }
+                    });
+                    return false;
+                });
+                $(layero).children('.layui-layer-content').css('overflow', 'visible');
+            }
+        });
+    }
+
+    function exportData(obj) {
+        admin.confirm('纭畾瀵煎嚭Excel鍚�', {shadeClose: true}, function () {
+            var titles = [];
+            var fields = [];
+            obj.config.cols[0].map(function (col) {
+                if (col.type === 'normal' && col.hide === false && col.toolbar == null) {
+                    titles.push(col.title);
+                    fields.push(col.field);
+                }
+            });
+            var param = {
+                basCrnDepthRule: collectSearchData(),
+                fields: fields
+            };
+            $.ajax({
+                url: baseUrl + '/basCrnDepthRule/export/auth',
+                headers: {'token': localStorage.getItem('token')},
+                data: JSON.stringify(param),
+                dataType: 'json',
+                contentType: 'application/json;charset=UTF-8',
+                method: 'POST',
+                success: function (res) {
+                    if (res.code === 200) {
+                        table.exportFile(titles, res.data, 'xls');
+                    } else if (res.code === 403) {
+                        top.location.href = baseUrl + "/";
+                    } else {
+                        layer.msg(res.msg, {icon: 2});
+                    }
+                }
+            });
+        });
+    }
+
+    function del(rows) {
+        layer.confirm('纭畾瑕佸垹闄ら�変腑鏁版嵁鍚楋紵', {skin: 'layui-layer-admin', shade: .1}, function (i) {
+            layer.close(i);
+            $.ajax({
+                url: baseUrl + '/basCrnDepthRule/delete/auth',
+                headers: {'token': localStorage.getItem('token')},
+                data: {param: JSON.stringify(rows)},
+                method: 'POST',
+                success: function (res) {
+                    if (res.code === 200) {
+                        layer.msg(res.msg, {icon: 1});
+                        tableReload();
+                    } else if (res.code === 403) {
+                        top.location.href = baseUrl + "/";
+                    } else {
+                        layer.msg(res.msg, {icon: 2});
+                    }
+                }
+            });
+        });
+    }
+
+    form.on('submit(search)', function () {
+        pageCurr = 1;
+        tableReload();
+        return false;
+    });
+
+    form.on('submit(reset)', function () {
+        pageCurr = 1;
+        clearFormVal($('#search-box'));
+        tableReload();
+        return false;
+    });
+});
+
+function tableReload() {
+    var searchData = {};
+    $.each($('#search-box [name]').serializeArray(), function () {
+        searchData[this.name] = this.value;
+    });
+    tableIns.reload({
+        where: searchData,
+        page: {curr: pageCurr || 1}
+    });
+}
diff --git a/src/main/webapp/static/js/basDevp/basDevp.js b/src/main/webapp/static/js/basDevp/basDevp.js
index c970277..f67d853 100644
--- a/src/main/webapp/static/js/basDevp/basDevp.js
+++ b/src/main/webapp/static/js/basDevp/basDevp.js
@@ -596,9 +596,13 @@
 function setFormVal(el, data, showImg) {
     for (var val in data) {
         var find = el.find(":input[id='" + val + "']");
+        var currentVal = data[val];
+        if (val === 'area') {
+            currentVal = normalizeAreaValue(currentVal);
+        }
         if (find[0]!=null){
             if (find[0].type === 'checkbox'){
-                if (data[val]==='Y'){
+                if (currentVal==='Y'){
                     find.attr("checked","checked");
                     find.val('Y');
                 } else {
@@ -608,13 +612,13 @@
                 continue;
             }
         }
-        find.val(data[val]);
+        find.val(currentVal);
         if (showImg){
             var next = find.next();
             if (next.get(0)){
                 if (next.get(0).localName === "img") {
                     find.hide();
-                    next.attr("src", data[val]);
+                    next.attr("src", currentVal);
                     next.show();
                 }
             }
@@ -622,6 +626,23 @@
     }
 }
 
+function normalizeAreaValue(value) {
+    if (value === undefined || value === null) {
+        return value;
+    }
+    var normalized = String(value).replace(/\s+/g, '').toUpperCase();
+    if (normalized === '1' || normalized === 'A' || normalized === 'A鍖�' || normalized === 'A搴�' || normalized === 'A搴撳尯') {
+        return 'A';
+    }
+    if (normalized === '2' || normalized === 'B' || normalized === 'B鍖�' || normalized === 'B搴�' || normalized === 'B搴撳尯') {
+        return 'B';
+    }
+    if (normalized === '3' || normalized === 'C' || normalized === 'C鍖�' || normalized === 'C搴�' || normalized === 'C搴撳尯') {
+        return 'C';
+    }
+    return value;
+}
+
 function clearFormVal(el) {
     $(':input', el)
         .val('')
diff --git a/src/main/webapp/static/js/rowLastno/rowLastno.js b/src/main/webapp/static/js/rowLastno/rowLastno.js
index 7bd9ec6..4c4aa10 100644
--- a/src/main/webapp/static/js/rowLastno/rowLastno.js
+++ b/src/main/webapp/static/js/rowLastno/rowLastno.js
@@ -25,6 +25,7 @@
             ,{field: 'wrkMk', align: 'center',title: '褰撳墠宸ヤ綔鍙�', hide:false}
             ,{field: 'sRow', align: 'center',title: '璧峰鎺掑彿', hide:true}
             ,{field: 'currentRow', align: 'center',title: '褰撳墠鎺掑彿', style: 'color: #AA3130;font-weight: bold', hide:false}
+            ,{field: 'currentCrnNo', align: 'center',title: '褰撳墠鍫嗗灈鏈哄彿', style: 'color: #AA3130;font-weight: bold', hide:false}
             ,{field: 'eRow', align: 'center',title: '缁堟鎺掑彿', hide:true}
             ,{field: 'sCrnNo', align: 'center',title: '璧峰鍫嗗灈鏈哄彿', hide:true}
             ,{field: 'eCrnNo', align: 'center',title: '缁堟鍫嗗灈鏈哄彿', hide:true}
@@ -332,6 +333,7 @@
             whsType: $('#whsType').val(),
             wrkMk: $('#wrkMk').val(),
             currentRow: $('#currentRow').val(),
+            currentCrnNo: $('#currentCrnNo').val(),
             sRow: $('#sRow').val(),
             eRow: $('#eRow').val(),
             crnQty: $('#crnQty').val(),
diff --git a/src/main/webapp/views/basCrnDepthRule/basCrnDepthRule.html b/src/main/webapp/views/basCrnDepthRule/basCrnDepthRule.html
new file mode 100644
index 0000000..d228cdd
--- /dev/null
+++ b/src/main/webapp/views/basCrnDepthRule/basCrnDepthRule.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title></title>
+    <meta content="webkit" name="renderer">
+    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
+    <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+    <link href="../../static/layui/css/layui.css" media="all" rel="stylesheet">
+    <link href="../../static/css/admin.css?v=318" media="all" rel="stylesheet">
+    <link href="../../static/css/cool.css" media="all" rel="stylesheet">
+</head>
+<body>
+<div class="layui-fluid">
+    <div class="layui-card">
+        <div class="layui-card-body">
+            <div class="layui-form toolbar" id="search-box">
+                <div class="layui-form-item">
+                    <div class="layui-inline">
+                        <div class="layui-input-inline">
+                            <input autocomplete="off" class="layui-input" name="whsType" placeholder="浠撳簱绫诲瀷" type="number">
+                        </div>
+                    </div>
+                    <div class="layui-inline">
+                        <div class="layui-input-inline">
+                            <input autocomplete="off" class="layui-input" name="crnNo" placeholder="鍫嗗灈鏈哄彿" type="number">
+                        </div>
+                    </div>
+                    <div class="layui-inline">&emsp;
+                        <button class="layui-btn icon-btn" lay-filter="search" lay-submit>
+                            <i class="layui-icon">&#xe615;</i>鎼滅储
+                        </button>
+                        <button class="layui-btn icon-btn" lay-filter="reset" lay-submit>
+                            <i class="layui-icon">&#xe666;</i>閲嶇疆
+                        </button>
+                    </div>
+                </div>
+            </div>
+            <table class="layui-hide" id="basCrnDepthRule" lay-filter="basCrnDepthRule"></table>
+        </div>
+    </div>
+</div>
+
+<script id="toolbar" type="text/html">
+    <div class="layui-btn-container">
+        <button class="layui-btn layui-btn-sm" lay-event="addData">鏂板</button>
+        <button class="layui-btn layui-btn-sm" lay-event="templatePreview">妯℃澘棰勮</button>
+        <button class="layui-btn layui-btn-sm" lay-event="templateGenerate">妯℃澘鐢熸垚</button>
+        <button class="layui-btn layui-btn-sm" lay-event="runtimePreview">杩愯棰勮</button>
+        <button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="deleteData">鍒犻櫎</button>
+        <button class="layui-btn layui-btn-primary layui-btn-sm" lay-event="exportData" style="float: right">瀵煎嚭</button>
+    </div>
+</script>
+
+<script id="operate" type="text/html">
+    <a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">淇敼</a>
+    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">鍒犻櫎</a>
+</script>
+
+<script id="editDialog" type="text/html">
+    <form id="detail" lay-filter="detail" class="layui-form admin-form model-form">
+        <input name="id" type="hidden">
+        <div class="layui-row">
+            <div class="layui-col-md6">
+                <div class="layui-form-item">
+                    <label class="layui-form-label layui-form-required">浠撳簱</label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" lay-verify="required|number" name="whsType" placeholder="璇疯緭鍏ヤ粨搴撶被鍨�" type="number">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label layui-form-required">鍫嗗灈鏈�</label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" lay-verify="required|number" name="crnNo" placeholder="璇疯緭鍏ュ爢鍨涙満鍙�" type="number">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label layui-form-required">甯冨眬</label>
+                    <div class="layui-input-block">
+                        <select lay-verify="required" name="layoutType">
+                            <option value="">璇烽�夋嫨</option>
+                            <option value="1">鍗曚几</option>
+                            <option value="2">鍙屼几</option>
+                        </select>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label layui-form-required">鎼滅储鎺�</label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" lay-verify="required" name="searchRowsCsv" placeholder="渚�: 2,3 鎴� 2-3">
+                    </div>
+                </div>
+            </div>
+            <div class="layui-col-md6">
+                <div class="layui-form-item">
+                    <label class="layui-form-label">娴呮帓</label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" name="shallowRowsCsv" placeholder="鍗曚几鍙暀绌猴紝鍙屼几蹇呭~">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">娣辨帓</label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" name="deepRowsCsv" placeholder="鍙屼几蹇呭~">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">鍚敤</label>
+                    <div class="layui-input-block">
+                        <select name="enabled">
+                            <option value="1">鍚敤</option>
+                            <option value="0">绂佺敤</option>
+                        </select>
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">澶囨敞</label>
+                    <div class="layui-input-block">
+                        <textarea class="layui-textarea" name="memo" placeholder="澶囨敞"></textarea>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <hr class="layui-bg-gray">
+        <div class="layui-form-item text-right">
+            <button class="layui-btn" lay-filter="editSubmit" lay-submit="">淇濆瓨</button>
+            <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">鍙栨秷</button>
+        </div>
+    </form>
+</script>
+
+<script id="templateDialog" type="text/html">
+    <form id="templateForm" lay-filter="templateForm" class="layui-form admin-form model-form">
+        <div class="layui-form-item">
+            <label class="layui-form-label layui-form-required">浠撳簱</label>
+            <div class="layui-input-block">
+                <input class="layui-input" lay-verify="required|number" name="whsType" placeholder="璇疯緭鍏ヤ粨搴撶被鍨�" type="number">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">寮�濮嬪爢鍨涙満</label>
+            <div class="layui-input-block">
+                <input class="layui-input" name="startCrnNo" placeholder="鍙暀绌�" type="number">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">缁撴潫鍫嗗灈鏈�</label>
+            <div class="layui-input-block">
+                <input class="layui-input" name="endCrnNo" placeholder="鍙暀绌�" type="number">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">鍚敤</label>
+            <div class="layui-input-block">
+                <select name="enabled">
+                    <option value="1">鍚敤</option>
+                    <option value="0">绂佺敤</option>
+                </select>
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">澶囨敞</label>
+            <div class="layui-input-block">
+                <textarea class="layui-textarea" name="memo" placeholder="妯℃澘澶囨敞"></textarea>
+            </div>
+        </div>
+        <div class="layui-form-item text-right">
+            <button class="layui-btn" lay-filter="templateSubmit" lay-submit="">纭畾</button>
+            <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">鍙栨秷</button>
+        </div>
+    </form>
+</script>
+
+<script id="runtimeDialog" type="text/html">
+    <form id="runtimeForm" lay-filter="runtimeForm" class="layui-form admin-form model-form">
+        <div class="layui-form-item">
+            <label class="layui-form-label layui-form-required">璺緞ID</label>
+            <div class="layui-input-block">
+                <input class="layui-input" lay-verify="required|number" name="staDescId" placeholder="璇疯緭鍏ヨ矾寰処D" type="number">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label layui-form-required">婧愮珯</label>
+            <div class="layui-input-block">
+                <input class="layui-input" lay-verify="required|number" name="sourceStaNo" placeholder="璇疯緭鍏ユ簮绔欏彿" type="number">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">璇锋眰搴撳尯</label>
+            <div class="layui-input-block">
+                <input class="layui-input" name="outArea" placeholder="1/2/3" type="number">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">鐗╂枡缂栫爜</label>
+            <div class="layui-input-block">
+                <input class="layui-input" name="matnr" placeholder="emptyPallet 琛ㄧず绌烘墭鐩�">
+            </div>
+        </div>
+        <div class="layui-form-item">
+            <label class="layui-form-label">楂樹綆/瀹界獎/杞婚噸</label>
+            <div class="layui-input-block">
+                <div class="layui-row layui-col-space10">
+                    <div class="layui-col-xs4"><input class="layui-input" name="locType1" placeholder="locType1" type="number"></div>
+                    <div class="layui-col-xs4"><input class="layui-input" name="locType2" placeholder="locType2" type="number"></div>
+                    <div class="layui-col-xs4"><input class="layui-input" name="locType3" placeholder="locType3" type="number"></div>
+                </div>
+            </div>
+        </div>
+        <div class="layui-form-item text-right">
+            <button class="layui-btn" lay-filter="runtimeSubmit" lay-submit="">棰勮</button>
+            <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">鍙栨秷</button>
+        </div>
+    </form>
+</script>
+
+<script src="../../static/js/jquery/jquery-3.3.1.min.js" type="text/javascript"></script>
+<script charset="utf-8" src="../../static/layui/layui.js" type="text/javascript"></script>
+<script charset="utf-8" src="../../static/js/common.js" type="text/javascript"></script>
+<script charset="utf-8" src="../../static/js/cool.js" type="text/javascript"></script>
+<script charset="utf-8" src="../../static/js/basCrnDepthRule/basCrnDepthRule.js" type="text/javascript"></script>
+</body>
+</html>
diff --git a/src/main/webapp/views/basDevp/basDevp_detail.html b/src/main/webapp/views/basDevp/basDevp_detail.html
index 7383b57..1703e79 100644
--- a/src/main/webapp/views/basDevp/basDevp_detail.html
+++ b/src/main/webapp/views/basDevp/basDevp_detail.html
@@ -153,7 +153,12 @@
         <div class="layui-inline"  style="width:31%;">
             <label class="layui-form-label">缁戝畾搴撳尯锛�</label>
             <div class="layui-input-inline">
-                <input id="area" class="layui-input" type="text" placeholder="绌�=涓嶉檺鍒讹紝鏀寔 1/2/3 鎴� A/B/C">
+                <select id="area" class="layui-input">
+                    <option value="">涓嶉檺鍒�</option>
+                    <option value="A">A搴撳尯</option>
+                    <option value="B">B搴撳尯</option>
+                    <option value="C">C搴撳尯</option>
+                </select>
             </div>
         </div>
         <div class="layui-inline"  style="width:31%;display: none">
diff --git a/src/main/webapp/views/rowLastno/rowLastno_detail.html b/src/main/webapp/views/rowLastno/rowLastno_detail.html
index e4adbf0..a7a61fb 100644
--- a/src/main/webapp/views/rowLastno/rowLastno_detail.html
+++ b/src/main/webapp/views/rowLastno/rowLastno_detail.html
@@ -42,6 +42,12 @@
             </div>
         </div>
         <div class="layui-inline"  style="width:31%;">
+            <label class="layui-form-label">褰撳墠鍫嗗灈鏈哄彿锛�</label>
+            <div class="layui-input-inline">
+                <input id="currentCrnNo" class="layui-input" type="text">
+            </div>
+        </div>
+        <div class="layui-inline"  style="width:31%;">
             <label class="layui-form-label">璧峰鎺掑彿锛�</label>
             <div class="layui-input-inline">
                 <input id="sRow" class="layui-input" type="text">
diff --git a/src/test/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImplTest.java b/src/test/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImplTest.java
new file mode 100644
index 0000000..461340e
--- /dev/null
+++ b/src/test/java/com/zy/asrs/service/impl/BasCrnDepthRuleServiceImplTest.java
@@ -0,0 +1,157 @@
+package com.zy.asrs.service.impl;
+
+import com.zy.asrs.entity.BasCrnDepthRule;
+import com.zy.asrs.entity.RowLastno;
+import com.zy.asrs.entity.param.BasCrnDepthRuleTemplateParam;
+import com.zy.asrs.service.RowLastnoService;
+import com.zy.common.model.CrnDepthRuleProfile;
+import com.zy.common.properties.SlaveProperties;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class BasCrnDepthRuleServiceImplTest {
+
+    @Mock
+    private RowLastnoService rowLastnoService;
+
+    private BasCrnDepthRuleServiceImpl service;
+
+    @BeforeEach
+    void setUp() {
+        service = spy(new BasCrnDepthRuleServiceImpl());
+        ReflectionTestUtils.setField(service, "rowLastnoService", rowLastnoService);
+        ReflectionTestUtils.setField(service, "slaveProperties", buildSlaveProperties());
+    }
+
+    @Test
+    void validateRule_forSingleExtension_fillsShallowRowsAndNormalizesCsv() {
+        BasCrnDepthRule rule = new BasCrnDepthRule();
+        rule.setWhsType(1);
+        rule.setCrnNo(1);
+        rule.setLayoutType(1);
+        rule.setSearchRowsCsv("1, 2,2");
+
+        service.validateRule(rule);
+
+        assertEquals("1,2", rule.getSearchRowsCsv());
+        assertEquals("1,2", rule.getShallowRowsCsv());
+        assertEquals(null, rule.getDeepRowsCsv());
+        assertEquals(Integer.valueOf(1), rule.getEnabled());
+    }
+
+    @Test
+    void validateRule_forSingleExtension_rejectsDeepRows() {
+        BasCrnDepthRule rule = new BasCrnDepthRule();
+        rule.setWhsType(1);
+        rule.setCrnNo(1);
+        rule.setLayoutType(1);
+        rule.setSearchRowsCsv("1,2");
+        rule.setDeepRowsCsv("3");
+
+        assertThrows(RuntimeException.class, () -> service.validateRule(rule));
+    }
+
+    @Test
+    void validateRule_forDoubleExtension_requiresBothShallowAndDeepRows() {
+        BasCrnDepthRule rule = new BasCrnDepthRule();
+        rule.setWhsType(1);
+        rule.setCrnNo(1);
+        rule.setLayoutType(2);
+        rule.setSearchRowsCsv("2,1");
+        rule.setShallowRowsCsv("2");
+
+        assertThrows(RuntimeException.class, () -> service.validateRule(rule));
+    }
+
+    @Test
+    void resolveProfile_prefersConfiguredRule() {
+        RowLastno rowLastno = new RowLastno();
+        rowLastno.setWhsType(1);
+        BasCrnDepthRule rule = new BasCrnDepthRule();
+        rule.setWhsType(1);
+        rule.setCrnNo(2);
+        rule.setLayoutType(2);
+        rule.setSearchRowsCsv("6,5");
+        rule.setShallowRowsCsv("6");
+        rule.setDeepRowsCsv("5");
+
+        doReturn(rule).when(service).findEnabledRule(1, 2);
+
+        CrnDepthRuleProfile profile = service.resolveProfile(rowLastno, 2, 6);
+
+        assertEquals(Integer.valueOf(2), profile.getLayoutType());
+        assertEquals(Arrays.asList(6, 5), profile.getSearchRows());
+        assertEquals(Arrays.asList(6), profile.getShallowRows());
+        assertEquals(Arrays.asList(5), profile.getDeepRows());
+        assertEquals(Integer.valueOf(5), profile.getPairedDeepRow(6));
+    }
+
+    @Test
+    void previewTemplate_buildsLegacyRowsForSingleExtensionWarehouse() {
+        RowLastno rowLastno = new RowLastno();
+        rowLastno.setWhsType(1);
+        rowLastno.setsCrnNo(1);
+        rowLastno.seteCrnNo(2);
+        rowLastno.setsRow(1);
+        rowLastno.seteRow(4);
+        rowLastno.setTypeId(2);
+        when(rowLastnoService.selectById(1)).thenReturn(rowLastno);
+
+        BasCrnDepthRuleTemplateParam param = new BasCrnDepthRuleTemplateParam();
+        param.setWhsType(1);
+
+        List<BasCrnDepthRule> preview = service.previewTemplate(param);
+
+        assertEquals(2, preview.size());
+        assertEquals(Integer.valueOf(1), preview.get(0).getLayoutType());
+        assertEquals("1,2", preview.get(0).getSearchRowsCsv());
+        assertEquals("3,4", preview.get(1).getSearchRowsCsv());
+    }
+
+    @Test
+    void previewTemplate_buildsLegacyRowsForDoubleExtensionWarehouse() {
+        RowLastno rowLastno = new RowLastno();
+        rowLastno.setWhsType(1);
+        rowLastno.setsCrnNo(1);
+        rowLastno.seteCrnNo(1);
+        rowLastno.setsRow(1);
+        rowLastno.seteRow(4);
+        rowLastno.setTypeId(1);
+        when(rowLastnoService.selectById(1)).thenReturn(rowLastno);
+
+        BasCrnDepthRuleTemplateParam param = new BasCrnDepthRuleTemplateParam();
+        param.setWhsType(1);
+
+        List<BasCrnDepthRule> preview = service.previewTemplate(param);
+
+        assertEquals(1, preview.size());
+        assertEquals("2,3", preview.get(0).getSearchRowsCsv());
+        assertTrue(preview.get(0).getShallowRowsCsv().contains("2"));
+        assertTrue(preview.get(0).getDeepRowsCsv().contains("1"));
+    }
+
+    private SlaveProperties buildSlaveProperties() {
+        SlaveProperties slaveProperties = new SlaveProperties();
+        slaveProperties.setDoubleDeep(true);
+        slaveProperties.setDoubleLocs(Arrays.asList(1, 4, 5, 8));
+        slaveProperties.setDoubleLocsLeft(Arrays.asList(1, 5));
+        slaveProperties.setDoubleLocsRight(Arrays.asList(4, 8));
+        slaveProperties.setGroupCount(4);
+        return slaveProperties;
+    }
+}
diff --git a/src/test/java/com/zy/common/service/CommonServiceLocTypeStrategyTest.java b/src/test/java/com/zy/common/service/CommonServiceLocTypeStrategyTest.java
new file mode 100644
index 0000000..528ba68
--- /dev/null
+++ b/src/test/java/com/zy/common/service/CommonServiceLocTypeStrategyTest.java
@@ -0,0 +1,165 @@
+package com.zy.common.service;
+
+import com.zy.asrs.entity.LocMast;
+import com.zy.asrs.entity.result.FindLocNoAttributeVo;
+import com.zy.common.model.LocTypeDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CommonServiceLocTypeStrategyTest {
+
+    private final CommonService commonService = new CommonService();
+
+    @Test
+    void normalizeLocTypeDto_forFullPallet_ignoresLocType2AndLocType3() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("MAT-001");
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1((short) 1);
+        locTypeDto.setLocType2((short) 1);
+        locTypeDto.setLocType3((short) 2);
+
+        LocTypeDto normalized = ReflectionTestUtils.invokeMethod(
+                commonService, "normalizeLocTypeDto", 1, attributeVo, locTypeDto);
+
+        assertEquals(Short.valueOf((short) 1), normalized.getLocType1());
+        org.junit.jupiter.api.Assertions.assertNull(normalized.getLocType2());
+        org.junit.jupiter.api.Assertions.assertNull(normalized.getLocType3());
+    }
+
+    @Test
+    void normalizeLocTypeDto_forEmptyPallet_prefersNarrowAndKeepsHeight() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("emptyPallet");
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1((short) 1);
+        locTypeDto.setLocType3((short) 2);
+
+        LocTypeDto normalized = ReflectionTestUtils.invokeMethod(
+                commonService, "normalizeLocTypeDto", 10, attributeVo, locTypeDto);
+
+        assertEquals(Short.valueOf((short) 1), normalized.getLocType1());
+        assertEquals(Short.valueOf((short) 1), normalized.getLocType2());
+        assertEquals(Short.valueOf((short) 2), normalized.getLocType3());
+    }
+
+    @Test
+    void buildRetryCompatibleLocTypeDto_forEmptyPallet_onlyRaisesHeightAndKeepsOtherLocTypes() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("emptyPallet");
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1((short) 1);
+        locTypeDto.setLocType2((short) 1);
+        locTypeDto.setLocType3((short) 2);
+
+        LocTypeDto compatible = ReflectionTestUtils.invokeMethod(
+                commonService, "buildRetryCompatibleLocTypeDto", 10, attributeVo, locTypeDto);
+
+        assertEquals(Short.valueOf((short) 2), compatible.getLocType1());
+        assertEquals(Short.valueOf((short) 1), compatible.getLocType2());
+        assertEquals(Short.valueOf((short) 2), compatible.getLocType3());
+    }
+
+    @Test
+    void buildRetryCompatibleLocTypeDto_forFullPallet_onlyRaisesHeight() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("MAT-001");
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1((short) 1);
+        locTypeDto.setLocType2((short) 2);
+        locTypeDto.setLocType3((short) 2);
+        LocTypeDto normalized = ReflectionTestUtils.invokeMethod(
+                commonService, "normalizeLocTypeDto", 1, attributeVo, locTypeDto);
+
+        LocTypeDto compatible = ReflectionTestUtils.invokeMethod(
+                commonService, "buildRetryCompatibleLocTypeDto", 1, attributeVo, normalized);
+
+        assertEquals(Short.valueOf((short) 2), compatible.getLocType1());
+        org.junit.jupiter.api.Assertions.assertNull(compatible.getLocType2());
+        org.junit.jupiter.api.Assertions.assertNull(compatible.getLocType3());
+    }
+
+    @Test
+    void wcsFullPalletPayload_whenLowLocExhausted_shouldRetryHighLocWithoutLocType2OrLocType3() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("MAT-80000001");
+        LocTypeDto requestLocType = new LocTypeDto();
+        requestLocType.setLocType1((short) 1);
+        requestLocType.setLocType2((short) 2);
+        requestLocType.setLocType3((short) 2);
+        LocTypeDto normalized = ReflectionTestUtils.invokeMethod(
+                commonService, "normalizeLocTypeDto", 1, attributeVo, requestLocType);
+
+        LocTypeDto compatible = ReflectionTestUtils.invokeMethod(
+                commonService, "buildRetryCompatibleLocTypeDto", 1, attributeVo, normalized);
+
+        assertEquals(Short.valueOf((short) 2), compatible.getLocType1());
+        org.junit.jupiter.api.Assertions.assertNull(compatible.getLocType2());
+        org.junit.jupiter.api.Assertions.assertNull(compatible.getLocType3());
+    }
+
+    @Test
+    void matchesLocType_forFullPallet_rejectsNarrowLocType2() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("MAT-001");
+        LocTypeDto requestLocType = new LocTypeDto();
+        requestLocType.setLocType1((short) 1);
+        LocTypeDto normalized = ReflectionTestUtils.invokeMethod(
+                commonService, "normalizeLocTypeDto", 1, attributeVo, requestLocType);
+        LocMast narrowLoc = new LocMast();
+        narrowLoc.setLocType1((short) 1);
+        narrowLoc.setLocType2((short) 1);
+
+        Boolean matched = ReflectionTestUtils.invokeMethod(
+                commonService, "matchesLocType", narrowLoc, normalized);
+
+        assertFalse(Boolean.TRUE.equals(matched));
+    }
+
+    @Test
+    void matchesLocType_forFullPallet_acceptsNonNarrowLocType2() {
+        FindLocNoAttributeVo attributeVo = new FindLocNoAttributeVo();
+        attributeVo.setMatnr("MAT-001");
+        LocTypeDto requestLocType = new LocTypeDto();
+        requestLocType.setLocType1((short) 1);
+        LocTypeDto normalized = ReflectionTestUtils.invokeMethod(
+                commonService, "normalizeLocTypeDto", 1, attributeVo, requestLocType);
+        LocMast wideLoc = new LocMast();
+        wideLoc.setLocType1((short) 1);
+        wideLoc.setLocType2((short) 2);
+
+        Boolean matched = ReflectionTestUtils.invokeMethod(
+                commonService, "matchesLocType", wideLoc, normalized);
+
+        assertTrue(Boolean.TRUE.equals(matched));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    void buildEmptyPalletSearchLocTypes_forLowLoc_returnsConfiguredFallbackOrder() {
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1((short) 1);
+        locTypeDto.setLocType2((short) 1);
+        locTypeDto.setLocType3((short) 2);
+
+        List<LocTypeDto> stages = ReflectionTestUtils.invokeMethod(
+                commonService, "buildEmptyPalletSearchLocTypes", locTypeDto);
+
+        assertEquals(4, stages.size());
+        assertEquals(Short.valueOf((short) 1), stages.get(0).getLocType1());
+        assertEquals(Short.valueOf((short) 1), stages.get(0).getLocType2());
+        assertEquals(Short.valueOf((short) 1), stages.get(1).getLocType1());
+        org.junit.jupiter.api.Assertions.assertNull(stages.get(1).getLocType2());
+        assertEquals(Short.valueOf((short) 2), stages.get(2).getLocType1());
+        assertEquals(Short.valueOf((short) 1), stages.get(2).getLocType2());
+        assertEquals(Short.valueOf((short) 2), stages.get(3).getLocType1());
+        org.junit.jupiter.api.Assertions.assertNull(stages.get(3).getLocType2());
+        assertEquals(Short.valueOf((short) 2), stages.get(3).getLocType3());
+    }
+}
diff --git a/src/test/java/com/zy/common/service/CommonServiceRun2AllocationTest.java b/src/test/java/com/zy/common/service/CommonServiceRun2AllocationTest.java
new file mode 100644
index 0000000..2d8a14f
--- /dev/null
+++ b/src/test/java/com/zy/common/service/CommonServiceRun2AllocationTest.java
@@ -0,0 +1,329 @@
+package com.zy.common.service;
+
+import com.baomidou.mybatisplus.mapper.Wrapper;
+import com.zy.asrs.entity.BasCrnp;
+import com.zy.asrs.entity.LocMast;
+import com.zy.asrs.entity.RowLastno;
+import com.zy.asrs.entity.RowLastnoType;
+import com.zy.asrs.service.BasCrnDepthRuleService;
+import com.zy.asrs.service.BasCrnpService;
+import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.service.LocMastService;
+import com.zy.asrs.service.RowLastnoService;
+import com.zy.asrs.service.StaDescService;
+import com.zy.common.model.CrnDepthRuleProfile;
+import com.zy.common.model.LocTypeDto;
+import com.zy.common.model.StartupDto;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class CommonServiceRun2AllocationTest {
+
+    @Mock
+    private BasCrnpService basCrnpService;
+    @Mock
+    private StaDescService staDescService;
+    @Mock
+    private BasDevpService basDevpService;
+    @Mock
+    private LocMastService locMastService;
+    @Mock
+    private BasCrnDepthRuleService basCrnDepthRuleService;
+    @Mock
+    private RowLastnoService rowLastnoService;
+
+    private CommonService commonService;
+
+    @BeforeEach
+    void setUp() {
+        commonService = new CommonService();
+        ReflectionTestUtils.setField(commonService, "basCrnpService", basCrnpService);
+        ReflectionTestUtils.setField(commonService, "staDescService", staDescService);
+        ReflectionTestUtils.setField(commonService, "basDevpService", basDevpService);
+        ReflectionTestUtils.setField(commonService, "locMastService", locMastService);
+        ReflectionTestUtils.setField(commonService, "basCrnDepthRuleService", basCrnDepthRuleService);
+        ReflectionTestUtils.setField(commonService, "rowLastnoService", rowLastnoService);
+    }
+
+    @Test
+    void findRun2EmptyLocByCrnNos_shouldStopAtFirstMatchingCraneInOrder() {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 2, 1, 4);
+        RowLastnoType rowLastnoType = standardRowLastnoType();
+        StartupDto startupDto = new StartupDto();
+        LocTypeDto locTypeDto = fullLocType((short) 1);
+        LocMast firstCraneLoc = openLoc("0200101", 2, 2, 1, 1);
+
+        when(basCrnpService.selectById(2)).thenReturn(activeCrn(2));
+        when(basCrnDepthRuleService.resolveProfile(eq(rowLastno), eq(2), any()))
+                .thenReturn(singleExtensionProfile(1, 2));
+        when(locMastService.selectList(any())).thenReturn(Collections.singletonList(firstCraneLoc));
+
+        LocMast result = ReflectionTestUtils.invokeMethod(commonService, "findRun2EmptyLocByCrnNos",
+                rowLastno, rowLastnoType, Arrays.asList(2, 1), locTypeDto,
+                1, 100, startupDto, 1, "run2-order", false);
+
+        assertEquals("0200101", result.getLocNo());
+        assertEquals(Integer.valueOf(2), result.getCrnNo());
+        verify(locMastService, times(1)).selectList(any());
+    }
+
+    @Test
+    void findRun2EmptyLocByCrnNos_shouldSkipInactiveCrane() {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 2, 1, 4);
+        RowLastnoType rowLastnoType = standardRowLastnoType();
+        StartupDto startupDto = new StartupDto();
+        LocTypeDto locTypeDto = fullLocType((short) 1);
+        LocMast secondCraneLoc = openLoc("0400101", 1, 4, 1, 1);
+
+        when(basCrnpService.selectById(2)).thenReturn(inactiveCrn(2));
+        when(basCrnpService.selectById(1)).thenReturn(activeCrn(1));
+        when(basCrnDepthRuleService.resolveProfile(eq(rowLastno), eq(1), any()))
+                .thenReturn(singleExtensionProfile(1, 4));
+        when(locMastService.selectList(any())).thenReturn(Collections.singletonList(secondCraneLoc));
+
+        LocMast result = ReflectionTestUtils.invokeMethod(commonService, "findRun2EmptyLocByCrnNos",
+                rowLastno, rowLastnoType, Arrays.asList(2, 1), locTypeDto,
+                1, 100, startupDto, 1, "run2-skip-offline", false);
+
+        assertEquals("0400101", result.getLocNo());
+        assertEquals(Integer.valueOf(1), result.getCrnNo());
+        verify(locMastService, times(1)).selectList(any());
+    }
+
+    @Test
+    void findOpenLocsByRow_forSingleExtension_shouldOrderByLevThenBay() {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 1, 1, 2);
+        RowLastnoType rowLastnoType = standardRowLastnoType();
+
+        when(locMastService.selectList(any())).thenReturn(Collections.emptyList());
+
+        ReflectionTestUtils.invokeMethod(commonService, "findOpenLocsByRow",
+                rowLastno, rowLastnoType, 1, 1, fullLocType((short) 1), true);
+
+        @SuppressWarnings("rawtypes")
+        ArgumentCaptor<Wrapper> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
+        verify(locMastService).selectList(wrapperCaptor.capture());
+        String sqlSegment = wrapperCaptor.getValue().getSqlSegment().toLowerCase();
+
+        assertTrue(sqlSegment.contains("order by"));
+        assertTrue(sqlSegment.indexOf("lev1") < sqlSegment.indexOf("bay1"));
+    }
+
+    @Test
+    void findConfiguredEmptyLocForCrn_forDoubleExtension_shouldPreferDeepOpenLoc() {
+        RowLastno rowLastno = doubleExtensionRowLastno();
+        RowLastnoType rowLastnoType = standardRowLastnoType();
+        LocMast shallowLoc = openLoc("0100101", 1, 1, 1, 1);
+        LocMast deepOpenLoc = openLoc("0200101", 1, 2, 1, 1);
+
+        when(basCrnDepthRuleService.resolveProfile(eq(rowLastno), eq(1), any()))
+                .thenReturn(doubleExtensionProfile());
+        when(locMastService.selectList(any())).thenReturn(Collections.singletonList(shallowLoc));
+        when(locMastService.selectOne(any())).thenReturn(deepOpenLoc);
+
+        LocMast result = ReflectionTestUtils.invokeMethod(commonService, "findConfiguredEmptyLocForCrn",
+                rowLastno, rowLastnoType, 1, 1, fullLocType((short) 1));
+
+        assertEquals("0200101", result.getLocNo());
+    }
+
+    @Test
+    void findConfiguredEmptyLocForCrn_forDoubleExtension_shouldFallbackToShallowWhenDeepBlocked() {
+        RowLastno rowLastno = doubleExtensionRowLastno();
+        RowLastnoType rowLastnoType = standardRowLastnoType();
+        LocMast shallowLoc = openLoc("0100101", 1, 1, 1, 1);
+        LocMast deepBlockedLoc = blockedLoc("0200101", 1, 2, 1, 1, "F");
+
+        when(basCrnDepthRuleService.resolveProfile(eq(rowLastno), eq(1), any()))
+                .thenReturn(doubleExtensionProfile());
+        when(locMastService.selectList(any())).thenReturn(Collections.singletonList(shallowLoc));
+        when(locMastService.selectOne(any())).thenReturn(null, deepBlockedLoc);
+
+        LocMast result = ReflectionTestUtils.invokeMethod(commonService, "findConfiguredEmptyLocForCrn",
+                rowLastno, rowLastnoType, 1, 1, fullLocType((short) 1));
+
+        assertEquals("0100101", result.getLocNo());
+    }
+
+    @Test
+    void getNextRun2CurrentRow_shouldAdvanceWithinRunnableCrnsOnly() {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 4, 1, 8);
+
+        Integer nextRow = ReflectionTestUtils.invokeMethod(commonService, "getNextRun2CurrentRow",
+                rowLastno, Arrays.asList(2, 4), 2, 3);
+
+        assertEquals(Integer.valueOf(7), nextRow);
+    }
+
+    @Test
+    void resolveRun2CrnNo_shouldPreferCurrentCrnNoOverCurrentRow() {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 4, 1, 8);
+        rowLastno.setCurrentRow(7);
+        rowLastno.setCurrentCrnNo(2);
+
+        Integer currentCrnNo = ReflectionTestUtils.invokeMethod(commonService, "resolveRun2CrnNo", rowLastno);
+
+        assertEquals(Integer.valueOf(2), currentCrnNo);
+    }
+
+    @Test
+    void resolveRun2CrnNo_shouldFallbackToStartCrnNoWhenCurrentCrnNoIsInvalid() {
+        RowLastno rowLastno = singleExtensionRowLastno(2, 4, 3, 8);
+
+        rowLastno.setCurrentCrnNo(null);
+        assertEquals(Integer.valueOf(2),
+                ReflectionTestUtils.invokeMethod(commonService, "resolveRun2CrnNo", rowLastno));
+
+        rowLastno.setCurrentCrnNo(0);
+        assertEquals(Integer.valueOf(2),
+                ReflectionTestUtils.invokeMethod(commonService, "resolveRun2CrnNo", rowLastno));
+
+        rowLastno.setCurrentCrnNo(5);
+        assertEquals(Integer.valueOf(2),
+                ReflectionTestUtils.invokeMethod(commonService, "resolveRun2CrnNo", rowLastno));
+    }
+
+    @Test
+    void advanceNormalRun2Cursor_shouldUpdateCurrentRowAndCurrentCrnNoTogether() {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 4, 1, 8);
+        rowLastno.setCurrentRow(1);
+        rowLastno.setCurrentCrnNo(1);
+
+        ReflectionTestUtils.invokeMethod(commonService, "advanceNormalRun2Cursor",
+                rowLastno, 1, Arrays.asList(1, 3), 1);
+
+        assertEquals(Integer.valueOf(5), rowLastno.getCurrentRow());
+        assertEquals(Integer.valueOf(3), rowLastno.getCurrentCrnNo());
+        verify(rowLastnoService).updateById(rowLastno);
+    }
+
+    @Test
+    void advanceEmptyPalletRun2Cursor_shouldUpdateCurrentRowAndCurrentCrnNoTogether() throws Exception {
+        RowLastno rowLastno = singleExtensionRowLastno(1, 4, 1, 8);
+        rowLastno.setCurrentRow(5);
+        rowLastno.setCurrentCrnNo(3);
+        LocMast locMast = openLoc("0500101", 3, 5, 1, 1);
+        Object searchResult = run2AreaSearchResult(locMast, rowLastno, Arrays.asList(1, 3));
+
+        ReflectionTestUtils.invokeMethod(commonService, "advanceEmptyPalletRun2Cursor", searchResult, locMast);
+
+        assertEquals(Integer.valueOf(1), rowLastno.getCurrentRow());
+        assertEquals(Integer.valueOf(1), rowLastno.getCurrentCrnNo());
+        verify(rowLastnoService).updateById(rowLastno);
+    }
+
+    private RowLastno singleExtensionRowLastno(int startCrnNo, int endCrnNo, int startRow, int endRow) {
+        RowLastno rowLastno = new RowLastno();
+        rowLastno.setWhsType(1);
+        rowLastno.setsCrnNo(startCrnNo);
+        rowLastno.seteCrnNo(endCrnNo);
+        rowLastno.setsRow(startRow);
+        rowLastno.seteRow(endRow);
+        rowLastno.setTypeId(2);
+        return rowLastno;
+    }
+
+    private RowLastno doubleExtensionRowLastno() {
+        RowLastno rowLastno = new RowLastno();
+        rowLastno.setWhsType(1);
+        rowLastno.setsCrnNo(1);
+        rowLastno.seteCrnNo(1);
+        rowLastno.setsRow(1);
+        rowLastno.seteRow(2);
+        rowLastno.setTypeId(1);
+        return rowLastno;
+    }
+
+    private RowLastnoType standardRowLastnoType() {
+        RowLastnoType rowLastnoType = new RowLastnoType();
+        rowLastnoType.setType(1);
+        return rowLastnoType;
+    }
+
+    private Object run2AreaSearchResult(LocMast locMast, RowLastno rowLastno, List<Integer> runnableCrnNos) throws Exception {
+        Class<?> clazz = Class.forName("com.zy.common.service.CommonService$Run2AreaSearchResult");
+        Constructor<?> constructor = clazz.getDeclaredConstructor(LocMast.class, RowLastno.class, List.class);
+        constructor.setAccessible(true);
+        return constructor.newInstance(locMast, rowLastno, runnableCrnNos);
+    }
+
+    private CrnDepthRuleProfile singleExtensionProfile(int shallowRow, int searchRow) {
+        CrnDepthRuleProfile profile = new CrnDepthRuleProfile();
+        profile.setLayoutType(1);
+        profile.setSearchRows(Collections.singletonList(searchRow));
+        profile.setShallowRows(Collections.singletonList(shallowRow));
+        return profile;
+    }
+
+    private CrnDepthRuleProfile doubleExtensionProfile() {
+        CrnDepthRuleProfile profile = new CrnDepthRuleProfile();
+        profile.setLayoutType(2);
+        profile.setSearchRows(Arrays.asList(1, 2));
+        profile.setShallowRows(Collections.singletonList(1));
+        profile.setDeepRows(Collections.singletonList(2));
+        profile.getShallowToDeepRow().put(1, 2);
+        profile.getDeepToShallowRow().put(2, 1);
+        return profile;
+    }
+
+    private BasCrnp activeCrn(int crnNo) {
+        BasCrnp basCrnp = new BasCrnp();
+        basCrnp.setCrnNo(crnNo);
+        basCrnp.setInEnable("Y");
+        basCrnp.setOutEnable("Y");
+        basCrnp.setCrnSts(3);
+        basCrnp.setCrnErr(0L);
+        return basCrnp;
+    }
+
+    private BasCrnp inactiveCrn(int crnNo) {
+        BasCrnp basCrnp = activeCrn(crnNo);
+        basCrnp.setInEnable("N");
+        return basCrnp;
+    }
+
+    private LocTypeDto fullLocType(short locType1) {
+        LocTypeDto locTypeDto = new LocTypeDto();
+        locTypeDto.setLocType1(locType1);
+        return locTypeDto;
+    }
+
+    private LocMast openLoc(String locNo, int crnNo, int row, int bay, int lev) {
+        LocMast locMast = new LocMast();
+        locMast.setLocNo(locNo);
+        locMast.setCrnNo(crnNo);
+        locMast.setWhsType(1L);
+        locMast.setRow1(row);
+        locMast.setBay1(bay);
+        locMast.setLev1(lev);
+        locMast.setLocSts("O");
+        locMast.setLocType1((short) 1);
+        locMast.setLocType2((short) 2);
+        return locMast;
+    }
+
+    private LocMast blockedLoc(String locNo, int crnNo, int row, int bay, int lev, String locSts) {
+        LocMast locMast = openLoc(locNo, crnNo, row, bay, lev);
+        locMast.setLocSts(locSts);
+        return locMast;
+    }
+}

--
Gitblit v1.9.1