| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # èªå¨åºåºä»»å¡åå°æå¡è®¾è®¡ææ¡£ |
| | | |
| | | **æ¥æ**: 2026-03-06 |
| | | **ä½è
**: Claude Code |
| | | **ç¶æ**: å¾
宿½ |
| | | |
| | | ## æ¦è¿° |
| | | |
| | | æ¬è®¾è®¡æ¨å¨å®ç°ä¸ä¸ªèªå¨åºåºä»»å¡åå°æå¡ï¼è¯¥æå¡å¨ WMS åºç¨ç¨åºå¯å¨åèªå¨è¿è¡ï¼å®ææ£æ¥åºåä¸å°æéè¦åºåºçç©æï¼å¹¶èªå¨å建åºåºä»»å¡éç¥ WCS ç³»ç»æ§è¡ã |
| | | |
| | | ## éæ±èæ¯ |
| | | |
| | | å½åç³»ç»ä¸ï¼åºåè®°å½å
å« `OutboundDate` åæ®µï¼è¡¨ç¤ºé¢æçåºåºæ¥æãéè¦å®ç°ä¸ä¸ªåå°æå¡ï¼èªå¨æ£æµå·²å°æï¼`OutboundDate <= å½åæ¶é´`ï¼çåºåï¼å¹¶å建ç¸åºçåºåºä»»å¡ã |
| | | |
| | | ## ææ¯æ¹æ¡ |
| | | |
| | | ### æ¶æéæ© |
| | | |
| | | éç¨ **BackgroundService** 模å¼å®ç°åå°å®æ¶ä»»å¡ã |
| | | |
| | | **çç±**ï¼ |
| | | - .NET æ 忍¡å¼ï¼ç®åææ |
| | | - æ éé¢å¤ä¾èµ |
| | | - çå½å¨æä¸åºç¨ç¨åºç»å®ï¼èªå¨ç®¡çå¯å¨/忢 |
| | | - æäºé
ç½®æ£æ¥é´é |
| | | |
| | | ### ç»ä»¶ç»æ |
| | | |
| | | ``` |
| | | WIDESEA_WMSServer/ |
| | | âââ BackgroundServices/ |
| | | â âââ AutoOutboundTaskBackgroundService.cs # æ°å»ºï¼åå°æå¡ç±» |
| | | âââ WIDESEA_TaskInfoService/ |
| | | â âââ TaskService.cs # ä¿®æ¹ï¼æ·»å æ°æ¹æ³ |
| | | âââ WIDESEA_ITaskInfoService/ |
| | | â âââ ITaskService.cs # ä¿®æ¹ï¼æ·»å æ¥å£å®ä¹ |
| | | âââ 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` å
裹æ´ä¸ªå¾ªç¯ä½ |
| | | |
| | | ### ä»»å¡åå»ºçº§å« |
| | | - å个任å¡å建失败ä¸å½±åå
¶ä»ä»»å¡ |
| | | - è®°å½å¤±è´¥çæçç ååå |
| | | - è¿å详ç»çæå/失败ç»è®¡ |
| | | |
| | | ### WCS éç¥å¤±è´¥ |
| | | - ä»»å¡å·²åå»ºå°æ°æ®åºï¼ä½ WCS éç¥å¤±è´¥ |
| | | - è®°å½è¦åæ¥å¿ |
| | | - ä¸éè¯ï¼WCS ä¼ä¸»å¨è½®è¯¢è·åä»»å¡ï¼ |
| | | |
| | | ## æ¥å¿è®°å½ |
| | | |
| | | | çº§å« | åºæ¯ | |
| | | |------|------| |
| | | | Information | æå¡å¯å¨/åæ¢ãæ£æ¥å¨æãåå»ºä»»å¡æ°é | |
| | | | Warning | WCS éç¥å¤±è´¥ãæ å°æåºå | |
| | | | Error | å¼å¸¸æ
åµãæ°æ®åºæä½å¤±è´¥ | |
| | | |
| | | ## æµè¯è®¡å |
| | | |
| | | 1. **åå
æµè¯** |
| | | - `CreateAutoOutboundTasksAsync` æ¹æ³é»è¾ |
| | | - `DetermineTargetAddress` å°åæ å°é»è¾ |
| | | |
| | | 2. **éææµè¯** |
| | | - åå°æå¡å¯å¨å忢 |
| | | - é
置项æ£ç¡®å è½½ |
| | | - æ°æ®åºæ¥è¯¢åä»»å¡å建 |
| | | - WCS éç¥æ¥å£è°ç¨ |
| | | |
| | | 3. **æå¨æµè¯** |
| | | - ä¿®æ¹åºå `OutboundDate` 为è¿å»æ¶é´ |
| | | - è§å¯æ¥å¿ç¡®è®¤ä»»å¡å建 |
| | | - éªè¯ WCS æ¯å¦æ¶å°éç¥ |
| | | |
| | | ## é¨ç½²æ³¨æäºé¡¹ |
| | | |
| | | 1. ç¡®ä¿ `appsettings.json` ä¸é
ç½®äºæ£ç¡®ç `AutoOutboundTask` è |
| | | 2. æ ¹æ®å®é
éæ±è°æ´ `CheckIntervalSeconds` |
| | | 3. éªè¯ WCS éç¥æ¥å£å°åæ¯å¦æ£ç¡® |
| | | 4. çæ§åºç¨ç¨åºæ¥å¿ï¼ç¡®è®¤å尿塿£å¸¸è¿è¡ |
| | | |
| | | ## æªæ¥æ¹è¿ |
| | | |
| | | 1. æ¯ææ´å¤æçç®æ å°åé
ç½®è§å |
| | | 2. æ·»å ä»»å¡å建çç»è®¡æ°æ®åçæ§ |
| | | 3. æ¯ææå¨è§¦åä»»å¡å建çç®¡çæ¥å£ |
| | | 4. èè使ç¨åå¸å¼éæ¯æå¤å®ä¾é¨ç½² |