From 0005d58f6888dd3e4524784d1b6f103f9b1c588e Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期一, 30 三月 2026 18:33:22 +0800
Subject: [PATCH] 合并

---
 Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/BackgroundServices/StockMonitorBackgroundService.cs |  199 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 199 insertions(+), 0 deletions(-)

diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/BackgroundServices/StockMonitorBackgroundService.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/BackgroundServices/StockMonitorBackgroundService.cs
new file mode 100644
index 0000000..074c48e
--- /dev/null
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/BackgroundServices/StockMonitorBackgroundService.cs
@@ -0,0 +1,199 @@
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using WIDESEA_Core.BaseRepository;
+using WIDESEA_IStockService;
+using WIDESEA_Model.Models;
+using WIDESEA_WMSServer.Hubs;
+
+namespace WIDESEA_WMSServer.BackgroundServices
+{
+    /// <summary>
+    /// 搴撳瓨鐩戞帶鍚庡彴鏈嶅姟
+    /// 瀹氭湡妫�鏌ュ簱瀛樺拰璐т綅鏁版嵁鍙樺寲骞堕�氳繃SignalR鎺ㄩ�佸埌鍓嶇
+    /// </summary>
+    public class StockMonitorBackgroundService : BackgroundService
+    {
+        private readonly ILogger<StockMonitorBackgroundService> _logger;
+        private readonly IHubContext<StockHub> _hubContext;
+        private readonly IServiceProvider _serviceProvider;
+
+        // 璐т綅鐘舵�佸揩鐓э細key = LocationId
+        private ConcurrentDictionary<int, LocationSnapshot> _lastLocationSnapshots = new();
+
+        // 鐩戞帶闂撮殧锛堟绉掞級
+        private const int MonitorIntervalMs = 3000;
+
+        public StockMonitorBackgroundService(
+            ILogger<StockMonitorBackgroundService> logger,
+            IHubContext<StockHub> hubContext,
+            IServiceProvider serviceProvider)
+        {
+            _logger = logger;
+            _hubContext = hubContext;
+            _serviceProvider = serviceProvider;
+        }
+
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            _logger.LogInformation("搴撳瓨鐩戞帶鍚庡彴鏈嶅姟宸插惎鍔�");
+
+            // 绛夊緟搴旂敤瀹屽叏鍚姩
+            await Task.Delay(5000, stoppingToken);
+
+            while (!stoppingToken.IsCancellationRequested)
+            {
+                try
+                {
+                    await CheckChangesAsync();
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "妫�鏌ユ暟鎹彉鍖栨椂鍙戠敓閿欒");
+                }
+
+                await Task.Delay(MonitorIntervalMs, stoppingToken);
+            }
+
+            _logger.LogInformation("搴撳瓨鐩戞帶鍚庡彴鏈嶅姟宸插仠姝�");
+        }
+
+        /// <summary>
+        /// 妫�鏌ヨ揣浣嶅拰搴撳瓨鍙樺寲
+        /// </summary>
+        private async Task CheckChangesAsync()
+        {
+            using var scope = _serviceProvider.CreateScope();
+            var stockService = scope.ServiceProvider.GetRequiredService<IStockInfoService>();
+            var locationRepo = scope.ServiceProvider.GetRequiredService<IRepository<Dt_LocationInfo>>();
+
+            // 1. 鑾峰彇鎵�鏈夎揣浣嶆暟鎹�
+            var allLocations = await locationRepo.QueryDataAsync(x => x.LocationStatus != 99); // 鎺掗櫎绂佺敤鐨勮揣浣�
+
+            // 2. 鑾峰彇鎵�鏈夊簱瀛樻暟鎹紙鍖呭惈鏄庣粏锛�
+            var allStockData = await stockService.Repository.Db.Queryable<Dt_StockInfo>()
+                .Includes(x => x.Details)
+                .ToListAsync();
+
+            // 鏋勫缓搴撳瓨瀛楀吀锛歀ocationId -> StockInfo
+            var stockDict = allStockData
+                .Where(s => s.LocationId > 0)
+                .ToDictionary(s => s.LocationId, s => s);
+
+            // 鏋勫缓褰撳墠璐т綅蹇収瀛楀吀
+            var currentSnapshots = new ConcurrentDictionary<int, LocationSnapshot>();
+
+            foreach (var location in allLocations)
+            {
+                // 鑾峰彇璇ヨ揣浣嶇殑搴撳瓨淇℃伅
+                stockDict.TryGetValue(location.Id, out var stock);
+
+                // 璁$畻搴撳瓨鏁伴噺
+                float totalQuantity = 0;
+                string detailsHash = string.Empty;
+                if (stock?.Details != null && stock.Details.Any())
+                {
+                    totalQuantity = stock.Details.Sum(d => d.StockQuantity);
+                    detailsHash = GenerateDetailsHash(stock.Details.ToList());
+                }
+
+                var snapshot = new LocationSnapshot
+                {
+                    LocationId = location.Id,
+                    WarehouseId = location.WarehouseId,
+                    LocationCode = location.LocationCode,
+                    LocationStatus = location.LocationStatus,
+                    PalletCode = stock?.PalletCode,
+                    StockStatus = stock?.StockStatus ?? 0,
+                    StockQuantity = totalQuantity,
+                    DetailsHash = detailsHash
+                };
+
+                currentSnapshots.TryAdd(location.Id, snapshot);
+
+                // 妫�鏌ユ槸鍚︽湁鍙樺寲
+                if (_lastLocationSnapshots.TryGetValue(location.Id, out var lastSnapshot))
+                {
+                    // 妫�娴嬪彉鍖栵細璐т綅鐘舵�併�佸簱瀛樼姸鎬併�佹暟閲忋�佹槑缁嗗彉鍖�
+                    if (lastSnapshot.LocationStatus != snapshot.LocationStatus ||
+                        lastSnapshot.StockStatus != snapshot.StockStatus ||
+                        lastSnapshot.PalletCode != snapshot.PalletCode ||
+                        Math.Abs(lastSnapshot.StockQuantity - snapshot.StockQuantity) > 0.001f ||
+                        lastSnapshot.DetailsHash != snapshot.DetailsHash)
+                    {
+                        // 鏋勫缓鏇存柊DTO
+                        var update = new StockUpdateDTO
+                        {
+                            LocationId = snapshot.LocationId,
+                            WarehouseId = snapshot.WarehouseId,
+                            PalletCode = snapshot.PalletCode,
+                            StockQuantity = snapshot.StockQuantity,
+                            StockStatus = snapshot.StockStatus,
+                            LocationStatus = snapshot.LocationStatus,
+                            Details = BuildDetailDtos(stock?.Details?.ToList())
+                        };
+
+                        await _hubContext.Clients.All.SendAsync("StockUpdated", update);
+                        _logger.LogDebug("鏁版嵁鍙樺寲鎺ㄩ��: LocationId={LocationId}, LocStatus={LocStatus}, StockStatus={StockStatus}, Quantity={Quantity}",
+                            snapshot.LocationId, snapshot.LocationStatus, snapshot.StockStatus, snapshot.StockQuantity);
+                    }
+                }
+            }
+
+            // 鏇存柊蹇収鏁版嵁
+            _lastLocationSnapshots = currentSnapshots;
+        }
+
+        /// <summary>
+        /// 鐢熸垚鏄庣粏鏁版嵁鍝堝笇
+        /// </summary>
+        private string GenerateDetailsHash(List<Dt_StockInfoDetail> details)
+        {
+            if (details == null || !details.Any()) return string.Empty;
+
+            var hashString = string.Join("|", details
+                .OrderBy(d => d.Id)
+                .Select(d => $"{d.Id}:{d.MaterielCode}:{d.BatchNo}:{d.StockQuantity}"));
+            return hashString.GetHashCode().ToString();
+        }
+
+        /// <summary>
+        /// 鏋勫缓鏄庣粏DTO鍒楄〃
+        /// </summary>
+        private List<StockDetailUpdateDTO> BuildDetailDtos(List<Dt_StockInfoDetail> details)
+        {
+            if (details == null || !details.Any()) return new List<StockDetailUpdateDTO>();
+
+            return details.Select(d => new StockDetailUpdateDTO
+            {
+                Id = d.Id,
+                MaterielCode = d.MaterielCode,
+                MaterielName = d.MaterielName,
+                BatchNo = d.BatchNo,
+                StockQuantity = d.StockQuantity,
+                Unit = d.Unit,
+                Status = d.Status
+            }).ToList();
+        }
+
+        /// <summary>
+        /// 璐т綅蹇収
+        /// </summary>
+        private class LocationSnapshot
+        {
+            public int LocationId { get; set; }
+            public int WarehouseId { get; set; }
+            public string LocationCode { get; set; }
+            public int LocationStatus { get; set; }
+            public string PalletCode { get; set; }
+            public int StockStatus { get; set; }
+            public float StockQuantity { get; set; }
+            public string DetailsHash { get; set; }
+        }
+    }
+}

--
Gitblit v1.9.3