From d812bb29149f19feac04d0723ce5795e0fddb658 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期五, 06 三月 2026 17:08:18 +0800
Subject: [PATCH] 添加自动出库任务后台服务设计文档
---
Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-06-auto-outbound-task-design.md | 368 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 368 insertions(+), 0 deletions(-)
diff --git a/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-06-auto-outbound-task-design.md b/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-06-auto-outbound-task-design.md
new file mode 100644
index 0000000..d684f04
--- /dev/null
+++ b/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-06-auto-outbound-task-design.md
@@ -0,0 +1,368 @@
+# 鑷姩鍑哄簱浠诲姟鍚庡彴鏈嶅姟璁捐鏂囨。
+
+**鏃ユ湡**: 2026-03-06
+**浣滆��**: Claude Code
+**鐘舵��**: 寰呭疄鏂�
+
+## 姒傝堪
+
+鏈璁℃棬鍦ㄥ疄鐜颁竴涓嚜鍔ㄥ嚭搴撲换鍔″悗鍙版湇鍔★紝璇ユ湇鍔″湪 WMS 搴旂敤绋嬪簭鍚姩鍚庤嚜鍔ㄨ繍琛岋紝瀹氭湡妫�鏌ュ簱瀛樹腑鍒版湡闇�瑕佸嚭搴撶殑鐗╂枡锛屽苟鑷姩鍒涘缓鍑哄簱浠诲姟閫氱煡 WCS 绯荤粺鎵ц銆�
+
+## 闇�姹傝儗鏅�
+
+褰撳墠绯荤粺涓紝搴撳瓨璁板綍鍖呭惈 `OutboundDate` 瀛楁锛岃〃绀洪鏈熺殑鍑哄簱鏃ユ湡銆傞渶瑕佸疄鐜颁竴涓悗鍙版湇鍔★紝鑷姩妫�娴嬪凡鍒版湡锛坄OutboundDate <= 褰撳墠鏃堕棿`锛夌殑搴撳瓨锛屽苟鍒涘缓鐩稿簲鐨勫嚭搴撲换鍔°��
+
+## 鎶�鏈柟妗�
+
+### 鏋舵瀯閫夋嫨
+
+閲囩敤 **BackgroundService** 妯″紡瀹炵幇鍚庡彴瀹氭椂浠诲姟銆�
+
+**鐞嗙敱**锛�
+- .NET 鏍囧噯妯″紡锛岀畝鍗曟槗鎳�
+- 鏃犻渶棰濆渚濊禆
+- 鐢熷懡鍛ㄦ湡涓庡簲鐢ㄧ▼搴忕粦瀹氾紝鑷姩绠$悊鍚姩/鍋滄
+- 鏄撲簬閰嶇疆妫�鏌ラ棿闅�
+
+### 缁勪欢缁撴瀯
+
+```
+WIDESEA_WMSServer/
+鈹溾攢鈹� BackgroundServices/
+鈹� 鈹斺攢鈹� AutoOutboundTaskBackgroundService.cs # 鏂板缓锛氬悗鍙版湇鍔$被
+鈹溾攢鈹� WIDESEA_TaskInfoService/
+鈹� 鈹斺攢鈹� TaskService.cs # 淇敼锛氭坊鍔犳柊鏂规硶
+鈹溾攢鈹� WIDESEA_ITaskInfoService/
+鈹� 鈹斺攢鈹� ITaskService.cs # 淇敼锛氭坊鍔犳帴鍙e畾涔�
+鈹溾攢鈹� WIDESEA_Core/Core/
+鈹� 鈹斺攢鈹� AutoOutboundTaskOptions.cs # 鏂板缓锛氶厤缃ā鍨嬬被
+鈹斺攢鈹� Program.cs # 淇敼锛氭敞鍐屽悗鍙版湇鍔″拰閰嶇疆
+```
+
+## 璇︾粏璁捐
+
+### 1. 閰嶇疆璁捐
+
+鍦� `appsettings.json` 涓坊鍔犻厤缃妭锛�
+
+```json
+{
+ "AutoOutboundTask": {
+ "Enable": true, // 鏄惁鍚敤鑷姩鍑哄簱浠诲姟
+ "CheckIntervalSeconds": 300, // 妫�鏌ラ棿闅旓紙绉掞級锛岄粯璁�5鍒嗛挓
+ "TargetAddresses": { // 鎸夊贩閬撻厤缃洰鏍囧湴鍧�
+ "GW": "10081", // 楂樻俯宸烽亾鐩爣鍦板潃
+ "CW": "10080" // 甯告俯宸烽亾鐩爣鍦板潃
+ }
+ }
+}
+```
+
+### 2. 閰嶇疆妯″瀷绫�
+
+**鏂囦欢**: `WIDESEA_Core/Core/AutoOutboundTaskOptions.cs`
+
+```csharp
+namespace WIDESEA_Core.Core
+{
+ public class AutoOutboundTaskOptions
+ {
+ /// <summary>
+ /// 鏄惁鍚敤鑷姩鍑哄簱浠诲姟
+ /// </summary>
+ public bool Enable { get; set; } = true;
+
+ /// <summary>
+ /// 妫�鏌ラ棿闅旓紙绉掞級
+ /// </summary>
+ public int CheckIntervalSeconds { get; set; } = 300;
+
+ /// <summary>
+ /// 鎸夊贩閬撳墠缂�閰嶇疆鐩爣鍦板潃
+ /// </summary>
+ public Dictionary<string, string> TargetAddresses { get; set; }
+ = new()
+ {
+ { "GW", "10081" },
+ { "CW", "10080" }
+ };
+ }
+}
+```
+
+### 3. 鍚庡彴鏈嶅姟绫�
+
+**鏂囦欢**: `WIDESEA_WMSServer/BackgroundServices/AutoOutboundTaskBackgroundService.cs`
+
+```csharp
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace WIDESEA_WMSServer.BackgroundServices
+{
+ public class AutoOutboundTaskBackgroundService : BackgroundService
+ {
+ private readonly ILogger<AutoOutboundTaskBackgroundService> _logger;
+ private readonly ITaskService _taskService;
+ private readonly AutoOutboundTaskOptions _options;
+
+ public AutoOutboundTaskBackgroundService(
+ ILogger<AutoOutboundTaskBackgroundService> logger,
+ ITaskService taskService,
+ IOptions<AutoOutboundTaskOptions> options)
+ {
+ _logger = logger;
+ _taskService = taskService;
+ _options = options.Value;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ _logger.LogInformation("鑷姩鍑哄簱浠诲姟鍚庡彴鏈嶅姟宸插惎鍔�");
+
+ if (!_options.Enable)
+ {
+ _logger.LogInformation("鑷姩鍑哄簱浠诲姟鍔熻兘宸茬鐢�");
+ return;
+ }
+
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ try
+ {
+ _logger.LogDebug("寮�濮嬫鏌ュ埌鏈熷簱瀛�...");
+ var result = await _taskService.CreateAutoOutboundTasksAsync();
+ _logger.LogInformation("鍒版湡搴撳瓨妫�鏌ュ畬鎴�: {Message}", result.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "鑷姩鍑哄簱浠诲姟鍒涘缓澶辫触");
+ }
+
+ var delay = TimeSpan.FromSeconds(_options.CheckIntervalSeconds);
+ _logger.LogDebug("绛夊緟 {Seconds} 绉掑悗杩涜涓嬫妫�鏌�", delay.TotalSeconds);
+ await Task.Delay(delay, stoppingToken);
+ }
+
+ _logger.LogInformation("鑷姩鍑哄簱浠诲姟鍚庡彴鏈嶅姟宸插仠姝�");
+ }
+ }
+}
+```
+
+### 4. TaskService 鏂板鏂规硶
+
+**鎺ュ彛瀹氫箟**: `WIDESEA_ITaskInfoService/ITaskService.cs`
+
+```csharp
+/// <summary>
+/// 鑷姩鍒涘缓鍑哄簱浠诲姟 - 鏌ヨ鍒版湡搴撳瓨骞跺垱寤轰换鍔�
+/// </summary>
+/// <returns>鍖呭惈鍒涘缓缁撴灉鐨勫搷搴斿璞�</returns>
+Task<WebResponseContent> CreateAutoOutboundTasksAsync();
+```
+
+**瀹炵幇**: `WIDESEA_TaskInfoService/TaskService.cs`
+
+```csharp
+public async Task<WebResponseContent> CreateAutoOutboundTasksAsync()
+{
+ try
+ {
+ // 1. 鏌ヨ鍒版湡搴撳瓨
+ var expiredStocks = await _stockInfoService.Repository
+ .QueryAsync(s => s.OutboundDate <= DateTime.Now
+ && s.StockStatus == StockStatusEmun.鍏ュ簱瀹屾垚.GetHashCode()
+ && s.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode(),
+ nameof(Dt_StockInfo.LocationDetails));
+
+ if (!expiredStocks.Any())
+ {
+ return WebResponseContent.Instance.OK("鏃犲埌鏈熷簱瀛橀渶瑕佸鐞�");
+ }
+
+ // 2. 妫�鏌ュ凡瀛樺湪鐨勪换鍔�
+ var palletCodes = expiredStocks.Select(s => s.PalletCode).ToList();
+ var existingTasks = await Repository.QueryAsync(t =>
+ palletCodes.Contains(t.PalletCode)
+ && t.TaskType == TaskTypeEnum.Outbound.GetHashCode()
+ && t.TaskStatus != TaskStatusEnum.Completed.GetHashCode());
+
+ var processedPallets = existingTasks.Select(t => t.PalletCode).ToHashSet();
+
+ // 3. 绛涢�夐渶瑕佸鐞嗙殑搴撳瓨
+ var stocksToProcess = expiredStocks
+ .Where(s => !processedPallets.Contains(s.PalletCode))
+ .ToList();
+
+ if (!stocksToProcess.Any())
+ {
+ return WebResponseContent.Instance.OK("鎵�鏈夊埌鏈熷簱瀛樺凡瀛樺湪浠诲姟");
+ }
+
+ // 4. 鑾峰彇閰嶇疆鐨勭洰鏍囧湴鍧�鏄犲皠
+ var targetAddressMap = _appSettings.Get<Dictionary<string, string>>("AutoOutboundTask:TargetAddresses")
+ ?? new Dictionary<string, string>();
+
+ // 5. 鎵归噺鍒涘缓浠诲姟
+ var taskList = new List<Dt_Task>();
+ foreach (var stock in stocksToProcess)
+ {
+ // 鏍规嵁宸烽亾纭畾鐩爣鍦板潃
+ var targetAddress = DetermineTargetAddress(stock.LocationDetails.RoadwayNo, targetAddressMap);
+
+ var task = new Dt_Task
+ {
+ WarehouseId = stock.WarehouseId,
+ PalletCode = stock.PalletCode,
+ PalletType = stock.PalletType,
+ SourceAddress = stock.LocationCode,
+ CurrentAddress = stock.LocationCode,
+ NextAddress = targetAddress,
+ TargetAddress = targetAddress,
+ Roadway = stock.LocationDetails.RoadwayNo,
+ TaskType = TaskTypeEnum.Outbound.GetHashCode(),
+ TaskStatus = TaskStatusEnum.New.GetHashCode(),
+ Grade = 1,
+ TaskNum = await Repository.GetTaskNo(),
+ Creater = "system_auto"
+ };
+ taskList.Add(task);
+ }
+
+ var addResult = await BaseDal.AddDataAsync(taskList) > 0;
+ if (!addResult)
+ {
+ return WebResponseContent.Instance.Error($"鎵归噺鍒涘缓浠诲姟澶辫触锛屽叡 {taskList.Count} 涓换鍔�");
+ }
+
+ // 6. 閫氱煡 WCS
+ var notifyTasks = taskList.Select(async task =>
+ {
+ try
+ {
+ var wmstaskDto = _mapper.Map<WMSTaskDTO>(task);
+ await _httpClientHelper.Post<WebResponseContent>(
+ "http://logistics-service/api/logistics/notifyoutbound",
+ JsonSerializer.Serialize(wmstaskDto));
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "WCS 閫氱煡澶辫触锛屼换鍔$紪鍙�: {TaskNum}", task.TaskNum);
+ }
+ });
+
+ await Task.WhenAll(notifyTasks);
+
+ return WebResponseContent.Instance.OK($"鎴愬姛鍒涘缓 {taskList.Count} 涓嚭搴撲换鍔�", taskList.Count);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "鑷姩鍒涘缓鍑哄簱浠诲姟鏃跺彂鐢熼敊璇�");
+ return WebResponseContent.Instance.Error($"鑷姩鍒涘缓鍑哄簱浠诲姟澶辫触: {ex.Message}");
+ }
+}
+
+private string DetermineTargetAddress(string roadway, Dictionary<string, string> addressMap)
+{
+ if (string.IsNullOrWhiteSpace(roadway))
+ return "10080"; // 榛樿鍦板潃
+
+ foreach (var kvp in addressMap)
+ {
+ if (roadway.Contains(kvp.Key))
+ return kvp.Value;
+ }
+
+ return "10080"; // 榛樿鍦板潃
+}
+```
+
+### 5. Program.cs 娉ㄥ唽鏈嶅姟
+
+```csharp
+// 閰嶇疆鑷姩鍑哄簱浠诲姟閫夐」
+builder.Services.Configure<AutoOutboundTaskOptions>(
+ builder.Configuration.GetSection("AutoOutboundTask"));
+
+// 娉ㄥ唽鍚庡彴鏈嶅姟
+builder.Services.AddHostedService<AutoOutboundTaskBackgroundService>();
+```
+
+## 鏁版嵁娴佺▼
+
+```mermaid
+graph TD
+ A[搴旂敤绋嬪簭鍚姩] --> B[娉ㄥ唽 AutoOutboundTaskBackgroundService]
+ B --> C[鍚庡彴鏈嶅姟鍚姩]
+ C --> D{妫�鏌� Enable 閰嶇疆}
+ D -->|绂佺敤| E[鏈嶅姟閫�鍑篯
+ D -->|鍚敤| F[杩涘叆瀹氭椂寰幆]
+ F --> G[璋冪敤 CreateAutoOutboundTasksAsync]
+ G --> H[鏌ヨ鍒版湡搴撳瓨]
+ H --> I[妫�鏌ュ凡瀛樺湪鐨勪换鍔
+ I --> J[绛涢�夐渶瑕佸鐞嗙殑搴撳瓨]
+ J --> K[鎵归噺鍒涘缓 Dt_Task]
+ K --> L[閫氱煡 WCS 绯荤粺]
+ L --> M[杩斿洖缁撴灉]
+ M --> N[璁板綍鏃ュ織]
+ N --> O[绛夊緟閰嶇疆鐨勯棿闅旀椂闂碷
+ O --> F
+```
+
+## 閿欒澶勭悊
+
+### 鍚庡彴鏈嶅姟绾у埆
+- 鎹曡幏鎵�鏈夊紓甯革紝璁板綍閿欒鏃ュ織
+- 涓嶄腑鏂湇鍔″惊鐜�
+- 浣跨敤 `try-catch` 鍖呰9鏁翠釜寰幆浣�
+
+### 浠诲姟鍒涘缓绾у埆
+- 鍗曚釜浠诲姟鍒涘缓澶辫触涓嶅奖鍝嶅叾浠栦换鍔�
+- 璁板綍澶辫触鐨勬墭鐩樼爜鍜屽師鍥�
+- 杩斿洖璇︾粏鐨勬垚鍔�/澶辫触缁熻
+
+### WCS 閫氱煡澶辫触
+- 浠诲姟宸插垱寤哄埌鏁版嵁搴擄紝浣� WCS 閫氱煡澶辫触
+- 璁板綍璀﹀憡鏃ュ織
+- 涓嶉噸璇曪紙WCS 浼氫富鍔ㄨ疆璇㈣幏鍙栦换鍔★級
+
+## 鏃ュ織璁板綍
+
+| 绾у埆 | 鍦烘櫙 |
+|------|------|
+| Information | 鏈嶅姟鍚姩/鍋滄銆佹鏌ュ懆鏈熴�佸垱寤轰换鍔℃暟閲� |
+| Warning | WCS 閫氱煡澶辫触銆佹棤鍒版湡搴撳瓨 |
+| Error | 寮傚父鎯呭喌銆佹暟鎹簱鎿嶄綔澶辫触 |
+
+## 娴嬭瘯璁″垝
+
+1. **鍗曞厓娴嬭瘯**
+ - `CreateAutoOutboundTasksAsync` 鏂规硶閫昏緫
+ - `DetermineTargetAddress` 鍦板潃鏄犲皠閫昏緫
+
+2. **闆嗘垚娴嬭瘯**
+ - 鍚庡彴鏈嶅姟鍚姩鍜屽仠姝�
+ - 閰嶇疆椤规纭姞杞�
+ - 鏁版嵁搴撴煡璇㈠拰浠诲姟鍒涘缓
+ - WCS 閫氱煡鎺ュ彛璋冪敤
+
+3. **鎵嬪姩娴嬭瘯**
+ - 淇敼搴撳瓨 `OutboundDate` 涓鸿繃鍘绘椂闂�
+ - 瑙傚療鏃ュ織纭浠诲姟鍒涘缓
+ - 楠岃瘉 WCS 鏄惁鏀跺埌閫氱煡
+
+## 閮ㄧ讲娉ㄦ剰浜嬮」
+
+1. 纭繚 `appsettings.json` 涓厤缃簡姝g‘鐨� `AutoOutboundTask` 鑺�
+2. 鏍规嵁瀹為檯闇�姹傝皟鏁� `CheckIntervalSeconds`
+3. 楠岃瘉 WCS 閫氱煡鎺ュ彛鍦板潃鏄惁姝g‘
+4. 鐩戞帶搴旂敤绋嬪簭鏃ュ織锛岀‘璁ゅ悗鍙版湇鍔℃甯歌繍琛�
+
+## 鏈潵鏀硅繘
+
+1. 鏀寔鏇村鏉傜殑鐩爣鍦板潃閰嶇疆瑙勫垯
+2. 娣诲姞浠诲姟鍒涘缓鐨勭粺璁℃暟鎹拰鐩戞帶
+3. 鏀寔鎵嬪姩瑙﹀彂浠诲姟鍒涘缓鐨勭鐞嗘帴鍙�
+4. 鑰冭檻浣跨敤鍒嗗竷寮忛攣鏀寔澶氬疄渚嬮儴缃�
--
Gitblit v1.9.3