From 5677fd88b56cd69e416b52144734f3997ef8f8f4 Mon Sep 17 00:00:00 2001
From: cl <1442464845@qq.com>
Date: 星期二, 28 四月 2026 16:29:12 +0800
Subject: [PATCH] 找库位

---
 src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java |  593 ++++++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 402 insertions(+), 191 deletions(-)

diff --git a/src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java b/src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java
index 3d39bb7..1315844 100644
--- a/src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java
+++ b/src/main/java/com/zy/api/service/impl/WcsApiServiceImpl.java
@@ -7,6 +7,7 @@
 import com.core.common.Cools;
 import com.core.common.R;
 import com.core.exception.CoolException;
+import com.zy.api.controller.params.ChangeLocParams;
 import com.zy.api.controller.params.ReassignLocParams;
 import com.zy.api.controller.params.ReceviceTaskParams;
 import com.zy.api.controller.params.StopOutTaskParams;
@@ -17,21 +18,21 @@
 import com.zy.api.service.WcsApiService;
 import com.zy.asrs.entity.*;
 import com.zy.asrs.service.*;
+import com.zy.asrs.task.support.OutboundBatchSeqReleaseGuard;
 import com.zy.asrs.utils.Utils;
 import com.zy.common.constant.MesConstant;
 import com.zy.common.model.LocTypeDto;
 import com.zy.common.model.StartupDto;
+import com.zy.common.model.CrnDepthRuleProfile;
+import com.zy.common.model.enums.WorkNoType;
 import com.zy.common.service.CommonService;
 import com.zy.common.utils.HttpHandler;
-import com.zy.common.utils.RedisUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronizationAdapter;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import java.io.IOException;
 import java.math.BigDecimal;
@@ -49,6 +50,8 @@
     private static final long OUT_LOCK_REPORT_PENDING_WRK_STS = 13L;
     private static final long OUT_LOCK_REPORT_SUCCESS_WRK_STS = 21L;
     private static final long OUT_LOCK_REPORT_FAIL_WRK_STS = 22L;
+    private static final long OUTBOUND_CRN_COMPLETE_WRK_STS = 25L;
+    private static final long OUTBOUND_STATION_COMPLETE_WRK_STS = 26L;
     private static final String OUT_LOCK_REPORT_PENDING_FLAG = "P";
 
     /** 鍚屼竴 WCS 璺緞銆佸悓涓�鍗曞彿涓嬩竴缁勪笅鍙戠殑浠诲姟鏉℃暟涓婇檺 */
@@ -56,8 +59,19 @@
 
     /** 涓夋柟鎺ュ彛缁熻锛氭湰绯荤粺璋冪敤 WCS 鐨� namespace 绾﹀畾 */
     private static final String NS_WMS_TO_WCS = "鏈郴缁熻姹俉CS";
-    private static final String REASSIGN_CRN_LOCK_KEY_PREFIX = "wcs:reassign:inbound:crn:";
-    private static final long REASSIGN_CRN_LOCK_SECONDS = 180L;
+    private static final String EMPTY_PALLET_MATNR = "emptyPallet";
+    private static final int EMPTY_PALLET_REASSIGN_TARGET_LEVEL = 8;
+    private static final short EMPTY_PALLET_REASSIGN_LOC_TYPE1 = 3;
+    private static final short EMPTY_PALLET_REASSIGN_LOC_TYPE2 = 0;
+    private static final int CHANGE_LOC_IO_TYPE = 5;
+
+    private static class ReassignCrnPool {
+        private final List<Integer> crnNos;
+
+        private ReassignCrnPool(List<Integer> crnNos) {
+            this.crnNos = crnNos;
+        }
+    }
 
     @Autowired
     private LocMastService locMastService;
@@ -105,9 +119,11 @@
     @Autowired
     private ApiLogService apiLogService;
     @Autowired
-    private RowLastnoService rowLastnoService;
+    private OutboundBatchSeqReleaseGuard outboundBatchSeqReleaseGuard;
     @Autowired
-    private RedisUtil redisUtil;
+    private BasCrnDepthRuleService basCrnDepthRuleService;
+    @Autowired
+    private RowLastnoService rowLastnoService;
 
 
     /**
@@ -127,6 +143,10 @@
         String validateMsg = validatePubTask(params, wrkMast);
         if (!Cools.isEmpty(validateMsg)) {
             return R.error(validateMsg);
+        }
+        String batchBlockMsg = validateOutboundBatchSeqReady(params, wrkMast);
+        if (!Cools.isEmpty(batchBlockMsg)) {
+            return R.error(batchBlockMsg);
         }
         String url = resolveTaskPath(params);
         String requestJson = JSON.toJSONString(params);
@@ -485,49 +505,19 @@
             return null;
         }
         WorkTaskParams head = chunk.get(0);
-        if (!"out".equalsIgnoreCase(head.getType())) {
-            return null;
-        }
-        WrkMast headMast = wrkMastMap.get(head.getTaskNo());
-        String userNo = sortUserNoForPub(head, headMast);
-        if (Cools.isEmpty(userNo)) {
-            return null;
-        }
-        String batchSeq = sortBatchGroupForPub(head, headMast);
-        String blockingBatchSeq = findFirstUnfinishedOutboundBatchSeq(userNo);
-        if (blockingBatchSeq == null || compareBatchSeqNatural(batchSeq, blockingBatchSeq) == 0) {
-            return null;
-        }
-        return "鍑哄簱鎵规鏈畬鎴愶紝鏆傚仠鍚庣画涓嬪彂, userNo=" + userNo
-                + ", blockingBatchSeq=" + normalizeBatchSeq(blockingBatchSeq)
-                + ", nextBatchSeq=" + normalizeBatchSeq(batchSeq);
+        return validateOutboundBatchSeqReady(head, wrkMastMap.get(head.getTaskNo()));
     }
 
-    private String findFirstUnfinishedOutboundBatchSeq(String userNo) {
-        EntityWrapper<WrkMast> wrapper = new EntityWrapper<>();
-        if (Cools.isEmpty(userNo)) {
-            wrapper.isNull("user_no");
-        } else {
-            wrapper.eq("user_no", userNo);
-        }
-        wrapper.eq("io_type", 101);
-        wrapper.last(" and (wrk_sts < 14 or wrk_sts in ("
-                + OUT_LOCK_REPORT_SUCCESS_WRK_STS + "," + OUT_LOCK_REPORT_FAIL_WRK_STS + "))");
-        List<WrkMast> rows = wrkMastService.selectList(wrapper);
-        if (rows == null || rows.isEmpty()) {
+    private String validateOutboundBatchSeqReady(WorkTaskParams params, WrkMast wrkMast) {
+        if (params == null || !"out".equalsIgnoreCase(params.getType())) {
             return null;
         }
-        String firstBatchSeq = null;
-        for (WrkMast row : rows) {
-            if (row == null) {
-                continue;
-            }
-            String batchSeq = sortBatchGroupForPub(null, row);
-            if (firstBatchSeq == null || compareBatchSeqNatural(batchSeq, firstBatchSeq) < 0) {
-                firstBatchSeq = batchSeq;
-            }
+        if (wrkMast == null || !Objects.equals(wrkMast.getIoType(), 101)) {
+            return null;
         }
-        return firstBatchSeq;
+        return outboundBatchSeqReleaseGuard.validateReady(
+                sortUserNoForPub(params, wrkMast),
+                sortBatchGroupForPub(params, wrkMast));
     }
 
     private boolean outboundPltSlotReleasedInWms(String userNo, String batchSeq, int pltType) {
@@ -714,11 +704,29 @@
                     throw new CoolException("浠诲姟鐘舵�佷慨鏀瑰け璐ワ紒锛�");
                 }
             }
-        } else if (params.getNotifyType().equals("task")) {
+        } else if (isOutboundCrnTaskComplete(params)) {
+            // WCS鍑哄簱浠诲姟瀹屾垚锛氬爢鍨涙満鍑哄簱浠诲姟鎵ц瀹屾垚锛屽伐浣滅姸鎬� -> 25銆�
+            if (isOutboundTask(mast) && canMarkOutboundCrnComplete(mast)) {
+                mast.setWrkSts(OUTBOUND_CRN_COMPLETE_WRK_STS);
+                mast.setModiTime(new Date());
+                if (!wrkMastService.updateById(mast)) {
+                    throw new CoolException("浠诲姟鐘舵�佷慨鏀瑰け璐ワ紒锛�");
+                }
+            }
+        } else if (isOutboundStationTaskRunComplete(params)) {
+            // WCS杈撻�佺珯鐐瑰嚭搴撲换鍔¤繍琛屽畬鎴愶細鎵樼洏宸插埌鐩殑鍦帮紝宸ヤ綔鐘舵�� -> 26銆�
+            if (isOutboundTask(mast) && canMarkOutboundStationComplete(mast)) {
+                mast.setWrkSts(OUTBOUND_STATION_COMPLETE_WRK_STS);
+                mast.setModiTime(new Date());
+                if (!wrkMastService.updateById(mast)) {
+                    throw new CoolException("浠诲姟鐘舵�佷慨鏀瑰け璐ワ紒锛�");
+                }
+            }
+        } else if ("task".equalsIgnoreCase(params.getNotifyType())) {
             //浠诲姟
-            if (params.getMsgType().equals("task_complete")) {
+            if ("task_complete".equalsIgnoreCase(params.getMsgType())) {
 
-                if (mast.getIoType() == 1 || mast.getIoType() == 2 ||mast.getIoType() == 10) {
+                if (mast.getIoType() == 1 || mast.getIoType() == 2 || mast.getIoType() == 10 || mast.getIoType() == CHANGE_LOC_IO_TYPE) {
                     mast.setWrkSts(4L);
                 } else if (isOutboundTask(mast) && canMarkOutboundTaskComplete(mast)) {
                     mast.setWrkSts(14L);
@@ -730,7 +738,7 @@
                     throw new CoolException("浠诲姟鐘舵�佷慨鏀瑰け璐ワ紒锛�");
                 }
                 //wcs浠诲姟鍙栨秷鎺ュ彛
-            } else if (params.getMsgType().equals("task_cancel")) {
+            } else if ("task_cancel".equalsIgnoreCase(params.getMsgType())) {
                 workService.cancelWrkMast(String.valueOf(mast.getWrkNo()), 9955L);
             }
         }
@@ -743,8 +751,29 @@
                 && "crn_out_task_run".equalsIgnoreCase(params.getMsgType());
     }
 
+    private boolean isOutboundCrnTaskComplete(ReceviceTaskParams params) {
+        return params != null
+                && "Crn".equalsIgnoreCase(params.getNotifyType())
+                && "crn_out_task_complete".equalsIgnoreCase(params.getMsgType());
+    }
+
+    private boolean isOutboundStationTaskRunComplete(ReceviceTaskParams params) {
+        return params != null
+                && "Devp".equalsIgnoreCase(params.getNotifyType())
+                && "station_out_task_run_complete".equalsIgnoreCase(params.getMsgType());
+    }
+
     private boolean isOutboundTask(WrkMast mast) {
         return mast != null && mast.getIoType() != null && (mast.getIoType() == 101 || mast.getIoType() == 110);
+    }
+
+    private boolean canMarkOutboundCrnComplete(WrkMast mast) {
+        if (mast == null || mast.getWrkSts() == null) {
+            return false;
+        }
+        return mast.getWrkSts() < 14
+                || mast.getWrkSts().equals(OUT_LOCK_REPORT_SUCCESS_WRK_STS)
+                || mast.getWrkSts().equals(OUT_LOCK_REPORT_FAIL_WRK_STS);
     }
 
     private boolean canMarkOutboundTaskComplete(WrkMast mast) {
@@ -753,7 +782,19 @@
         }
         return mast.getWrkSts() < 14
                 || mast.getWrkSts().equals(OUT_LOCK_REPORT_SUCCESS_WRK_STS)
-                || mast.getWrkSts().equals(OUT_LOCK_REPORT_FAIL_WRK_STS);
+                || mast.getWrkSts().equals(OUT_LOCK_REPORT_FAIL_WRK_STS)
+                || mast.getWrkSts().equals(OUTBOUND_CRN_COMPLETE_WRK_STS)
+                || mast.getWrkSts().equals(OUTBOUND_STATION_COMPLETE_WRK_STS);
+    }
+
+    private boolean canMarkOutboundStationComplete(WrkMast mast) {
+        if (mast == null || mast.getWrkSts() == null) {
+            return false;
+        }
+        return mast.getWrkSts() < 14
+                || mast.getWrkSts().equals(OUT_LOCK_REPORT_SUCCESS_WRK_STS)
+                || mast.getWrkSts().equals(OUT_LOCK_REPORT_FAIL_WRK_STS)
+                || mast.getWrkSts().equals(OUTBOUND_CRN_COMPLETE_WRK_STS);
     }
 
     @Override
@@ -814,28 +855,37 @@
             return R.error("褰撳墠鐩爣搴撲綅涓嶅瓨鍦�");
         }
 
-        LocTypeDto locTypeDto = buildReassignLocTypeDto(currentLoc);
-        List<Integer> areaOrder = buildReassignAreaOrder(wrkMast, currentLoc);
-        if (Cools.isEmpty(areaOrder)) {
-            return R.error("鏃犳硶纭畾浠诲姟鎵�灞炲簱鍖�");
+        boolean emptyPallet = isReassignEmptyPallet(wrkMast);
+        LocTypeDto locTypeDto = buildReassignLocTypeDto(currentLoc, emptyPallet);
+        BasDevp sourceStation = basDevpService.selectById(wrkMast.getSourceStaNo());
+        if (Cools.isEmpty(sourceStation)) {
+            return R.error("婧愮珯鏈厤缃叆搴撲紭鍏堟睜");
+        }
+        List<ReassignCrnPool> poolOrder;
+        try {
+            poolOrder = buildReassignCrnPoolOrder(sourceStation, wrkMast.getCrnNo());
+        } catch (CoolException e) {
+            return R.error(e.getMessage());
+        }
+        if (Cools.isEmpty(poolOrder)) {
+            return R.error("婧愮珯鏈厤缃叆搴撲紭鍏堟睜");
         }
 
         StartupDto startupDto = null;
-        Integer preferredArea = null;
-        for (Integer area : areaOrder) {
-            List<Integer> candidateCrnNos = buildReassignCandidateCrnNos(area, wrkMast.getCrnNo());
+        Integer targetLevel = resolveReassignTargetLevel(emptyPallet);
+        for (ReassignCrnPool pool : poolOrder) {
+            List<Integer> candidateCrnNos = buildReassignCandidateCrnNos(pool.crnNos, wrkMast.getCrnNo());
             if (candidateCrnNos.isEmpty()) {
                 continue;
             }
             startupDto = commonService.findRun2InboundLocByCandidateCrnNos(
-                    wrkMast.getSourceStaNo(), wrkMast.getIoType(), area, candidateCrnNos, locTypeDto);
+                    wrkMast.getSourceStaNo(), wrkMast.getIoType(), candidateCrnNos, locTypeDto, targetLevel);
             if (startupDto != null && !Cools.isEmpty(startupDto.getLocNo())) {
-                preferredArea = area;
                 break;
             }
         }
         if (startupDto == null || Cools.isEmpty(startupDto.getLocNo())) {
-            return R.error("褰撳墠搴撳尯娌℃湁鍙噸鏂板垎閰嶇殑绌哄簱浣�");
+            return R.error("褰撳墠浼樺厛姹犳病鏈夊彲閲嶆柊鍒嗛厤鐨勭┖搴撲綅");
         }
 
         LocMast targetLoc = locMastService.selectById(startupDto.getLocNo());
@@ -850,11 +900,219 @@
         updateReassignTargetLoc(targetLoc, wrkMast, currentLoc, now);
         updateReassignWorkMast(wrkMast, startupDto, now);
         releaseOldReservedLocIfNeeded(currentLoc, targetLoc.getLocNo(), now);
-        lockReassignedCrnAfterCommit(preferredArea, targetLoc.getCrnNo(), wrkMast.getWrkNo());
 
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("locNo", Utils.WMSLocToWCSLoc(targetLoc.getLocNo()));
         return R.ok("鎿嶄綔鎴愬姛").add(result);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R changeLoc(ChangeLocParams params) {
+        if (params == null || Cools.isEmpty(params.getLocNo())) {
+            return R.error("locNo涓嶈兘涓虹┖");
+        }
+        LocMast sourceLoc = locMastService.selectById(params.getLocNo());
+        if (sourceLoc == null) {
+            return R.error("褰撳墠搴撲綅涓嶅瓨鍦�");
+        }
+        CrnDepthRuleProfile profile = resolveChangeLocProfile(sourceLoc);
+        String validateMsg = validateChangeLocSource(sourceLoc, profile);
+        if (!Cools.isEmpty(validateMsg)) {
+            return R.error(validateMsg);
+        }
+        List<Integer> candidateRows = resolveChangeLocRows(params.getRow(), profile);
+        LocMast targetLoc = selectChangeLocTarget(sourceLoc, profile, candidateRows);
+        if (targetLoc == null) {
+            return R.error("褰撳墠鍫嗗灈鏈烘棤鍙敤绉诲簱鐩爣浣�");
+        }
+        Integer workNo = commonService.getWorkNo(WorkNoType.PICK.type);
+        Date now = new Date();
+        createChangeLocTask(workNo, sourceLoc, targetLoc, now);
+        reserveChangeLocTargetLoc(targetLoc, sourceLoc, now);
+        reserveChangeLocSourceLoc(sourceLoc, now);
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("locNo", Utils.WMSLocToWCSLoc(targetLoc.getLocNo()));
+        result.put("taskNo", String.valueOf(workNo));
+        return R.ok("鎿嶄綔鎴愬姛").add(result);
+    }
+
+    private CrnDepthRuleProfile resolveChangeLocProfile(LocMast sourceLoc) {
+        RowLastno rowLastno = rowLastnoService.selectById(sourceLoc.getWhsType());
+        return basCrnDepthRuleService.resolveProfile(rowLastno, sourceLoc.getCrnNo(), sourceLoc.getRow1());
+    }
+
+    private String validateChangeLocSource(LocMast sourceLoc, CrnDepthRuleProfile profile) {
+        if (sourceLoc.getCrnNo() == null) {
+            return "褰撳墠搴撲綅鏈粦瀹氬爢鍨涙満";
+        }
+        if (profile == null || !profile.isDoubleExtension()) {
+            return "褰撳墠鍫嗗灈鏈洪潪鍙屾繁搴撲綅锛屼笉鏀寔绉诲簱";
+        }
+        if (sourceLoc.getRow1() == null || profile == null || !profile.isShallowRow(sourceLoc.getRow1())) {
+            return "褰撳墠搴撲綅涓嶆槸娴呭簱浣�";
+        }
+        if (!isChangeLocInStockLocSts(sourceLoc.getLocSts())) {
+            return "褰撳墠搴撲綅鐘舵�佷笉鍏佽绉诲簱";
+        }
+        return null;
+    }
+
+    private List<Integer> resolveChangeLocRows(List<Integer> requestRows, CrnDepthRuleProfile profile) {
+        if (!Cools.isEmpty(requestRows)) {
+            return requestRows.stream()
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
+        return profile == null ? Collections.emptyList() : profile.getSearchRows();
+    }
+
+    private LocMast selectChangeLocTarget(LocMast sourceLoc, CrnDepthRuleProfile profile, List<Integer> candidateRows) {
+        List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>()
+                .eq("crn_no", sourceLoc.getCrnNo())
+                .eq("loc_type1",2)
+                .eq("loc_sts", "O")
+                .orderBy("row1", true)
+                .orderBy("lev1", true)
+                .orderBy("bay1", true));
+        if (Cools.isEmpty(locMasts)) {
+            return null;
+        }
+//        for (LocMast locMast : locMasts) {
+//            if (!isChangeLocRowAllowed(locMast, candidateRows)) {
+//                continue;
+//            }
+//            if (locMast.getRow1() != null && Utils.isDeepLoc(slaveProperties, locMast.getRow1())) {
+//                return locMast;
+//            }
+//        }
+//        for (LocMast locMast : locMasts) {
+//            if (!isChangeLocRowAllowed(locMast, candidateRows)) {
+//                continue;
+//            }
+//            if (locMast.getRow1() == null || !Utils.isShallowLoc(slaveProperties, locMast.getRow1())) {
+//                continue;
+//            }
+//            String deepLocNo = Utils.getDeepLoc(slaveProperties, locMast.getLocNo());
+//            LocMast deepLoc = locMastService.selectById(deepLocNo);
+//            if (deepLoc != null && ("F".equals(deepLoc.getLocSts()) || "D".equals(deepLoc.getLocSts()))) {
+//                return locMast;
+//            }
+//        }
+        LocMast shallowFallback = null;
+        for (LocMast locMast : locMasts) {
+            //鎺掕繃婊�
+            if (!isChangeLocRowAllowed(locMast, candidateRows)) {
+                continue;
+            }
+            //娣变綅浼樺厛
+            if (isChangeLocDeepCandidate(locMast, profile)) {
+                return locMast;
+            }
+            //娴呬綅鍏滃簳
+            if (shallowFallback == null && isChangeLocShallowFallbackCandidate(locMast, profile)) {
+                shallowFallback = locMast;
+            }
+        }
+        return shallowFallback;
+    }
+
+    private boolean isChangeLocRowAllowed(LocMast locMast, List<Integer> candidateRows) {
+        if (Cools.isEmpty(candidateRows)) {
+            return true;
+        }
+        return locMast.getRow1() != null && candidateRows.contains(locMast.getRow1());
+    }
+
+    private boolean isChangeLocDeepCandidate(LocMast locMast, CrnDepthRuleProfile profile) {
+        return locMast.getRow1() != null
+                && profile != null
+                && profile.isDeepRow(locMast.getRow1());
+    }
+
+    private boolean isChangeLocShallowFallbackCandidate(LocMast locMast, CrnDepthRuleProfile profile) {
+        if (locMast.getRow1() == null || profile == null || !profile.isShallowRow(locMast.getRow1())) {
+            return false;
+        }
+        Integer deepRow = profile.getPairedDeepRow(locMast.getRow1());
+        if (deepRow == null) {
+            return false;
+        }
+        LocMast deepLoc = locMastService.selectOne(new EntityWrapper<LocMast>()
+                .eq("crn_no", locMast.getCrnNo())
+                .eq("row1", deepRow)
+                .eq("bay1", locMast.getBay1())
+                .eq("lev1", locMast.getLev1()));
+        return deepLoc != null && isChangeLocInStockLocSts(deepLoc.getLocSts());
+    }
+
+    private boolean isChangeLocInStockLocSts(String locSts) {
+        return "F".equals(locSts) || "D".equals(locSts);
+    }
+
+    private void createChangeLocTask(Integer workNo, LocMast sourceLoc, LocMast targetLoc, Date now) {
+        List<LocDetl> sourceDetls = locDetlService.selectList(new EntityWrapper<LocDetl>().eq("loc_no", sourceLoc.getLocNo()));
+        WrkMast wrkMast = new WrkMast();
+        wrkMast.setWrkNo(workNo);
+        wrkMast.setIoTime(now);
+        wrkMast.setWrkSts(11L);
+        wrkMast.setIoType(CHANGE_LOC_IO_TYPE);
+        wrkMast.setIoPri(10D);
+        wrkMast.setCrnNo(sourceLoc.getCrnNo());
+        wrkMast.setSourceLocNo(sourceLoc.getLocNo());
+        wrkMast.setLocNo(targetLoc.getLocNo());
+        wrkMast.setFullPlt(Cools.isEmpty(sourceDetls) ? "N" : "Y");
+        wrkMast.setPicking("N");
+        wrkMast.setExitMk("N");
+        wrkMast.setEmptyMk("D".equals(sourceLoc.getLocSts()) ? "Y" : "N");
+        wrkMast.setBarcode(sourceLoc.getBarcode());
+        wrkMast.setLinkMis("N");
+        wrkMast.setMemo("CHANGE_LOC");
+        wrkMast.setAppeUser(WCS_SYNC_USER);
+        wrkMast.setAppeTime(now);
+        wrkMast.setModiUser(WCS_SYNC_USER);
+        wrkMast.setModiTime(now);
+        if (!wrkMastService.insert(wrkMast)) {
+            throw new CoolException("淇濆瓨宸ヤ綔妗eけ璐�");
+        }
+        if (Cools.isEmpty(sourceDetls)) {
+            return;
+        }
+        for (LocDetl sourceDetl : sourceDetls) {
+            WrkDetl wrkDetl = new WrkDetl();
+            wrkDetl.sync(sourceDetl);
+            wrkDetl.setWrkNo(workNo);
+            wrkDetl.setIoTime(now);
+            wrkDetl.setAnfme(sourceDetl.getAnfme());
+            wrkDetl.setAppeTime(now);
+            wrkDetl.setAppeUser(WCS_SYNC_USER);
+            wrkDetl.setModiTime(now);
+            wrkDetl.setModiUser(WCS_SYNC_USER);
+            if (!wrkDetlService.insert(wrkDetl)) {
+                throw new CoolException("淇濆瓨宸ヤ綔妗f槑缁嗗け璐�");
+            }
+        }
+    }
+
+    private void reserveChangeLocTargetLoc(LocMast targetLoc, LocMast sourceLoc, Date now) {
+        targetLoc.setLocSts("S");
+        targetLoc.setBarcode(sourceLoc.getBarcode());
+        targetLoc.setScWeight(sourceLoc.getScWeight() == null ? BigDecimal.ZERO : sourceLoc.getScWeight());
+        targetLoc.setModiUser(WCS_SYNC_USER);
+        targetLoc.setModiTime(now);
+        if (!locMastService.updateById(targetLoc)) {
+            throw new CoolException("鏇存柊鐩爣搴撲綅鐘舵�佸け璐�");
+        }
+    }
+
+    private void reserveChangeLocSourceLoc(LocMast sourceLoc, Date now) {
+        sourceLoc.setLocSts("R");
+        sourceLoc.setModiUser(WCS_SYNC_USER);
+        sourceLoc.setModiTime(now);
+        if (!locMastService.updateById(sourceLoc)) {
+            throw new CoolException("鏇存柊婧愬簱浣嶇姸鎬佸け璐�");
+        }
     }
 
     private boolean requiresOutboundErpConfirm(WrkMast wrkMast) {
@@ -886,6 +1144,9 @@
             if ("Y".equalsIgnoreCase(wrkMast.getPauseMk())) {
                 return "task paused";
             }
+            if (Objects.equals(wrkMast.getIoType(), 101) && Cools.isEmpty(wrkMast.getBatchSeq())) {
+                return "鍑哄簱杩涗粨缂栧彿(batchSeq)涓虹┖锛岃烦杩囦笅鍙�";
+            }
             if (requiresOutboundErpConfirm(wrkMast) && !"Y".equalsIgnoreCase(wrkMast.getPdcType())) {
                 return "task not confirmed by erp";
             }
@@ -915,115 +1176,102 @@
         return null;
     }
 
-    private Integer resolveReassignArea(WrkMast wrkMast, LocMast currentLoc) {
-        List<Integer> stationAreas = Utils.getStationStorageAreas(wrkMast.getSourceStaNo());
-        if (!Cools.isEmpty(stationAreas)) {
-            for (Integer area : stationAreas) {
-                if (belongsToArea(area, wrkMast.getCrnNo(), currentLoc)) {
-                    return area;
-                }
-            }
+    private List<ReassignCrnPool> buildReassignCrnPoolOrder(BasDevp sourceStation, Integer currentCrnNo) {
+        List<Integer> firstPoolCrnNos = Utils.distinctCrnNos(sourceStation.getInFirstCrnCsv());
+        List<Integer> secondPoolCrnNos = excludeReassignPoolCrnNos(
+                Utils.distinctCrnNos(sourceStation.getInSecondCrnCsv()), firstPoolCrnNos);
+        if (Cools.isEmpty(firstPoolCrnNos) && Cools.isEmpty(secondPoolCrnNos)) {
+            throw new CoolException("婧愮珯鏈厤缃叆搴撲紭鍏堟睜");
         }
-        Integer fallbackArea = findAreaByCurrentTask(wrkMast.getCrnNo(), currentLoc);
-        if (fallbackArea != null) {
-            return fallbackArea;
+        boolean inFirstPool = firstPoolCrnNos.contains(currentCrnNo);
+        boolean inSecondPool = secondPoolCrnNos.contains(currentCrnNo);
+        if (!inFirstPool && !inSecondPool) {
+            throw new CoolException("褰撳墠浠诲姟鍫嗗灈鏈烘湭閰嶇疆鍦ㄦ簮绔欏叆搴撲紭鍏堟睜");
         }
-        return Utils.getStationStorageArea(wrkMast.getSourceStaNo());
+        List<ReassignCrnPool> poolOrder = new ArrayList<>();
+        if (inFirstPool) {
+            poolOrder.add(new ReassignCrnPool(firstPoolCrnNos));
+            poolOrder.add(new ReassignCrnPool(secondPoolCrnNos));
+            return poolOrder;
+        }
+        poolOrder.add(new ReassignCrnPool(secondPoolCrnNos));
+        poolOrder.add(new ReassignCrnPool(firstPoolCrnNos));
+        return poolOrder;
     }
 
-    private Integer findAreaByCurrentTask(Integer currentCrnNo, LocMast currentLoc) {
-        for (int area = 1; area <= 3; area++) {
-            if (belongsToArea(area, currentCrnNo, currentLoc)) {
-                return area;
+    private List<Integer> excludeReassignPoolCrnNos(List<Integer> crnNos, List<Integer> excludedCrnNos) {
+        List<Integer> result = new ArrayList<>();
+        if (Cools.isEmpty(crnNos)) {
+            return result;
+        }
+        LinkedHashSet<Integer> excluded = new LinkedHashSet<>(Utils.distinctCrnNos(excludedCrnNos));
+        for (Integer crnNo : Utils.distinctCrnNos(crnNos)) {
+            if (crnNo == null || excluded.contains(crnNo)) {
+                continue;
             }
+            result.add(crnNo);
+        }
+        return result;
+    }
+
+    private List<Integer> buildReassignCandidateCrnNos(List<Integer> poolCrnNos, Integer currentCrnNo) {
+        return buildReassignCrnSearchOrder(poolCrnNos, currentCrnNo);
+    }
+
+    private List<Integer> buildReassignCrnSearchOrder(List<Integer> poolCrnNos, Integer currentCrnNo) {
+        List<Integer> orderedCrnNos = Utils.distinctCrnNos(poolCrnNos);
+        Collections.sort(orderedCrnNos);
+        if (Cools.isEmpty(orderedCrnNos) || currentCrnNo == null) {
+            return orderedCrnNos;
+        }
+        List<Integer> searchOrder = new ArrayList<>();
+        for (int index = orderedCrnNos.size() - 1; index >= 0; index--) {
+            Integer crnNo = orderedCrnNos.get(index);
+            if (crnNo == null || crnNo >= currentCrnNo) {
+                continue;
+            }
+            searchOrder.add(crnNo);
+        }
+        for (int index = orderedCrnNos.size() - 1; index >= 0; index--) {
+            Integer crnNo = orderedCrnNos.get(index);
+            if (crnNo == null || crnNo <= 0 || crnNo.equals(currentCrnNo) || crnNo < currentCrnNo) {
+                continue;
+            }
+            searchOrder.add(crnNo);
+        }
+        return searchOrder;
+    }
+
+    private Integer resolveReassignTargetLevel(boolean emptyPallet) {
+        if (emptyPallet) {
+            return EMPTY_PALLET_REASSIGN_TARGET_LEVEL;
         }
         return null;
     }
 
-    private List<Integer> buildReassignAreaOrder(WrkMast wrkMast, LocMast currentLoc) {
-        LinkedHashSet<Integer> areaOrder = new LinkedHashSet<>();
-        List<Integer> stationAreas = Utils.getStationStorageAreas(wrkMast.getSourceStaNo());
-        if (!Cools.isEmpty(stationAreas)) {
-            areaOrder.addAll(stationAreas);
+    private boolean isReassignEmptyPallet(WrkMast wrkMast) {
+        if (wrkMast == null || wrkMast.getWrkNo() == null) {
+            return false;
         }
-        Integer currentArea = findAreaByCurrentTask(wrkMast.getCrnNo(), currentLoc);
-        if (currentArea != null) {
-            areaOrder.add(currentArea);
+        List<WrkDetl> wrkDetls = wrkDetlService.selectByWrkNo(wrkMast.getWrkNo());
+        if (Cools.isEmpty(wrkDetls)) {
+            return false;
         }
-        if (areaOrder.isEmpty()) {
-            Integer resolvedArea = resolveReassignArea(wrkMast, currentLoc);
-            if (resolvedArea != null) {
-                areaOrder.add(resolvedArea);
+        for (WrkDetl wrkDetl : wrkDetls) {
+            if (wrkDetl != null && EMPTY_PALLET_MATNR.equalsIgnoreCase(String.valueOf(wrkDetl.getMatnr()).trim())) {
+                return true;
             }
         }
-        return new ArrayList<>(areaOrder);
+        return false;
     }
 
-    private boolean belongsToArea(Integer area, Integer currentCrnNo, LocMast currentLoc) {
-        if (area == null || area <= 0) {
-            return false;
-        }
-        RowLastno areaRowLastno = rowLastnoService.selectById(area);
-        if (areaRowLastno == null) {
-            return false;
-        }
-        Integer startCrnNo = resolveAreaStartCrnNo(areaRowLastno);
-        Integer endCrnNo = resolveAreaEndCrnNo(areaRowLastno, startCrnNo);
-        if (currentCrnNo != null && currentCrnNo >= startCrnNo && currentCrnNo <= endCrnNo) {
-            return true;
-        }
-        Integer row = currentLoc == null ? null : currentLoc.getRow1();
-        Integer startRow = areaRowLastno.getsRow();
-        Integer endRow = areaRowLastno.geteRow();
-        return row != null && startRow != null && endRow != null && row >= startRow && row <= endRow;
-    }
-
-    private List<Integer> buildReassignCandidateCrnNos(Integer area, Integer currentCrnNo) {
-        RowLastno areaRowLastno = rowLastnoService.selectById(area);
-        if (areaRowLastno == null) {
-            throw new CoolException("鏈壘鍒板簱鍖鸿疆璇㈣鍒�");
-        }
-        int startCrnNo = resolveAreaStartCrnNo(areaRowLastno);
-        int endCrnNo = resolveAreaEndCrnNo(areaRowLastno, startCrnNo);
-        if (currentCrnNo == null || currentCrnNo < startCrnNo || currentCrnNo > endCrnNo) {
-            throw new CoolException("褰撳墠浠诲姟鍫嗗灈鏈轰笉鍦ㄦ墍灞炲簱鍖鸿寖鍥村唴");
-        }
-        List<Integer> candidateCrnNos = new ArrayList<>();
-        for (int crnNo = currentCrnNo - 1; crnNo >= startCrnNo; crnNo--) {
-            addUnlockedReassignCandidate(candidateCrnNos, area, crnNo);
-        }
-        for (int crnNo = endCrnNo; crnNo > currentCrnNo; crnNo--) {
-            addUnlockedReassignCandidate(candidateCrnNos, area, crnNo);
-        }
-        return candidateCrnNos;
-    }
-
-    private void addUnlockedReassignCandidate(List<Integer> candidateCrnNos, Integer area, int crnNo) {
-        if (isReassignCrnLocked(area, crnNo)) {
-            log.info("skip locked reassign crane. area={}, crnNo={}, ttl={}s",
-                    area, crnNo, redisUtil.getExpire(buildReassignCrnLockKey(area, crnNo)));
-            return;
-        }
-        candidateCrnNos.add(crnNo);
-    }
-
-    private int resolveAreaStartCrnNo(RowLastno areaRowLastno) {
-        if (areaRowLastno.getsCrnNo() != null && areaRowLastno.getsCrnNo() > 0) {
-            return areaRowLastno.getsCrnNo();
-        }
-        return 1;
-    }
-
-    private int resolveAreaEndCrnNo(RowLastno areaRowLastno, int startCrnNo) {
-        if (areaRowLastno.geteCrnNo() != null && areaRowLastno.geteCrnNo() >= startCrnNo) {
-            return areaRowLastno.geteCrnNo();
-        }
-        int crnQty = areaRowLastno.getCrnQty() == null || areaRowLastno.getCrnQty() <= 0 ? 1 : areaRowLastno.getCrnQty();
-        return startCrnNo + crnQty - 1;
-    }
-
-    private LocTypeDto buildReassignLocTypeDto(LocMast currentLoc) {
+    private LocTypeDto buildReassignLocTypeDto(LocMast currentLoc, boolean emptyPallet) {
         LocTypeDto locTypeDto = new LocTypeDto();
+        if (emptyPallet) {
+            locTypeDto.setLocType1(EMPTY_PALLET_REASSIGN_LOC_TYPE1);
+            locTypeDto.setLocType2(EMPTY_PALLET_REASSIGN_LOC_TYPE2);
+            return locTypeDto;
+        }
         if (currentLoc == null) {
             return locTypeDto;
         }
@@ -1089,43 +1337,6 @@
         if (!locMastService.updateById(currentLoc)) {
             throw new CoolException("閲婃斁鍘熺洰鏍囧簱浣嶅け璐�");
         }
-    }
-
-    private boolean isReassignCrnLocked(Integer area, Integer crnNo) {
-        if (area == null || crnNo == null) {
-            return false;
-        }
-        return redisUtil.hasKey(buildReassignCrnLockKey(area, crnNo));
-    }
-
-    private String buildReassignCrnLockKey(Integer area, Integer crnNo) {
-        return REASSIGN_CRN_LOCK_KEY_PREFIX + area + ":" + crnNo;
-    }
-
-    private void lockReassignedCrnAfterCommit(Integer area, Integer crnNo, Integer wrkNo) {
-        if (area == null || crnNo == null) {
-            return;
-        }
-        Runnable action = () -> {
-            String key = buildReassignCrnLockKey(area, crnNo);
-            boolean locked = redisUtil.set(key, String.valueOf(wrkNo), REASSIGN_CRN_LOCK_SECONDS);
-            if (!locked) {
-                log.warn("failed to lock reassigned crane in redis. area={}, crnNo={}, wrkNo={}", area, crnNo, wrkNo);
-                return;
-            }
-            log.info("locked reassigned crane in redis. area={}, crnNo={}, wrkNo={}, ttl={}s",
-                    area, crnNo, wrkNo, REASSIGN_CRN_LOCK_SECONDS);
-        };
-        if (TransactionSynchronizationManager.isActualTransactionActive()) {
-            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
-                @Override
-                public void afterCommit() {
-                    action.run();
-                }
-            });
-            return;
-        }
-        action.run();
     }
 
     /**

--
Gitblit v1.9.1