From d0dfd9606e3ccc6f5cd4647270328b52b2e5a384 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期二, 21 四月 2026 19:43:45 +0800
Subject: [PATCH] feat: 添加堆垛机TargetAddress输送线站台检查
---
Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-check.md | 186 ++++++++++++++++
Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-search.md | 143 +++++++++++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs | 93 +++++++
Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-search-design.md | 97 ++++++++
Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-check-design.md | 95 ++++++++
5 files changed, 607 insertions(+), 7 deletions(-)
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
index 80771a1..4500212 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -9,6 +9,7 @@
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
+using WIDESEAWCS_QuartzJob.ConveyorLine.Enum;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
@@ -62,7 +63,7 @@
/// <param name="httpClientHelper">HTTP 瀹㈡埛绔府鍔╃被</param>
/// <param name="logger">鏃ュ織璁板綍鍣�</param>
public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper, ILogger logger)
- : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum), logger)
+ : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum, logger), logger)
{
}
@@ -146,6 +147,23 @@
return selectedTask;
}
+ // ===== TargetAddress 涓嶅彲鐢ㄦ椂锛屽厛灏濊瘯鍚� NextAddress 鐨勫叾浠栦换鍔� =====
+ var sameStationTasks = _taskService
+ .QueryStackerCraneOutTasks(deviceCode, new List<string> { candidateTask.NextAddress })
+ .Where(x => x.TaskId != candidateTask.TaskId)
+ .ToList();
+
+ foreach (var sameStationTask in sameStationTasks)
+ {
+ selectedTask = TrySelectOutboundTask(sameStationTask);
+ if (selectedTask != null)
+ {
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑鍚岀珯鍙板閫夊嚭搴撲换鍔★紝浠诲姟鍙�: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+ }
+ }
+
+ // ===== 鍚� NextAddress 鏃犲彲鐢ㄤ换鍔★紝灏濊瘯涓嶅悓 NextAddress 鐨勪换鍔� =====
// 鏌ユ壘鍏朵粬鍙敤鐨勫嚭搴撶珯鍙�
var otherOutStationCodes = _routerService
.QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType)
@@ -184,7 +202,21 @@
/// <returns>鍙�変腑鐨勪换鍔★紝鎴� null锛堢珯鍙颁笉鍙敤锛�</returns>
private Dt_Task? TrySelectOutboundTask(Dt_Task outboundTask)
{
- // 瀵逛簬鎵�鏈夊嚭搴撲换鍔★紝蹇呴』鍏堣皟鐢� WMS 鍒ゆ柇鏄惁闇�瑕佺Щ搴�
+ // 鍏堣繘琛屾湰鍦扮珯鍙版鏌ワ紙PLC 璇诲彇锛屽揩閫燂級锛岄伩鍏嶄笉蹇呰鐨� WMS HTTP 璋冪敤
+
+ // 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+ if (!IsTargetAddressConveyorStationAvailable(outboundTask))
+ {
+ return null;
+ }
+
+ // 鍒ゆ柇 NextAddress 鍑哄簱绔欏彴鏄惁鍙敤
+ if (!IsOutTaskStationAvailable(outboundTask))
+ {
+ return null;
+ }
+
+ // 绔欏彴妫�鏌ラ�氳繃鍚庯紝璋冪敤 WMS 鍒ゆ柇鏄惁闇�瑕佺Щ搴�
var taskAfterTransferCheck = _transferCheck(outboundTask.TaskNum) ?? outboundTask;
var taskGroup = taskAfterTransferCheck.TaskType.GetTaskTypeGroup();
@@ -206,8 +238,7 @@
return taskAfterTransferCheck;
}
- // 鍒ゆ柇鍑哄簱绔欏彴鏄惁鍙敤
- return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null;
+ return taskAfterTransferCheck;
}
/// <summary>
@@ -219,18 +250,24 @@
/// <param name="httpClientHelper">HTTP 瀹㈡埛绔府鍔╃被</param>
/// <param name="taskNum">浠诲姟鍙�</param>
/// <returns>濡傛灉闇�瑕佺Щ搴撹繑鍥炵Щ搴撲换鍔★紝鍚﹀垯杩斿洖 null</returns>
- private static Dt_Task? QueryTransferTask(HttpClientHelper httpClientHelper, int taskNum)
+ private static Dt_Task? QueryTransferTask(HttpClientHelper httpClientHelper, int taskNum, ILogger logger)
{
// 璋冪敤 WMS 鐨勭Щ搴撴鏌ユ帴鍙�
+ string configKey = nameof(ConfigKey.TransferCheck);
+ string requestParam = taskNum.ToString();
+
var response = httpClientHelper.Post<WebResponseContent>(
- nameof(ConfigKey.TransferCheck),
- taskNum.ToString());
+ configKey,
+ requestParam);
// 妫�鏌ュ搷搴旀槸鍚︽垚鍔�
if (response == null || !response.IsSuccess || response.Data == null || !response.Data.Status || response.Data.Data == null)
{
+ QuartzLogHelper.LogError(logger, $"璋冪敤WMS鎺ュ彛澶辫触,鎺ュ彛:銆恵configKey}銆�,璇锋眰鍙傛暟:銆恵requestParam}銆�,閿欒淇℃伅:銆恵(response?.Data?.Message ?? "鏃犲搷搴�")}銆�", "StackerCraneTaskSelector");
return null;
}
+
+ QuartzLogHelper.LogInfo(logger, $"璋冪敤WMS鎺ュ彛鎴愬姛,鎺ュ彛:銆恵configKey}銆�,鍝嶅簲鏁版嵁:銆恵response.Data.Data}銆�", "StackerCraneTaskSelector");
// 瑙f瀽杩斿洖鐨勪换鍔℃暟鎹�
var taskJson = response.Data.Data.ToString();
@@ -313,5 +350,47 @@
return isOccupied;
}
+
+ /// <summary>
+ /// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+ /// </summary>
+ /// <param name="task">鍑哄簱浠诲姟</param>
+ /// <returns>绔欏彴绌洪棽锛圕V_State == 2锛夎繑鍥� true</returns>
+ private bool IsTargetAddressConveyorStationAvailable([NotNull] Dt_Task task)
+ {
+ // 纭畾浠诲姟绫诲瀷
+ int taskType = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty
+ ? StackerCraneConst.EmptyPalletTaskType
+ : task.TaskType;
+
+ // 閫氳繃璺敱鏌ユ壘 TargetAddress 瀵瑰簲鐨勮澶囦俊鎭�
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType);
+ if (router == null)
+ {
+ QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌 TargetAddress 璺敱淇℃伅锛孴argetAddress: {TargetAddress}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌 TargetAddress 璺敱淇℃伅锛孴argetAddress: {task.TargetAddress}", task.Roadway, task.TargetAddress, task.TaskNum);
+ return false;
+ }
+
+ // 鏌ユ壘杈撻�佺嚎璁惧
+ IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
+ if (device == null)
+ {
+ QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌杈撻�佺嚎璁惧锛孋hildPosiDeviceCode: {ChildPosiDeviceCode}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌杈撻�佺嚎璁惧锛孋hildPosiDeviceCode: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum);
+ return false;
+ }
+
+ // 杞崲涓鸿緭閫佺嚎璁惧
+ CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
+
+ // 璇诲彇 CV_State锛孋V_State == 2 琛ㄧず绌洪棽
+ byte cvState = conveyorLine.GetValue<ConveyorLineStatus, byte>(ConveyorLineStatus.CV_State, task.TargetAddress);
+ bool isAvailable = cvState == 2;
+ QuartzLogHelper.LogInfo(_logger, "IsTargetAddressConveyorStationAvailable锛歍argetAddress: {TargetAddress}锛孋V_State: {CV_State}锛屾槸鍚︾┖闂�: {IsAvailable}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛歍argetAddress: {task.TargetAddress}锛孋V_State: {cvState}锛屾槸鍚︾┖闂�: {isAvailable}", task.Roadway, task.TargetAddress, cvState, isAvailable, task.TaskNum);
+
+ return isAvailable;
+ }
}
}
\ No newline at end of file
diff --git a/Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-check.md b/Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-check.md
new file mode 100644
index 0000000..89aa8a0
--- /dev/null
+++ b/Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-check.md
@@ -0,0 +1,186 @@
+# 鍫嗗灈鏈� TargetAddress 杈撻�佺嚎绔欏彴绌洪棽妫�鏌� 瀹炴柦璁″垝
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 鍦� `StackerCraneTaskSelector.TrySelectOutboundTask` 涓柊澧� `TargetAddress` 杈撻�佺嚎绔欏彴绌洪棽妫�鏌ワ紙CV_State == 2 琛ㄧず绌洪棽锛夛紝涓嶅彲鐢ㄥ垯杩斿洖 null銆�
+
+**Architecture:** 鍦� `StackerCraneTaskSelector` 绫讳腑鏂板 `IsTargetAddressConveyorStationAvailable` 绉佹湁鏂规硶锛岄�氳繃 `_routerService.QueryNextRoute` 鑾峰彇璺敱淇℃伅锛屼粠 `Storage.Devices` 鎵惧埌杈撻�佺嚎璁惧锛岃皟鐢� `GetValue<ConveyorLineStatus, byte>` 璇诲彇 `CV_State` 鍒ゆ柇鏄惁绌洪棽銆�
+
+**Tech Stack:** C# / .NET 6+锛孲qlSugar ORM锛孲erilog
+
+---
+
+## 娑夊強鏂囦欢
+
+- 淇敼: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
+
+---
+
+## Task 1: 鏂板 IsTargetAddressConveyorStationAvailable 鏂规硶
+
+**Files:**
+- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
+
+- [ ] **Step 1: 纭鐜版湁 using 鍛藉悕绌洪棿**
+
+鎵撳紑 `StackerCraneTaskSelector.cs`锛屾鏌ユ槸鍚﹀凡鍖呭惈浠ヤ笅 using锛�
+- `WIDESEAWCS_QuartzJob.ConveyorLine.Enum.ConveyorLineStatus`
+- `WIDESEAWCS_Model.Models.Dt_Router`
+
+濡傜己灏戯紝娣诲姞锛�
+```csharp
+using WIDESEAWCS_QuartzJob.ConveyorLine.Enum;
+using WIDESEAWCS_Model.Models;
+```
+
+- [ ] **Step 2: 鍦ㄧ被鏈熬锛坄IsOutTaskStationAvailable` 鏂规硶涔嬪悗锛宍}` 涔嬪墠锛夋柊澧炴柟娉�**
+
+```csharp
+/// <summary>
+/// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+/// </summary>
+/// <param name="task">鍑哄簱浠诲姟</param>
+/// <returns>绔欏彴绌洪棽锛圕V_State == 2锛夎繑鍥� true</returns>
+private bool IsTargetAddressConveyorStationAvailable([NotNull] Dt_Task task)
+{
+ // 纭畾浠诲姟绫诲瀷
+ int taskType = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty
+ ? StackerCraneConst.EmptyPalletTaskType
+ : task.TaskType;
+
+ // 閫氳繃璺敱鏌ユ壘 TargetAddress 瀵瑰簲鐨勮澶囦俊鎭�
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType);
+ if (router == null)
+ {
+ QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌 TargetAddress 璺敱淇℃伅锛孴argetAddress: {TargetAddress}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌 TargetAddress 璺敱淇℃伅锛孴argetAddress: {task.TargetAddress}", task.Roadway, task.TargetAddress, task.TaskNum);
+ return false;
+ }
+
+ // 鏌ユ壘杈撻�佺嚎璁惧
+ IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
+ if (device == null)
+ {
+ QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌杈撻�佺嚎璁惧锛孋hildPosiDeviceCode: {ChildPosiDeviceCode}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌杈撻�佺嚎璁惧锛孋hildPosiDeviceCode: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum);
+ return false;
+ }
+
+ // 杞崲涓鸿緭閫佺嚎璁惧
+ CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
+
+ // 璇诲彇 CV_State锛孋V_State == 2 琛ㄧず绌洪棽
+ byte cvState = conveyorLine.GetValue<ConveyorLineStatus, byte>(ConveyorLineStatus.CV_State, task.TargetAddress);
+ bool isAvailable = cvState == 2;
+ QuartzLogHelper.LogInfo(_logger, "IsTargetAddressConveyorStationAvailable锛歍argetAddress: {TargetAddress}锛孋V_State: {CV_State}锛屾槸鍚︾┖闂�: {IsAvailable}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛歍argetAddress: {task.TargetAddress}锛孋V_State: {cvState}锛屾槸鍚︾┖闂�: {isAvailable}", task.Roadway, task.TargetAddress, cvState, isAvailable, task.TaskNum);
+
+ return isAvailable;
+}
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
+git commit -m "feat(StackerCraneTaskSelector): 鏂板 TargetAddress 杈撻�佺嚎绔欏彴绌洪棽妫�鏌ユ柟娉�
+
+IsTargetAddressConveyorStationAvailable 閫氳繃璺敱鏌ユ壘杈撻�佺嚎璁惧锛岃鍙� CV_State 鍒ゆ柇鏄惁绌洪棽锛圕V_State == 2锛�
+
+Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
+```
+
+---
+
+## Task 2: 鍦� TrySelectOutboundTask 涓皟鐢ㄦ柊鏂规硶
+
+**Files:**
+- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
+
+- [ ] **Step 1: 鎵惧埌 TrySelectOutboundTask 鏂规硶涓殑 return 璇彞**
+
+褰撳墠鏂规硶鏈熬锛坙ine ~210锛夛細
+```csharp
+return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null;
+```
+
+- [ ] **Step 2: 鍦� return 璇彞涔嬪墠鎻掑叆 TargetAddress 妫�鏌ラ�昏緫**
+
+鍦� `return IsOutTaskStationAvailable(...)` 涔嬪墠鎻掑叆锛�
+```csharp
+// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck))
+{
+ return null;
+}
+```
+
+淇敼鍚庯細
+```csharp
+// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck))
+{
+ return null;
+}
+
+// 鍒ゆ柇鍑哄簱绔欏彴鏄惁鍙敤
+return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null;
+```
+
+- [ ] **Step 3: 楠岃瘉淇敼浣嶇疆姝g‘**
+
+纭淇敼鍚庣殑 `TrySelectOutboundTask` 鏂规硶鏈熬閫昏緫涓猴細
+```csharp
+// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck))
+{
+ return null;
+}
+
+// 鍒ゆ柇鍑哄簱绔欏彴鏄惁鍙敤
+return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null;
+```
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
+git commit -m "feat(StackerCraneTaskSelector): 鍦� TrySelectOutboundTask 涓皟鐢� TargetAddress 绔欏彴绌洪棽妫�鏌�
+
+妫�鏌ラ『搴忥細鍏堟鏌� NextAddress 鍑哄簱绔欏彴锛屽啀妫�鏌� TargetAddress 杈撻�佺嚎绔欏彴
+鑻� TargetAddress 绔欏彴涓嶇┖闂诧紙CV_State != 2锛夛紝浠诲姟涓嶅彲閫�
+
+Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
+```
+
+---
+
+## Task 3: 楠岃瘉鏋勫缓
+
+- [ ] **Step 1: 鎵ц鏋勫缓楠岃瘉**
+
+```bash
+cd D:/Git/ShanMeiXinNengYuan/Code
+dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
+```
+
+棰勬湡锛氭棤缂栬瘧閿欒
+
+- [ ] **Step 2: 濡傛湁閿欒锛屽垎鏋愬苟淇**
+
+甯歌閿欒锛�
+- 缂哄皯 using 鈫� 娣诲姞鍛藉悕绌洪棿
+- 绫诲瀷杞崲寮傚父 鈫� 纭璁惧绫诲瀷涓� `CommonConveyorLine`
+- 鏂规硶鏈壘鍒� 鈫� 纭鏂规硶绛惧悕姝g‘
+
+---
+
+## 楠岃瘉娓呭崟
+
+- [ ] `IsTargetAddressConveyorStationAvailable` 鏂规硶宸叉坊鍔犲湪 `IsOutTaskStationAvailable` 鏂规硶涔嬪悗
+- [ ] `TrySelectOutboundTask` 鏂规硶鍦ㄥ垽鏂� `IsOutTaskStationAvailable` 涔嬪墠锛屽厛鍒ゆ柇 `IsTargetAddressConveyorStationAvailable`
+- [ ] 鏂板鏂规硶鍖呭惈瀹屾暣鐨勬棩蹇楄褰曪紙Warn 鍜� Info锛�
+- [ ] `CV_State == 2` 浣滀负绌洪棽鍒ゆ柇鏉′欢
+- [ ] `task.TargetAddress` 鐩存帴浣滀负 `GetValue` 鐨勮澶囧瓙缂栧彿
+- [ ] 鏋勫缓閫氳繃锛屾棤缂栬瘧閿欒
+- [ ] 宸叉彁浜や袱涓� commit锛堟柟娉曟柊澧� + 璋冪敤鐐癸級
diff --git a/Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-search.md b/Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-search.md
new file mode 100644
index 0000000..fc67c36
--- /dev/null
+++ b/Code/docs/superpowers/plans/2026-04-21-stacker-crane-target-address-search.md
@@ -0,0 +1,143 @@
+# 鍫嗗灈鏈� TargetAddress 涓嶅彲鐢ㄦ椂缁х画鎼滅储 瀹炴柦璁″垝
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 鍦� `SelectTask` 鏂规硶涓紝褰� `TrySelectOutboundTask` 鍥� `TargetAddress` 涓嶅彲鐢ㄨ繑鍥� null 鏃讹紝澧炲姞鍚� NextAddress 鍏朵粬浠诲姟鐨勬悳绱㈤�昏緫銆�
+
+**Architecture:** 鍦� `SelectTask` 鐨� `TrySelectOutboundTask(candidateTask)` 杩斿洖 null 涔嬪悗銆佺幇鏈夊閫変换鍔″惊鐜箣鍓嶏紝鎻掑叆鍚� NextAddress 浠诲姟鎼滅储銆�
+
+**Tech Stack:** C# / .NET 6+锛孲qlSugar ORM锛孲erilog
+
+---
+
+## 娑夊強鏂囦欢
+
+- 淇敼: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
+
+---
+
+## Task 1: 淇敼 SelectTask 鏂规硶 - 娣诲姞鍚� NextAddress 浠诲姟鎼滅储
+
+**Files:**
+- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
+
+- [ ] **Step 1: 鎵惧埌 SelectTask 鏂规硶涓殑鐩爣浣嶇疆**
+
+鎵撳紑 `StackerCraneTaskSelector.cs`锛屾壘鍒� `SelectTask` 鏂规硶涓殑浠ヤ笅浠g爜锛堢害 lines 143-166锛夛細
+
+```csharp
+// 灏濊瘯閫夋嫨鍑哄簱浠诲姟锛堝彲鑳介渶瑕佺Щ搴撴鏌ュ拰绔欏彴鍙敤鎬у垽鏂級
+Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask);
+if (selectedTask != null)
+{
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑鍑哄簱浠诲姟锛屼换鍔″彿: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+}
+
+// 鏌ユ壘鍏朵粬鍙敤鐨勫嚭搴撶珯鍙�
+var otherOutStationCodes = _routerService
+ .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType)
+ .Select(x => x.ChildPosi)
+ .ToList();
+
+// 鏌ヨ鍏朵粬绔欏彴鐨勫嚭搴撲换鍔�
+var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes);
+foreach (var alternativeTask in tasks)
+{
+ selectedTask = TrySelectOutboundTask(alternativeTask);
+ if (selectedTask != null)
+ {
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑澶囬�夊嚭搴撲换鍔★紝浠诲姟鍙�: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+ }
+}
+```
+
+**Step 2: 鍦� `// 鏌ユ壘鍏朵粬鍙敤鐨勫嚭搴撶珯鍙癭 涔嬪墠鎻掑叆鍚� NextAddress 鎼滅储閫昏緫**
+
+灏嗭細
+```csharp
+// 鏌ユ壘鍏朵粬鍙敤鐨勫嚭搴撶珯鍙�
+var otherOutStationCodes = _routerService
+ ...
+```
+
+鏇挎崲涓猴細
+```csharp
+// ===== TargetAddress 涓嶅彲鐢ㄦ椂锛屽厛灏濊瘯鍚� NextAddress 鐨勫叾浠栦换鍔� =====
+var sameStationTasks = _taskService
+ .QueryStackerCraneOutTasks(deviceCode, new List<string> { candidateTask.NextAddress })
+ .Where(x => x.TaskId != candidateTask.TaskId)
+ .ToList();
+
+foreach (var sameStationTask in sameStationTasks)
+{
+ selectedTask = TrySelectOutboundTask(sameStationTask);
+ if (selectedTask != null)
+ {
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑鍚岀珯鍙板閫夊嚭搴撲换鍔★紝浠诲姟鍙�: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+ }
+}
+
+// ===== 鍚� NextAddress 鏃犲彲鐢ㄤ换鍔★紝灏濊瘯涓嶅悓 NextAddress 鐨勪换鍔� =====
+// 鏌ユ壘鍏朵粬鍙敤鐨勫嚭搴撶珯鍙�
+var otherOutStationCodes = _routerService
+ .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType)
+ .Select(x => x.ChildPosi)
+ .ToList();
+
+// 鏌ヨ鍏朵粬绔欏彴鐨勫嚭搴撲换鍔�
+var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes);
+foreach (var alternativeTask in tasks)
+{
+ selectedTask = TrySelectOutboundTask(alternativeTask);
+ if (selectedTask != null)
+ {
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑澶囬�夊嚭搴撲换鍔★紝浠诲姟鍙�: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+ }
+}
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
+git commit -m "feat(StackerCraneTaskSelector): TargetAddress涓嶅彲鐢ㄦ椂鎼滅储鍚孨extAddress鐨勫叾浠栦换鍔�
+
+褰� TrySelectOutboundTask 鍥� TargetAddress 杈撻�佺嚎绔欏彴涓嶇┖闂茶繑鍥� null 鏃讹紝
+鍏堝皾璇曞悓 NextAddress 鐨勫叾浠栧嚭搴撲换鍔★紝鍐嶅皾璇曚笉鍚� NextAddress 鐨勪换鍔�
+
+Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>"
+```
+
+---
+
+## Task 2: 楠岃瘉鏋勫缓
+
+- [ ] **Step 1: 鎵ц鏋勫缓楠岃瘉**
+
+```bash
+cd D:/Git/ShanMeiXinNengYuan/Code
+dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
+```
+
+棰勬湡锛氭棤缂栬瘧閿欒锛堟祴璇曢」鐩殑棰勫瓨鍦ㄩ敊璇彲蹇界暐锛�
+
+- [ ] **Step 2: 濡傛湁閿欒锛屽垎鏋愬苟淇**
+
+甯歌閿欒锛�
+- 绫诲瀷涓嶅尮閰� 鈫� 纭 `QueryStackerCraneOutTasks` 杩斿洖 `List<Dt_Task>`
+- 缂哄皯 using 鈫� 娣诲姞 `using System.Linq;`
+
+---
+
+## 楠岃瘉娓呭崟
+
+- [ ] 鍚� NextAddress 浠诲姟鎼滅储閫昏緫宸叉坊鍔犲湪 `TrySelectOutboundTask` 杩斿洖 null 涔嬪悗
+- [ ] 浣跨敤 `TaskId` 杩囨护鎺掗櫎褰撳墠浠诲姟
+- [ ] 鍚� NextAddress 鏃犲彲鐢ㄤ换鍔″悗缁х画灏濊瘯涓嶅悓 NextAddress 鐨勪换鍔�
+- [ ] 鏃ュ織璁板綍"鍚岀珯鍙板閫夊嚭搴撲换鍔�"
+- [ ] 鏋勫缓閫氳繃锛屾棤缂栬瘧閿欒
+- [ ] 宸叉彁浜� commit
diff --git a/Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-check-design.md b/Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-check-design.md
new file mode 100644
index 0000000..23e4b80
--- /dev/null
+++ b/Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-check-design.md
@@ -0,0 +1,95 @@
+# 鍫嗗灈鏈哄嚭搴撲换鍔� TargetAddress 杈撻�佺嚎绔欏彴绌洪棽妫�鏌� 璁捐鏂囨。
+
+## 1. 鑳屾櫙涓庣洰鏍�
+
+鍦� `StackerCraneTaskSelector.TrySelectOutboundTask` 鏂规硶涓紝褰撳墠鍙鏌ュ嚭搴撶珯鍙扮殑 `NextAddress` 鏄惁鍙敤锛坄IsOutTaskStationAvailable`锛夈�傞渶姹傦細澧炲姞瀵� `TargetAddress` 杈撻�佺嚎绔欏彴鐨勭┖闂茬姸鎬佹鏌ャ�傚彧鏈夊綋 `TargetAddress` 杈撻�佺嚎绔欏彴绌洪棽锛坄CV_State == 2`锛夋椂锛屾墠璁や负璇ュ嚭搴撲换鍔$湡姝e彲閫夈��
+
+## 2. 璁捐鏂规
+
+### 2.1 鏂板绉佹湁鏂规硶
+
+鍦� `StackerCraneTaskSelector` 绫讳腑鏂板鏂规硶 `IsTargetAddressConveyorStationAvailable`锛�
+
+```csharp
+/// <summary>
+/// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+/// </summary>
+/// <param name="task">鍑哄簱浠诲姟</param>
+/// <returns>绔欏彴绌洪棽杩斿洖 true锛屽惁鍒欒繑鍥� false</returns>
+private bool IsTargetAddressConveyorStationAvailable([NotNull] Dt_Task task)
+{
+ // 纭畾浠诲姟绫诲瀷
+ int taskType = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty
+ ? StackerCraneConst.EmptyPalletTaskType
+ : task.TaskType;
+
+ // 閫氳繃璺敱鏌ユ壘 TargetAddress 瀵瑰簲鐨勮澶囦俊鎭�
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType);
+ if (router == null)
+ {
+ QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌 TargetAddress 璺敱淇℃伅锛孴argetAddress: {TargetAddress}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌 TargetAddress 璺敱淇℃伅锛孴argetAddress: {task.TargetAddress}", task.Roadway, task.TargetAddress, task.TaskNum);
+ return false;
+ }
+
+ // 鏌ユ壘杈撻�佺嚎璁惧
+ IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
+ if (device == null)
+ {
+ QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌杈撻�佺嚎璁惧锛孋hildPosiDeviceCode: {ChildPosiDeviceCode}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛氭湭鎵惧埌杈撻�佺嚎璁惧锛孋hildPosiDeviceCode: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum);
+ return false;
+ }
+
+ // 杞崲涓鸿緭閫佺嚎璁惧
+ CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
+
+ // 璇诲彇 CV_State锛孋V_State == 2 琛ㄧず绌洪棽
+ byte cvState = conveyorLine.GetValue<ConveyorLineStatus, byte>(ConveyorLineStatus.CV_State, task.TargetAddress);
+ bool isAvailable = cvState == 2;
+ QuartzLogHelper.LogInfo(_logger, "IsTargetAddressConveyorStationAvailable锛歍argetAddress: {TargetAddress}锛孋V_State: {CV_State}锛屾槸鍚︾┖闂�: {IsAvailable}锛屼换鍔″彿: {TaskNum}",
+ $"IsTargetAddressConveyorStationAvailable锛歍argetAddress: {task.TargetAddress}锛孋V_State: {cvState}锛屾槸鍚︾┖闂�: {isAvailable}", task.Roadway, task.TargetAddress, cvState, isAvailable, task.TaskNum);
+
+ return isAvailable;
+}
+```
+
+### 2.2 璋冪敤鐐逛慨鏀�
+
+鍦� `TrySelectOutboundTask` 鏂规硶涓紝鍦� `return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null;` 涔嬪墠鎻掑叆锛�
+
+```csharp
+// 鍒ゆ柇 TargetAddress 杈撻�佺嚎绔欏彴鏄惁绌洪棽
+if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck))
+{
+ return null;
+}
+```
+
+### 2.3 娴佺▼鍥�
+
+```
+鍑哄簱浠诲姟杩涘叆 TrySelectOutboundTask
+ 鈫�
+绉诲簱妫�鏌ワ紙_transferCheck锛�
+ 鈫�
+鏄惁绉诲簱浠诲姟锛熸槸 鈫� 杩斿洖浠诲姟
+ 鈫擄紙鍚︼級
+IsOutTaskStationAvailable(NextAddress 鍑哄簱绔欏彴) 鈫� 涓嶅彲鐢� 鈫� 杩斿洖 null
+ 鈫擄紙鍙敤锛�
+IsTargetAddressConveyorStationAvailable(TargetAddress 杈撻�佺嚎绔欏彴) 鈫� 涓嶅彲鐢� 鈫� 杩斿洖 null
+ 鈫擄紙鍙敤锛屽嵆 CV_State == 2锛�
+杩斿洖浠诲姟
+```
+
+## 3. 娑夊強鐨勫懡鍚嶇┖闂�
+
+鏂板鏂规硶闇�瑕� using锛�
+- `WIDESEAWCS_QuartzJob.ConveyorLine.Enum.ConveyorLineStatus`锛坄ConveyorLineStatus.CV_State` 鏋氫妇锛�
+- `WIDESEAWCS_Model.Models.Dt_Router`锛堣矾鐢卞疄浣擄級
+- `WIDESEAWCS_Core.Storage`锛堣澶囧瓨鍌級
+
+## 4. 椋庨櫓涓庣害鏉�
+
+- `CV_State == 2` 琛ㄧず绌洪棽鏄笟鍔$害瀹氾紝闇�涓� PLC 渚у崗璁繚鎸佷竴鑷�
+- 濡傝澶囨湭杩炴帴鎴栧崗璁厤缃己澶憋紝`GetValue` 浼氭姏鍑哄紓甯革紝褰撳墠璁捐璁╁紓甯稿悜涓婁紶鎾紝璋冪敤鏂归�氳繃 try-catch 鍏滃簳
diff --git a/Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-search-design.md b/Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-search-design.md
new file mode 100644
index 0000000..5380254
--- /dev/null
+++ b/Code/docs/superpowers/specs/2026-04-21-stacker-crane-target-address-search-design.md
@@ -0,0 +1,97 @@
+# 鍫嗗灈鏈哄嚭搴撲换鍔� TargetAddress 涓嶅彲鐢ㄦ椂缁х画鎼滅储 璁捐鏂囨。
+
+## 1. 鑳屾櫙涓庣洰鏍�
+
+鍦� `StackerCraneTaskSelector.SelectTask` 鏂规硶涓紝褰� `TrySelectOutboundTask` 鍥� `TargetAddress` 杈撻�佺嚎绔欏彴涓嶇┖闂诧紙`CV_State != 2`锛夎�岃繑鍥� null 鏃讹紝闇�瑕佺户缁悳绱㈠叾浠栧彲閫夌殑鍑哄簱浠诲姟锛岃�屼笉鏄洿鎺ヨ繑鍥� null 鎴栦粎灏濊瘯涓嶅悓 NextAddress 鐨勪换鍔°��
+
+## 2. 璁捐鏂规
+
+### 2.1 鎼滅储椤哄簭
+
+褰� TargetAddress 涓嶅彲鐢ㄦ椂锛屾寜浠ヤ笅椤哄簭鎼滅储澶囬�変换鍔★細
+
+1. **鍚� NextAddress 鐨勫叾浠栦换鍔�** - 鏌ヨ涓庡綋鍓嶄换鍔� NextAddress 鐩稿悓鐨勫叾浠栧嚭搴撲换鍔★紝灏濊瘯鍏� TargetAddress 鏄惁鍙敤
+2. **涓嶅悓 NextAddress 鐨勪换鍔�** - 鏌ヨ鍏朵粬绔欏彴锛圢 extAddress 涓嶅悓锛夌殑鍑哄簱浠诲姟锛堢幇鏈夐�昏緫锛�
+
+### 2.2 浠g爜淇敼
+
+鍦� `SelectTask` 鏂规硶涓紝`TrySelectOutboundTask(candidateTask)` 杩斿洖 null 涔嬪悗銆佺幇鏈夊閫変换鍔″惊鐜箣鍓嶏紝鎻掑叆鍚� NextAddress 浠诲姟鎼滅储閫昏緫锛�
+
+```csharp
+// 灏濊瘯閫夋嫨鍑哄簱浠诲姟
+Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask);
+if (selectedTask != null)
+{
+ return selectedTask;
+}
+
+// ===== TargetAddress 涓嶅彲鐢ㄦ椂锛屽厛灏濊瘯鍚� NextAddress 鐨勫叾浠栦换鍔� =====
+// 鏌ヨ涓庡綋鍓嶄换鍔� NextAddress 鐩稿悓鐨勫叾浠栧嚭搴撲换鍔�
+var sameStationTasks = _taskService
+ .QueryStackerCraneOutTasks(deviceCode, new List<string> { candidateTask.NextAddress })
+ .Where(x => x.TaskId != candidateTask.TaskId)
+ .ToList();
+
+foreach (var sameStationTask in sameStationTasks)
+{
+ selectedTask = TrySelectOutboundTask(sameStationTask);
+ if (selectedTask != null)
+ {
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑鍚岀珯鍙板閫夊嚭搴撲换鍔★紝浠诲姟鍙�: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+ }
+}
+
+// ===== 鍚� NextAddress 鏃犲彲鐢ㄤ换鍔★紝灏濊瘯涓嶅悓 NextAddress 鐨勪换鍔★紙鐜版湁閫昏緫锛�=====
+var otherOutStationCodes = _routerService
+ .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType)
+ .Select(x => x.ChildPosi)
+ .ToList();
+
+var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes);
+foreach (var alternativeTask in tasks)
+{
+ selectedTask = TrySelectOutboundTask(alternativeTask);
+ if (selectedTask != null)
+ {
+ QuartzLogHelper.LogDebug(_logger, $"閫変腑澶囬�夊嚭搴撲换鍔★紝浠诲姟鍙�: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
+ return selectedTask;
+ }
+}
+```
+
+### 2.3 鏃犻渶鏂板鏂规硶
+
+鍒╃敤鐜版湁鐨� `QueryStackerCraneOutTasks(deviceCode, stationCodes)` 鏂规硶锛屼紶鍏ュ崟绔欏彴鍒楄〃鍗冲彲鏌ヨ鍚� NextAddress 鐨勪换鍔°�傝繃婊ゆ帀褰撳墠浠诲姟锛坄TaskId` 涓嶅悓锛夐伩鍏嶉噸澶嶅皾璇曘��
+
+### 2.4 娴佺▼鍥�
+
+```
+SelectTask
+ 鈫�
+TrySelectOutboundTask(candidateTask)
+ 鈫�
+TargetAddress 鍙敤锛熷惁 鈫� 杩斿洖 null
+ 鈫�
+鏌ヨ鍚� NextAddress 鐨勫叾浠栧嚭搴撲换鍔�
+ 鈫�
+閬嶅巻鍚岀珯鍙颁换鍔�
+ 鈫� TrySelectOutboundTask(task) 鈫� 鍙敤 鈫� 杩斿洖浠诲姟
+ 鈫� 涓嶅彲鐢� 鈫� 缁х画涓嬩竴涓�
+ 鈫�
+鍚� NextAddress 鏃犲彲鐢ㄤ换鍔�
+ 鈫�
+鏌ヨ涓嶅悓 NextAddress 鐨勫嚭搴撲换鍔★紙鐜版湁閫昏緫锛�
+ 鈫�
+閬嶅巻 鈫� 杩斿洖鍙敤浠诲姟 / 鏃犲彲鐢� 鈫� 杩斿洖 null
+```
+
+## 3. 娑夊強鐨勫懡鍚嶇┖闂�
+
+鏃犻渶鏂板 using锛岀幇鏈夋柟娉曠鍚嶅凡婊¤冻銆�
+
+## 4. 椋庨櫓涓庣害鏉�
+
+- `TaskId` 浣滀负鍞竴鏍囪瘑鐢ㄤ簬杩囨护宸插皾璇曚换鍔★紝鍋囪浠诲姟琛ㄤ腑鏃犻噸澶� TaskId
+- `QueryStackerCraneOutTasks` 杩斿洖鐨勪换鍔″垪琛ㄩ渶瑕佽�冭檻鎺掑簭锛堝鏋滄湁涓氬姟瑙勫垯鍐冲畾浼樺厛绾э級
+- 濡傛灉鍚� NextAddress 鏈夊ぇ閲忎换鍔★紝鍙兘澧炲姞閫夋嫨寤惰繜
--
Gitblit v1.9.3