From 63b01db83d9aad8a15276b4236a9a22e4aeef065 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 05 五月 2026 12:30:59 +0800
Subject: [PATCH] # Agent数据分析V3.0.1.7

---
 src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java |  648 +++++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 483 insertions(+), 165 deletions(-)

diff --git a/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java b/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java
index 644090a..1833aa5 100644
--- a/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java
+++ b/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java
@@ -2,8 +2,10 @@
 
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.zy.asrs.entity.BasStation;
 import com.zy.asrs.domain.enums.NotifyMsgType;
 import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.BasStationService;
 import com.zy.asrs.service.WrkAnalysisService;
 import com.zy.asrs.service.WrkMastService;
 import com.zy.asrs.utils.NotifyUtils;
@@ -19,6 +21,7 @@
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.move.StationMoveCoordinator;
+import com.zy.core.move.StationMoveSession;
 import com.zy.core.thread.StationThread;
 import com.zy.core.utils.station.model.DispatchLimitConfig;
 import com.zy.core.utils.station.model.LoadGuardState;
@@ -26,16 +29,26 @@
 import com.zy.core.utils.station.model.OutOrderDispatchDecision;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
+import org.springframework.beans.factory.annotation.Value;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 @Component
 public class StationOutboundDispatchProcessor {
 
+    private static final int PENDING_DISPATCH_EXPIRE_SECONDS = 60 * 60 * 24;
+    private static final long DEFAULT_PENDING_DISCOVER_TIMEOUT_MS = 60_000L;
+    private static final long DEFAULT_PENDING_SESSION_PROTECT_MS = 90_000L;
+    private static final long DEFAULT_RECENT_DISPATCH_PROTECT_MS = 60_000L;
+
     @Autowired
     private WrkMastService wrkMastService;
+    @Autowired
+    private BasStationService basStationService;
     @Autowired
     private WrkAnalysisService wrkAnalysisService;
     @Autowired
@@ -50,191 +63,218 @@
     private StationDispatchLoadSupport stationDispatchLoadSupport;
     @Autowired
     private StationOutboundDecisionSupport stationOutboundDecisionSupport;
+    @Autowired
+    private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport;
 
-    public void crnStationOutExecute() {
+    @Value("${station.outbound.pending-discover-timeout-seconds:60}")
+    private long pendingDiscoverTimeoutSeconds;
+    @Value("${station.outbound.pending-session-protect-seconds:90}")
+    private long pendingSessionProtectSeconds;
+    @Value("${station.outbound.recent-dispatch-protect-seconds:60}")
+    private long recentDispatchProtectSeconds;
+
+    public void crnStationOutExecute(WrkMast wrkMast) {
         try {
-            DispatchLimitConfig baseLimitConfig =
-                    stationDispatchLoadSupport.getDispatchLimitConfig(null, null);
-            int[] currentStationTaskCountRef = new int[]{stationDispatchLoadSupport.countCurrentStationTask()};
-            LoadGuardState loadGuardState =
-                    stationDispatchLoadSupport.buildLoadGuardState(baseLimitConfig);
-
-            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
-                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
-                    .isNotNull("crn_no"));
-            List<Integer> outOrderList = stationOutboundDecisionSupport.getAllOutOrderList();
-
-            for (WrkMast wrkMast : wrkMasts) {
-                Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
-                if (infoObj == null) {
-                    News.info("鍑哄簱浠诲姟{}鏁版嵁缂撳瓨涓嶅瓨鍦�", wrkMast.getWrkNo());
-                    continue;
-                }
-
-                StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class);
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
-                StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
-                if (stationProtocol == null) {
-                    continue;
-                }
-
-                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
-                if (lock != null) {
-                    continue;
-                }
-
-                if (stationProtocol.isAutoing()
-                        && stationProtocol.isLoading()
-                        && stationProtocol.getTaskNo() == 0) {
-                    Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
-                    OutOrderDispatchDecision dispatchDecision =
-                            stationOutboundDecisionSupport.resolveOutboundDispatchDecision(
-                                    stationProtocol.getStationId(),
-                                    wrkMast,
-                                    outOrderList,
-                                    pathLenFactor
-                            );
-                    Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
-                    if (moveStaNo == null) {
-                        continue;
-                    }
-
-                    DispatchLimitConfig limitConfig =
-                            stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo);
-                    LoopHitResult loopHitResult =
-                            stationDispatchLoadSupport.findPathLoopHit(
-                                    limitConfig,
-                                    stationProtocol.getStationId(),
-                                    moveStaNo,
-                                    loadGuardState,
-                                    wrkMast,
-                                    pathLenFactor
-                            );
-                    if (stationDispatchLoadSupport.isDispatchBlocked(
-                            limitConfig,
-                            currentStationTaskCountRef[0],
-                            loadGuardState,
-                            loopHitResult.isThroughLoop())) {
-                        return;
-                    }
-
-                    StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
-                            stationThread,
-                            wrkMast,
-                            stationProtocol.getStationId(),
-                            moveStaNo,
-                            pathLenFactor
-                    );
-                    if (command == null) {
-                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-                        continue;
-                    }
-
-                    Date now = new Date();
-                    wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
-                    wrkMast.setSystemMsg("");
-                    wrkMast.setIoTime(now);
-                    wrkMast.setModiTime(now);
-                    if (wrkMastService.updateById(wrkMast)) {
-                        wrkAnalysisService.markOutboundStationStart(wrkMast, now);
-                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
-                        if (offered && stationMoveCoordinator != null) {
-                            stationMoveCoordinator.recordDispatch(
-                                    wrkMast.getWrkNo(),
-                                    stationProtocol.getStationId(),
-                                    "crnStationOutExecute",
-                                    command,
-                                    false
-                            );
-                        }
-                        News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}",
-                                stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
-                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
-                        redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
-                        currentStationTaskCountRef[0]++;
-                        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
-                        stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
-                    }
-                }
+            if (wrkMast == null || wrkMast.getWrkNo() == null) {
+                return;
             }
+            if (shouldStopAfterPendingDispatchCheck(wrkMast)) {
+                return;
+            }
+            dispatchCrnStationOutTask(wrkMast);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
-    public void dualCrnStationOutExecute() {
+    public void confirmPendingCrnStationOutDispatch(WrkMast wrkMast) {
         try {
-            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
-                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
-                    .isNotNull("dual_crn_no"));
-            for (WrkMast wrkMast : wrkMasts) {
-                Object infoObj = redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
-                if (infoObj == null) {
-                    News.info("鍑哄簱浠诲姟{}鏁版嵁缂撳瓨涓嶅瓨鍦�", wrkMast.getWrkNo());
+            shouldStopAfterPendingDispatchCheck(wrkMast);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void dispatchNextCrnStationOutTask(Integer sourceStationId) {
+        try {
+            if (sourceStationId == null) {
+                return;
+            }
+            List<WrkMast> sameSourceTaskList = loadCrnOutboundCompleteTasksBySourceStation(sourceStationId);
+            if (sameSourceTaskList.isEmpty()) {
+                return;
+            }
+            WrkMast dispatchCandidate = null;
+            int pendingTaskCount = 0;
+            int dispatchableTaskCount = 0;
+            for (WrkMast sameSourceTask : sameSourceTaskList) {
+                if (sameSourceTask == null || sameSourceTask.getWrkNo() == null) {
                     continue;
                 }
-
-                StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class);
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
-                if (stationThread == null) {
+                if (hasPendingDispatch(sameSourceTask.getWrkNo())) {
+                    pendingTaskCount++;
                     continue;
                 }
+                dispatchableTaskCount++;
+                if (dispatchCandidate == null) {
+                    dispatchCandidate = sameSourceTask;
+                }
+            }
+            if (dispatchCandidate == null) {
+                return;
+            }
+            if (dispatchableTaskCount > 1) {
+                News.taskError(dispatchCandidate.getWrkNo(),
+                        "鍑哄簱寮傚父锛氬悓涓�婧愮珯瀛樺湪澶氱瑪寰呬笅鍙戞惉杩愬畬鎴愪换鍔★紝涓嶄笅鍙戝懡浠ゃ�傛簮绔�={}锛屽緟涓嬪彂鏁伴噺={}锛屽緟纭鏁伴噺={}",
+                        sourceStationId, dispatchableTaskCount, pendingTaskCount);
+                return;
+            }
+            if (pendingTaskCount > 0 || sameSourceTaskList.size() > 1) {
+                News.info("婧愮珯鍑哄簱鍊欓�変换鍔″凡纭畾銆傛簮绔�={}锛屽�欓�夊伐浣滃彿={}锛屽緟纭鏁伴噺={}锛屽緟涓嬪彂鏁伴噺={}",
+                        sourceStationId,
+                        dispatchCandidate.getWrkNo(),
+                        pendingTaskCount,
+                        dispatchableTaskCount);
+            }
+            dispatchCrnStationOutTask(dispatchCandidate);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 
-                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
-                StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
-                if (stationProtocol == null) {
-                    continue;
+    private boolean shouldStopAfterPendingDispatchCheck(WrkMast wrkMast) {
+        if (wrkMast == null || wrkMast.getWrkNo() == null) {
+            return true;
+        }
+        Object pendingDispatchMarker = redisUtil.get(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkMast.getWrkNo());
+        if (pendingDispatchMarker == null) {
+            return false;
+        }
+        return processPendingDispatch(wrkMast, pendingDispatchMarker);
+    }
+
+    private boolean processPendingDispatch(WrkMast wrkMast, Object pendingDispatchMarker) {
+        if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.OUTBOUND_RUN_COMPLETE.sts)) {
+            clearPendingDispatch(wrkMast);
+            return true;
+        }
+
+        StationObjModel sourceStationObjModel = getOutboundSourceStation(wrkMast);
+        if (sourceStationObjModel == null
+                || sourceStationObjModel.getDeviceNo() == null
+                || sourceStationObjModel.getStationId() == null) {
+            clearPendingDispatch(wrkMast);
+            return true;
+        }
+
+        StationThread sourceStationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, sourceStationObjModel.getDeviceNo());
+        if (sourceStationThread != null) {
+            List<Integer> taskNoList = sourceStationThread.getAllTaskNoList();
+            if (taskNoList != null && taskNoList.contains(wrkMast.getWrkNo())) {
+                Date now = new Date();
+                wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
+                wrkMast.setSystemMsg("");
+                wrkMast.setIoTime(now);
+                wrkMast.setModiTime(now);
+                if (wrkMastService.updateById(wrkMast)) {
+                    wrkAnalysisService.markOutboundStationStart(wrkMast, now);
+                    notifyUtils.notify(String.valueOf(SlaveType.Devp), sourceStationObjModel.getDeviceNo(),
+                            String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(),
+                            NotifyMsgType.STATION_OUT_TASK_RUN, null);
+                    clearPendingDispatch(wrkMast);
+                    News.info("杈撻�佽澶囧凡鍙戠幇浠诲姟鍙凤紝浠诲姟杞繍琛屼腑銆俤eviceNo={}锛屾簮绔�={}锛屽伐浣滃彿={}",
+                            sourceStationObjModel.getDeviceNo(), sourceStationObjModel.getStationId(), wrkMast.getWrkNo());
+                }
+                return true;
+            }
+        }
+
+        long createdAt;
+        try {
+            createdAt = Long.parseLong(String.valueOf(pendingDispatchMarker));
+        } catch (Exception ignore) {
+            createdAt = System.currentTimeMillis();
+        }
+        long pendingDiscoverTimeoutMs = resolvePendingDiscoverTimeoutMs();
+        if (System.currentTimeMillis() - createdAt < pendingDiscoverTimeoutMs) {
+            return true;
+        }
+
+        PendingDispatchGuardResult guardResult = evaluatePendingDispatchGuard(wrkMast, sourceStationObjModel, sourceStationThread);
+        if (guardResult.keepPending()) {
+            News.info("杈撻�佺珯鐐圭瓑寰呮簮绔欐墽琛岃秴鏃跺悗缁х画淇濇寔绛夊緟銆傚伐浣滃彿={}锛屾簮绔�={}锛屽師鍥�={}",
+                    wrkMast.getWrkNo(),
+                    sourceStationObjModel.getStationId(),
+                    guardResult.reason());
+            markPendingDispatch(wrkMast.getWrkNo());
+            return true;
+        }
+        clearPendingDispatch(wrkMast);
+        News.warn("杈撻�佺珯鐐规墽琛岀‘璁よ秴鏃讹紝涓旀湭鍙戠幇鏈夋晥娲诲姩閾捐矾锛屽凡閲婃斁閲嶈瘯璧勬牸銆傚伐浣滃彿={}", wrkMast.getWrkNo());
+        return false;
+    }
+
+    public void dualCrnStationOutExecute(WrkMast wrkMast) {
+        try {
+            if (wrkMast == null || wrkMast.getWrkNo() == null) {
+                return;
+            }
+            if (hasPendingDispatch(wrkMast.getWrkNo())) {
+                return;
+            }
+            StationObjModel stationObjModel = getOutboundSourceStation(wrkMast);
+            if (stationObjModel == null || stationObjModel.getDeviceNo() == null || stationObjModel.getStationId() == null) {
+                return;
+            }
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
+            if (stationThread == null) {
+                return;
+            }
+
+            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
+            StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
+            if (stationProtocol == null) {
+                return;
+            }
+
+            Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
+            if (lock != null) {
+                return;
+            }
+
+            if (stationProtocol.isAutoing()
+                    && stationProtocol.isLoading()
+                    && stationProtocol.getTaskNo() == 0) {
+                Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+                StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
+                        stationThread,
+                        wrkMast,
+                        stationProtocol.getStationId(),
+                        wrkMast.getStaNo(),
+                        pathLenFactor
+                );
+                if (command == null) {
+                    News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+                    return;
                 }
 
-                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
-                if (lock != null) {
-                    continue;
+                boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute");
+                if (!offered) {
+                    return;
                 }
-
-                if (stationProtocol.isAutoing()
-                        && stationProtocol.isLoading()
-                        && stationProtocol.getTaskNo() == 0) {
-                    Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
-                    StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
-                            stationThread,
-                            wrkMast,
+                if (stationMoveCoordinator != null) {
+                    stationMoveCoordinator.recordDispatch(
+                            wrkMast.getWrkNo(),
                             stationProtocol.getStationId(),
-                            wrkMast.getStaNo(),
-                            pathLenFactor
+                            "dualCrnStationOutExecute",
+                            command,
+                            false
                     );
-                    if (command == null) {
-                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-                        continue;
-                    }
-
-                    wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
-                    wrkMast.setSystemMsg("");
-                    wrkMast.setIoTime(new Date());
-                    if (wrkMastService.updateById(wrkMast)) {
-                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute");
-                        if (offered && stationMoveCoordinator != null) {
-                            stationMoveCoordinator.recordDispatch(
-                                    wrkMast.getWrkNo(),
-                                    stationProtocol.getStationId(),
-                                    "dualCrnStationOutExecute",
-                                    command,
-                                    false
-                            );
-                        }
-                        notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(),
-                                String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(),
-                                NotifyMsgType.STATION_OUT_TASK_RUN, null);
-                        News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}",
-                                stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
-                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
-                        redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
-                    }
                 }
+                markPendingDispatch(wrkMast.getWrkNo());
+                News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠ゅ凡鍏ヨ澶囨墽琛岄摼璺紝绛夊緟婧愮珯鎺ュ崟銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}",
+                        stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
+                redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
             }
         } catch (Exception e) {
             e.printStackTrace();
@@ -246,4 +286,282 @@
                 .dispatch(deviceNo, command, "station-operate-process", scene);
         return dispatchResult.isAccepted();
     }
+
+    private StationObjModel getOutboundSourceStation(WrkMast wrkMast) {
+        if (wrkMast == null || wrkMast.getSourceStaNo() == null) {
+            return null;
+        }
+        return getOutboundSourceStation(wrkMast.getSourceStaNo());
+    }
+
+    private StationObjModel getOutboundSourceStation(Integer sourceStationId) {
+        if (sourceStationId == null) {
+            return null;
+        }
+        BasStation basStation = basStationService.getById(sourceStationId);
+        if (basStation == null || basStation.getDeviceNo() == null) {
+            return null;
+        }
+        StationObjModel stationObjModel = new StationObjModel();
+        stationObjModel.setStationId(sourceStationId);
+        stationObjModel.setDeviceNo(basStation.getDeviceNo());
+        return stationObjModel;
+    }
+
+    private void dispatchCrnStationOutTask(WrkMast wrkMast) {
+        if (wrkMast == null || wrkMast.getWrkNo() == null) {
+            return;
+        }
+        StationObjModel stationObjModel = getOutboundSourceStation(wrkMast);
+        if (stationObjModel == null || stationObjModel.getDeviceNo() == null || stationObjModel.getStationId() == null) {
+            return;
+        }
+
+        StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
+        if (stationThread == null) {
+            return;
+        }
+
+        Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
+        StationProtocol stationProtocol = stationMap == null ? null : stationMap.get(stationObjModel.getStationId());
+        if (stationProtocol == null) {
+            return;
+        }
+
+        Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
+        if (lock != null) {
+            return;
+        }
+
+        if (!(stationProtocol.isAutoing()
+                && stationProtocol.isLoading()
+                && stationProtocol.getTaskNo() == 0)) {
+            return;
+        }
+
+        Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+        List<Integer> outOrderList = stationOutboundDecisionSupport.getAllOutOrderList();
+        OutOrderDispatchDecision dispatchDecision =
+                stationOutboundDecisionSupport.resolveOutboundDispatchDecision(
+                        stationProtocol.getStationId(),
+                        wrkMast,
+                        outOrderList,
+                        pathLenFactor
+                );
+        Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
+        if (moveStaNo == null) {
+            return;
+        }
+
+        DispatchLimitConfig limitConfig =
+                stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo);
+        int currentStationTaskCount = stationDispatchLoadSupport.countCurrentStationTask();
+        LoadGuardState loadGuardState = stationDispatchLoadSupport.buildLoadGuardState(limitConfig);
+        LoopHitResult loopHitResult =
+                stationDispatchLoadSupport.findPathLoopHit(
+                        limitConfig,
+                        stationProtocol.getStationId(),
+                        moveStaNo,
+                        loadGuardState,
+                        wrkMast,
+                        pathLenFactor
+                );
+        if (stationDispatchLoadSupport.isDispatchBlocked(
+                limitConfig,
+                currentStationTaskCount,
+                loadGuardState,
+                loopHitResult.isThroughLoop())) {
+            return;
+        }
+
+        StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
+                stationThread,
+                wrkMast,
+                stationProtocol.getStationId(),
+                moveStaNo,
+                pathLenFactor
+        );
+        if (command == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+            return;
+        }
+
+        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
+        if (!offered) {
+            return;
+        }
+        if (stationMoveCoordinator != null) {
+            stationMoveCoordinator.recordDispatch(
+                    wrkMast.getWrkNo(),
+                    stationProtocol.getStationId(),
+                    "crnStationOutExecute",
+                    command,
+                    false
+            );
+        }
+        markPendingDispatch(wrkMast.getWrkNo());
+        News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠ゅ凡鍏ヨ澶囨墽琛岄摼璺紝绛夊緟婧愮珯鎵ц銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}",
+                stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
+        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
+        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+        stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
+    }
+
+    private List<WrkMast> loadCrnOutboundCompleteTasksBySourceStation(Integer sourceStationId) {
+        if (sourceStationId == null) {
+            return new ArrayList<>();
+        }
+        return wrkMastService.list(new QueryWrapper<WrkMast>()
+                .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
+                .eq("source_sta_no", sourceStationId)
+                .isNotNull("crn_no")
+                .orderByAsc("io_time", "wrk_no"));
+    }
+
+    private boolean hasPendingDispatch(Integer wrkNo) {
+        return wrkNo != null && redisUtil.get(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkNo) != null;
+    }
+
+    private void markPendingDispatch(Integer wrkNo) {
+        if (wrkNo == null) {
+            return;
+        }
+        redisUtil.set(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkNo, String.valueOf(System.currentTimeMillis()), PENDING_DISPATCH_EXPIRE_SECONDS);
+    }
+
+    private void clearPendingDispatch(Integer wrkNo) {
+        if (wrkNo == null) {
+            return;
+        }
+        redisUtil.del(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkNo);
+    }
+
+    private void clearPendingDispatch(WrkMast wrkMast) {
+        if (wrkMast == null || wrkMast.getWrkNo() == null) {
+            return;
+        }
+        clearPendingDispatch(wrkMast.getWrkNo());
+    }
+
+    private PendingDispatchGuardResult evaluatePendingDispatchGuard(WrkMast wrkMast,
+                                                                    StationObjModel stationObjModel,
+                                                                    StationThread stationThread) {
+        if (wrkMast == null || wrkMast.getWrkNo() == null || stationObjModel == null || stationObjModel.getStationId() == null) {
+            return PendingDispatchGuardResult.release("missing-runtime-context");
+        }
+        if (hasActiveMoveSession(wrkMast.getWrkNo(), stationObjModel.getStationId())) {
+            return PendingDispatchGuardResult.keep("active-session");
+        }
+        if (hasRecentSuccessfulDispatch(wrkMast.getWrkNo(), stationObjModel.getStationId())) {
+            return PendingDispatchGuardResult.keep("recent-successful-dispatch");
+        }
+        if (taskExistsInBuffer(stationThread, wrkMast.getWrkNo())) {
+            return PendingDispatchGuardResult.keep("task-buffer-detected");
+        }
+        return PendingDispatchGuardResult.release("no-active-chain");
+    }
+
+    private boolean hasActiveMoveSession(Integer wrkNo, Integer sourceStationId) {
+        if (wrkNo == null || wrkNo <= 0 || stationMoveCoordinator == null || sourceStationId == null) {
+            return false;
+        }
+        StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo);
+        if (session == null || !session.isActive()) {
+            return false;
+        }
+        if (!Objects.equals(sourceStationId, session.getDispatchStationId())) {
+            return false;
+        }
+        long now = System.currentTimeMillis();
+        Long lastIssuedAt = session.getLastIssuedAt();
+        if (lastIssuedAt != null && now - lastIssuedAt <= resolvePendingSessionProtectMs()) {
+            return true;
+        }
+        Long updatedAt = session.getUpdatedAt();
+        return updatedAt != null && now - updatedAt <= resolvePendingSessionProtectMs();
+    }
+
+    private boolean hasRecentSuccessfulDispatch(Integer wrkNo, Integer sourceStationId) {
+        if (stationDispatchRuntimeStateSupport == null) {
+            return false;
+        }
+        return stationDispatchRuntimeStateSupport.hasRecentIssuedMoveCommand(
+                wrkNo,
+                sourceStationId,
+                resolveRecentDispatchProtectMs()
+        );
+    }
+
+    private boolean taskExistsInBuffer(StationThread stationThread, Integer wrkNo) {
+        if (stationThread == null || wrkNo == null || wrkNo <= 0) {
+            return false;
+        }
+        Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+        if (statusMap == null || statusMap.isEmpty()) {
+            return false;
+        }
+        for (StationProtocol stationProtocol : statusMap.values()) {
+            if (stationProtocol == null || stationProtocol.getTaskBufferItems() == null) {
+                continue;
+            }
+            if (containsTaskNo(stationProtocol, wrkNo)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean containsTaskNo(StationProtocol stationProtocol, Integer wrkNo) {
+        if (stationProtocol == null || stationProtocol.getTaskBufferItems() == null || wrkNo == null) {
+            return false;
+        }
+        return stationProtocol.getTaskBufferItems().stream()
+                .filter(Objects::nonNull)
+                .anyMatch(item -> Objects.equals(wrkNo, item.getTaskNo()));
+    }
+
+    private long resolvePendingDiscoverTimeoutMs() {
+        return resolveMillis(pendingDiscoverTimeoutSeconds, DEFAULT_PENDING_DISCOVER_TIMEOUT_MS);
+    }
+
+    private long resolvePendingSessionProtectMs() {
+        return resolveMillis(pendingSessionProtectSeconds, DEFAULT_PENDING_SESSION_PROTECT_MS);
+    }
+
+    private long resolveRecentDispatchProtectMs() {
+        return resolveMillis(recentDispatchProtectSeconds, DEFAULT_RECENT_DISPATCH_PROTECT_MS);
+    }
+
+    private long resolveMillis(long seconds, long defaultMs) {
+        if (seconds <= 0L) {
+            return defaultMs;
+        }
+        return seconds * 1000L;
+    }
+
+    private static class PendingDispatchGuardResult {
+        private final boolean keepPending;
+        private final String reason;
+
+        private PendingDispatchGuardResult(boolean keepPending, String reason) {
+            this.keepPending = keepPending;
+            this.reason = reason;
+        }
+
+        public static PendingDispatchGuardResult keep(String reason) {
+            return new PendingDispatchGuardResult(true, reason);
+        }
+
+        public static PendingDispatchGuardResult release(String reason) {
+            return new PendingDispatchGuardResult(false, reason);
+        }
+
+        public boolean keepPending() {
+            return keepPending;
+        }
+
+        public String reason() {
+            return reason;
+        }
+    }
 }

--
Gitblit v1.9.1