From 0fa137570bf7ac2bf58c8af2828cd595625fa400 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期日, 19 四月 2026 18:53:45 +0800
Subject: [PATCH] Merge branch 'dev' of http://115.159.85.185:8098/r/SuZhouGuanHong/ShanMeiXinNengYuan into dev

---
 Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs |  585 +++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 373 insertions(+), 212 deletions(-)

diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs
index 78a21e9..b80970b 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs
@@ -1,212 +1,373 @@
-using Microsoft.AspNetCore.Mvc;
-using SqlSugar;
-using WIDESEA_Core;
-using WIDESEA_Model.Models;
-
-namespace WIDESEA_WMSServer.Controllers.Dashboard
-{
-    /// <summary>
-    /// 浠〃鐩�
-    /// </summary>
-    [Route("api/Dashboard")]
-    [ApiController]
-    public class DashboardController : ControllerBase
-    {
-        private readonly ISqlSugarClient _db;
-
-        public DashboardController(ISqlSugarClient db)
-        {
-            _db = db;
-        }
-
-        /// <summary>
-        /// 鎬昏鏁版嵁
-        /// </summary>
-        [HttpGet("Overview")]
-        public async Task<WebResponseContent> Overview()
-        {
-            var today = DateTime.Today;
-            var firstDayOfMonth = new DateTime(today.Year, today.Month, 1);
-
-            // 浠婃棩鍏ュ簱鏁�
-            var todayInbound = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= today && t.TaskType >= 500 && t.TaskType < 600)
-                .CountAsync();
-
-            // 浠婃棩鍑哄簱鏁�
-            var todayOutbound = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= today && t.TaskType >= 100 && t.TaskType < 200)
-                .CountAsync();
-
-            // 鏈湀鍏ュ簱鏁�
-            var monthInbound = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 500 && t.TaskType < 600)
-                .CountAsync();
-
-            // 鏈湀鍑哄簱鏁�
-            var monthOutbound = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 100 && t.TaskType < 200)
-                .CountAsync();
-
-            // 褰撳墠鎬诲簱瀛�
-            var totalStock = await _db.Queryable<Dt_StockInfo>().CountAsync();
-
-            return WebResponseContent.Instance.OK(null, new
-            {
-                TodayInbound = todayInbound,
-                TodayOutbound = todayOutbound,
-                MonthInbound = monthInbound,
-                MonthOutbound = monthOutbound,
-                TotalStock = totalStock
-            });
-        }
-
-        /// <summary>
-        /// 姣忔棩缁熻
-        /// </summary>
-        [HttpGet("DailyStats")]
-        public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30)
-        {
-            if (days <= 0) days = 30;
-            if (days > 365) days = 365;
-
-            var startDate = DateTime.Today.AddDays(-days + 1);
-
-            var query = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= startDate)
-                .Select(t => new { t.InsertTime, t.TaskType })
-                .ToListAsync();
-
-            var result = query
-                .GroupBy(t => t.InsertTime.Date)
-                .Select(g => new
-                {
-                    Date = g.Key.ToString("yyyy-MM-dd"),
-                    Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600),
-                    Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
-                })
-                .OrderBy(x => x.Date)
-                .ToList();
-
-            return WebResponseContent.Instance.OK(null, result);
-        }
-
-        /// <summary>
-        /// 姣忓懆缁熻
-        /// </summary>
-        [HttpGet("WeeklyStats")]
-        public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12)
-        {
-            if (weeks <= 0) weeks = 12;
-
-            var startDate = DateTime.Today.AddDays(-weeks * 7);
-
-            var query = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= startDate)
-                .Select(t => new { t.InsertTime, t.TaskType })
-                .ToListAsync();
-
-            var result = query
-                .GroupBy(t => GetWeekKey(t.InsertTime))
-                .Select(g => new
-                {
-                    Week = g.Key,
-                    Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600),
-                    Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
-                })
-                .OrderBy(x => x.Week)
-                .ToList();
-
-            return WebResponseContent.Instance.OK(null, result);
-        }
-
-        private string GetWeekKey(DateTime date)
-        {
-            // 鑾峰彇鍛ㄤ竴寮�濮嬬殑鍛� (ISO 8601)
-            var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
-            var monday = date.AddDays(-diff);
-            var weekNum = System.Globalization.CultureInfo.InvariantCulture
-                .Calendar.GetWeekOfYear(monday, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
-            return $"{monday.Year}-W{weekNum:D2}";
-        }
-
-        /// <summary>
-        /// 姣忔湀缁熻
-        /// </summary>
-        [HttpGet("MonthlyStats")]
-        public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12)
-        {
-            if (months <= 0) months = 12;
-
-            var startDate = DateTime.Today.AddMonths(-months + 1);
-            startDate = new DateTime(startDate.Year, startDate.Month, 1);
-
-            var query = await _db.Queryable<Dt_Task_Hty>()
-                .Where(t => t.InsertTime >= startDate)
-                .Select(t => new { t.InsertTime, t.TaskType })
-                .ToListAsync();
-
-            var result = query
-                .GroupBy(t => new { t.InsertTime.Year, t.InsertTime.Month })
-                .Select(g => new
-                {
-                    Month = $"{g.Key.Year}-{g.Key.Month:D2}",
-                    Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600),
-                    Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
-                })
-                .OrderBy(x => x.Month)
-                .ToList();
-
-            return WebResponseContent.Instance.OK(null, result);
-        }
-
-        /// <summary>
-        /// 搴撳瓨搴撻緞鍒嗗竷
-        /// </summary>
-        [HttpGet("StockAgeDistribution")]
-        public async Task<WebResponseContent> StockAgeDistribution()
-        {
-            var now = DateTime.Now;
-
-            // 浣跨敤 SQL 鐩存帴鍒嗙粍缁熻锛岄伩鍏嶅姞杞芥墍鏈夋暟鎹埌鍐呭瓨
-            var result = new[]
-            {
-                new { Range = "7澶╁唴", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, now) <= 7).CountAsync() },
-                new { Range = "7-30澶�", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, now) > 7 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, now) <= 30).CountAsync() },
-                new { Range = "30-90澶�", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, now) > 30 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, now) <= 90).CountAsync() },
-                new { Range = "90澶╀互涓�", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, now) > 90).CountAsync() }
-            };
-
-            return WebResponseContent.Instance.OK(null, result);
-        }
-
-        /// <summary>
-        /// 鍚勪粨搴撳簱瀛樺垎甯�
-        /// </summary>
-        [HttpGet("StockByWarehouse")]
-        public async Task<WebResponseContent> StockByWarehouse()
-        {
-            // 鍏堟煡璇粨搴撳悕绉�
-            var warehouses = await _db.Queryable<Dt_Warehouse>()
-                .Select(w => new { w.WarehouseId, w.WarehouseName })
-                .ToListAsync();
-            var warehouseDict = warehouses.ToDictionary(w => w.WarehouseId, w => w.WarehouseName);
-
-            // 鏌ヨ搴撳瓨鏁版嵁骞跺湪鍐呭瓨涓垎缁�
-            var stocks = await _db.Queryable<Dt_StockInfo>()
-                .Select(s => new { s.WarehouseId })
-                .ToListAsync();
-
-            var result = stocks
-                .GroupBy(s => s.WarehouseId)
-                .Select(g => new
-                {
-                    Warehouse = warehouseDict.TryGetValue(g.Key, out var name) ? name : $"浠撳簱{g.Key}",
-                    Count = g.Count()
-                })
-                .ToList();
-
-            return WebResponseContent.Instance.OK(null, result);
-        }
-    }
-}
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using WIDESEA_Common.LocationEnum;
+using WIDESEA_Core;
+using WIDESEA_Model.Models;
+
+namespace WIDESEA_WMSServer.Controllers.Dashboard
+{
+    /// <summary>
+    /// 浠〃鐩�
+    /// </summary>
+    [Route("api/Dashboard")]
+    [ApiController]
+    public class DashboardController : ControllerBase
+    {
+        private readonly ISqlSugarClient _db;
+
+        public DashboardController(ISqlSugarClient db)
+        {
+            _db = db;
+        }
+
+        /// <summary>
+        /// 鎬昏鏁版嵁
+        /// </summary>
+        [HttpGet("Overview"), AllowAnonymous]
+        public async Task<WebResponseContent> Overview()
+        {
+            try
+            {
+                var today = DateTime.Today;
+                var firstDayOfMonth = new DateTime(today.Year, today.Month, 1);
+
+                // 浠婃棩鍏ュ簱鏁�
+                var todayInbound = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= today && t.TaskType >= 500 && t.TaskType < 600)
+                    .CountAsync();
+
+                // 浠婃棩鍑哄簱鏁�
+                var todayOutbound = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= today && t.TaskType >= 100 && t.TaskType < 200)
+                    .CountAsync();
+
+                // 鏈湀鍏ュ簱鏁�
+                var monthInbound = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 500 && t.TaskType < 600)
+                    .CountAsync();
+
+                // 鏈湀鍑哄簱鏁�
+                var monthOutbound = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 100 && t.TaskType < 200)
+                    .CountAsync();
+
+                // 褰撳墠鎬诲簱瀛�
+                var totalStock = await _db.Queryable<Dt_StockInfo>().CountAsync();
+
+                return WebResponseContent.Instance.OK(null, new
+                {
+                    TodayInbound = todayInbound,
+                    TodayOutbound = todayOutbound,
+                    MonthInbound = monthInbound,
+                    MonthOutbound = monthOutbound,
+                    TotalStock = totalStock
+                });
+            }
+            catch (Exception ex)
+            {
+                return WebResponseContent.Instance.Error($"鎬昏鏁版嵁鑾峰彇澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 姣忔棩缁熻
+        /// </summary>
+        [HttpGet("DailyStats"), AllowAnonymous]
+        public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30)
+        {
+            try
+            {
+                if (days <= 0) days = 30;
+                if (days > 365) days = 365;
+
+                var startDate = DateTime.Today.AddDays(-days + 1);
+                var endDate = DateTime.Today; // 鍖呭惈浠婂ぉ
+
+                var query = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= startDate && t.InsertTime <= endDate)
+                    .Select(t => new { t.InsertTime, t.TaskType })
+                    .ToListAsync();
+
+                // 鐢熸垚鏃ユ湡鑼冨洿
+                var allDates = new List<DateTime>();
+                for (var date = startDate; date <= endDate; date = date.AddDays(1))
+                {
+                    allDates.Add(date);
+                }
+
+                // 鎸夋棩鏈熷垎缁勭粺璁�
+                var groupedData = query
+                    .GroupBy(t => t.InsertTime.Date)
+                    .Select(g => new
+                    {
+                        Date = g.Key,
+                        Inbound = g.Count(t => t.TaskType >= 200 && t.TaskType < 300),
+                        Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
+                    })
+                    .ToDictionary(x => x.Date, x => x);
+
+                // 琛ュ叏缂哄け鏃ユ湡
+                var result = allDates.Select(date =>
+                {
+                    if (groupedData.TryGetValue(date, out var data))
+                    {
+                        return new
+                        {
+                            Date = date.ToString("MM-dd"),
+                            Inbound = data.Inbound,
+                            Outbound = data.Outbound
+                        };
+                    }
+                    else
+                    {
+                        return new
+                        {
+                            Date = date.ToString("MM-dd"),
+                            Inbound = 0,
+                            Outbound = 0
+                        };
+                    }
+                })
+                .OrderBy(x => x.Date)
+                .ToList();
+
+                return WebResponseContent.Instance.OK(null, result);
+            }
+            catch (Exception ex)
+            {
+                return WebResponseContent.Instance.Error($"姣忔棩缁熻鑾峰彇澶辫触: {ex.Message}");
+            }
+        }
+        /// <summary>
+        /// 姣忓懆缁熻
+        /// </summary>
+        /// <remarks>
+        /// 娉ㄦ剰锛氭暟鎹湪 SQL 灞傝繃婊ゅ悗锛屽湪搴旂敤灞傛寜 ISO 8601 鍛ㄩ敭鍒嗙粍銆�
+        /// 鍛ㄩ敭涓� "YYYY-Www" 鏍煎紡锛屾棤娉曠洿鎺ュ湪 SQL 灞傜敤 GROUP BY 瀹炵幇銆�
+        /// </remarks>
+        [HttpGet("WeeklyStats"), AllowAnonymous]
+        public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12)
+        {
+            try
+            {
+                if (weeks <= 0) weeks = 12;
+
+                var startDate = DateTime.Today.AddDays(-weeks * 7);
+
+                var query = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= startDate)
+                    .Select(t => new { t.InsertTime, t.TaskType })
+                    .ToListAsync();
+
+                var result = query
+                    .GroupBy(t => GetWeekKey(t.InsertTime))
+                    .Select(g => new
+                    {
+                        Week = g.Key,
+                        Inbound = g.Count(t => t.TaskType >= 200 && t.TaskType < 300),
+                        Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
+                    })
+                    .OrderBy(x => x.Week)
+                    .ToList();
+
+                return WebResponseContent.Instance.OK(null, result);
+            }
+            catch (Exception ex)
+            {
+                return WebResponseContent.Instance.Error($"姣忓懆缁熻鑾峰彇澶辫触: {ex.Message}");
+            }
+        }
+
+        private string GetWeekKey(DateTime date)
+        {
+            // 鑾峰彇鍛ㄤ竴寮�濮嬬殑鍛� (ISO 8601)
+            var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
+            var monday = date.AddDays(-diff);
+            var weekNum = System.Globalization.CultureInfo.InvariantCulture
+                .Calendar.GetWeekOfYear(monday, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
+            return $"{monday.Year}-W{weekNum:D2}";
+        }
+
+        /// <summary>
+        /// 姣忔湀缁熻
+        /// </summary>
+        /// <remarks>
+        /// 鎸夊勾鏈堢粺璁″叆绔欏拰鍑虹珯浠诲姟鏁伴噺
+        /// </remarks>
+        [HttpGet("MonthlyStats"), AllowAnonymous]
+        public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12)
+        {
+            try
+            {
+                if (months <= 0) months = 12;
+
+                var startDate = DateTime.Today.AddMonths(-months + 1);
+                startDate = new DateTime(startDate.Year, startDate.Month, 1);
+
+                var monthlyStats = await _db.Queryable<Dt_Task_Hty>()
+                    .Where(t => t.InsertTime >= startDate)
+                    .GroupBy(t => new { t.InsertTime.Year, t.InsertTime.Month })
+                    .Select(t => new
+                    {
+                        Year = t.InsertTime.Year,
+                        Month = t.InsertTime.Month,
+                        Inbound = SqlFunc.AggregateSum(
+                            SqlFunc.IIF(t.TaskType >= 200 && t.TaskType < 300, 1, 0)
+                        ),
+                        Outbound = SqlFunc.AggregateSum(
+                            SqlFunc.IIF(t.TaskType >= 100 && t.TaskType < 200, 1, 0)
+                        )
+                    })
+                    .OrderBy(t => t.Year)
+                    .OrderBy(t => t.Month)
+                    .ToListAsync();
+
+                // 鐢熸垚鎵�鏈夐渶瑕佺粺璁$殑鏈堜唤鍒楄〃
+                var allMonths = new List<DateTime>();
+                var currentMonth = startDate;
+                var endMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
+
+                while (currentMonth <= endMonth)
+                {
+                    allMonths.Add(currentMonth);
+                    currentMonth = currentMonth.AddMonths(1);
+                }
+
+                // 灏嗘煡璇㈢粨鏋滆浆鎹负瀛楀吀锛屾柟渚挎煡鎵�
+                var statsDict = monthlyStats.ToDictionary(
+                    s => $"{s.Year}-{s.Month:D2}",
+                    s => new { s.Inbound, s.Outbound }
+                );
+
+                // 鏋勫缓瀹屾暣鐨勭粨鏋滃垪琛紝鍖呭惈鎵�鏈夋湀浠�
+                var result = new List<object>();
+                foreach (var month in allMonths)
+                {
+                    var monthKey = $"{month.Year}-{month.Month:D2}";
+
+                    if (statsDict.TryGetValue(monthKey, out var stat))
+                    {
+                        result.Add(new
+                        {
+                            Month = monthKey,
+                            Inbound = stat.Inbound,
+                            Outbound = stat.Outbound
+                        });
+                    }
+                    else
+                    {
+                        result.Add(new
+                        {
+                            Month = monthKey,
+                            Inbound = 0,
+                            Outbound = 0
+                        });
+                    }
+                }
+
+                return WebResponseContent.Instance.OK(null, result);
+            }
+            catch (Exception ex)
+            {
+                // 璁板綍寮傚父鏃ュ織锛堝疄闄呴」鐩腑寤鸿浣跨敤鏃ュ織妗嗘灦锛�
+                // _logger.LogError(ex, "姣忔湀缁熻鑾峰彇澶辫触");
+
+                return WebResponseContent.Instance.Error($"姣忔湀缁熻鑾峰彇澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 搴撳瓨搴撻緞鍒嗗竷
+        /// </summary>
+        [HttpGet("StockAgeDistribution"), AllowAnonymous]
+        public async Task<WebResponseContent> StockAgeDistribution()
+        {
+            try
+            {
+                var today = DateTime.Today;
+
+                // 浣跨敤 SQL 鐩存帴鍒嗙粍缁熻锛岄伩鍏嶅姞杞芥墍鏈夋暟鎹埌鍐呭瓨
+                var result = new[]
+                {
+                    new { Range = "7澶╁唴", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 7).CountAsync() },
+                    new { Range = "7-30澶�", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 7 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 30).CountAsync() },
+                    new { Range = "30-90澶�", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 30 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 90).CountAsync() },
+                    new { Range = "90澶╀互涓�", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 90).CountAsync() }
+                };
+
+                return WebResponseContent.Instance.OK(null, result);
+            }
+            catch (Exception ex)
+            {
+                return WebResponseContent.Instance.Error($"搴撳瓨搴撻緞鍒嗗竷鑾峰彇澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 鍚勪粨搴撳簱瀛樺垎甯�
+        /// </summary>
+        /// <remarks>
+        /// 浣跨敤 SQL GROUP BY 鍦ㄦ暟鎹簱灞傞潰鑱氬悎锛岄伩鍏嶅姞杞藉叏閮ㄥ簱瀛樿褰曞埌鍐呭瓨銆�
+        /// </remarks>
+        [HttpGet("StockByWarehouse"), AllowAnonymous]
+        public async Task<WebResponseContent> StockByWarehouse()
+        {
+            try
+            {
+                // 鏌ヨ鎵�鏈変粨搴撲俊鎭�
+                var warehouses = await _db.Queryable<Dt_Warehouse>()
+                    .Select(w => new { w.WarehouseId, w.WarehouseName })
+                    .ToListAsync();
+
+                // 鏌ヨ鎵�鏈夎揣浣嶄俊鎭紝鎸変粨搴撳垎缁勭粺璁℃�绘暟
+                var locationGroups = await _db.Queryable<Dt_LocationInfo>()
+                    .GroupBy(l => l.WarehouseId)
+                    .Select(l => new
+                    {
+                        WarehouseId = l.WarehouseId,
+                        TotalLocations = SqlFunc.AggregateCount(l.Id)
+                    })
+                    .ToListAsync();
+
+                // 鏌ヨ鐘舵�佷笉涓篎ree鐨勮揣浣嶄俊鎭紙鏈夎揣璐т綅锛夛紝鎸変粨搴撳垎缁勭粺璁�
+                var occupiedLocationGroups = await _db.Queryable<Dt_LocationInfo>()
+                    .Where(l => l.LocationStatus != (int)LocationStatusEnum.Free)
+                    .GroupBy(l => l.WarehouseId)
+                    .Select(l => new
+                    {
+                        WarehouseId = l.WarehouseId,
+                        OccupiedLocations = SqlFunc.AggregateCount(l.Id)
+                    })
+                    .ToListAsync();
+
+                // 灏嗕粨搴撲俊鎭笌璐т綅缁熻淇℃伅鍚堝苟
+                var result = warehouses.Select(w =>
+                {
+                    var totalLocations = locationGroups.FirstOrDefault(lg => lg.WarehouseId == w.WarehouseId)?.TotalLocations ?? 0;
+                    var occupiedLocations = occupiedLocationGroups.FirstOrDefault(og => og.WarehouseId == w.WarehouseId)?.OccupiedLocations ?? 0;
+                    var emptyLocations = totalLocations - occupiedLocations;
+
+                    var occupiedPercentage = totalLocations > 0 ? Math.Round((double)occupiedLocations / totalLocations * 100, 2) : 0.0;
+                    var emptyPercentage = totalLocations > 0 ? Math.Round((double)emptyLocations / totalLocations * 100, 2) : 0.0;
+
+                    return new
+                    {
+                        Warehouse = w.WarehouseName,
+                        Total = totalLocations,
+                        HasStock = occupiedLocations,
+                        NoStock = emptyLocations,
+                        HasStockPercentage = $"{occupiedPercentage}%",
+                        NoStockPercentage = $"{emptyPercentage}%"
+                    };
+                }).ToList();
+
+                return WebResponseContent.Instance.OK(null, result);
+            }
+            catch (Exception ex)
+            {
+                return WebResponseContent.Instance.Error($"鍚勪粨搴撳簱瀛樺垎甯冭幏鍙栧け璐�: {ex.Message}");
+            }
+        }
+    }
+}

--
Gitblit v1.9.3