From 5c74cb2b4e05e7071e0dc1061a437c5f902c5b1e Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 16 Dec 2025 09:35:37 -0500 Subject: [PATCH] refactor(analytics): extract helper methods and add programmedBy filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract common patterns into reusable methods (GetDateRange, BuildNestQuery, GetPlateWeightByNest, BuildMaterialSummaries). Add programmedBy filter to materials-to-order endpoint. Introduce record types for cleaner data passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../Controllers/AnalyticsController.cs | 491 +++++++----------- 1 file changed, 196 insertions(+), 295 deletions(-) diff --git a/PepApi.Core/Controllers/AnalyticsController.cs b/PepApi.Core/Controllers/AnalyticsController.cs index 49ec842..dacc1bd 100644 --- a/PepApi.Core/Controllers/AnalyticsController.cs +++ b/PepApi.Core/Controllers/AnalyticsController.cs @@ -11,11 +11,8 @@ public class AnalyticsController : ControllerBase { private readonly PepDB _db; - // Status codes for "has been cut" - private static readonly int[] CutStatuses = [2, 5]; // "Has been cut", "Quote, accepted, has been cut" - - // Status codes for "to be cut" - private static readonly int[] ToBeCutStatuses = [0, 4]; // "To be cut", "Quote, accepted, to be cut" + private static readonly int[] CutStatuses = [2, 5]; + private static readonly int[] ToBeCutStatuses = [0, 4]; public AnalyticsController(PepDB db) { @@ -32,43 +29,25 @@ public class AnalyticsController : ControllerBase [FromQuery] string? groupBy = null, [FromQuery] bool cutOnly = true) { - var start = startDate ?? DateTime.Now.AddYears(-1); - var end = endDate ?? DateTime.Now; + var (start, end) = GetDateRange(startDate, endDate); + var nestsQuery = BuildNestQuery(start, end, cutOnly ? CutStatuses : null); - var nestsQuery = _db.NestHeaders - .Where(n => n.DateProgrammed != null - && n.DateProgrammed >= start - && n.DateProgrammed <= end); + var nestData = await nestsQuery + .Select(n => new NestMaterialDataWithDate( + n.NestName, + n.CopyID, + n.Material, + n.MatGrade, + n.MatThick, + n.PlateCount, + n.DateProgrammed!.Value.Year, + n.DateProgrammed!.Value.Month)) + .ToListAsync(); - if (cutOnly) - nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status)); + var platesByNest = await GetPlateWeightAndAreaByNest(); if (groupBy?.ToLower() == "month") { - var nestData = await nestsQuery - .Select(n => new - { - n.NestName, - n.CopyID, - n.Material, - n.MatGrade, - n.MatThick, - n.PlateCount, - Year = n.DateProgrammed!.Value.Year, - Month = n.DateProgrammed!.Value.Month - }) - .ToListAsync(); - - var plateData = await _db.PlateHeaders - .Select(p => new { p.NestName, p.CopyID, p.PlateWeight, p.TotalArea1 }) - .ToListAsync(); - - var platesByNest = plateData - .GroupBy(p => (p.NestName, p.CopyID)) - .ToDictionary( - g => g.Key, - g => (Weight: g.Sum(x => x.PlateWeight ?? 0), Area: g.Sum(x => x.TotalArea1 ?? 0))); - var grouped = nestData .GroupBy(n => (n.Year, n.Month)) .OrderBy(g => g.Key.Year).ThenBy(g => g.Key.Month) @@ -76,65 +55,14 @@ public class AnalyticsController : ControllerBase { Year = g.Key.Year, Month = g.Key.Month, - Materials = g - .GroupBy(n => (n.Material, n.MatGrade, n.MatThick)) - .Select(mg => new MaterialUsageSummary - { - MaterialNumber = int.TryParse(mg.Key.Material, out var num) ? num : 0, - MaterialGrade = mg.Key.MatGrade ?? "", - Thickness = mg.Key.MatThick, - NestCount = mg.Count(), - PlateCount = mg.Sum(x => x.PlateCount), - TotalWeight = mg.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Weight : 0), - TotalArea = mg.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Area : 0) - }) - .OrderByDescending(m => m.TotalWeight) - .ToList() + Materials = BuildMaterialSummaries(g.Select(x => x.ToBase()), platesByNest) }); return Ok(grouped); } - else - { - var nestData = await nestsQuery - .Select(n => new - { - n.NestName, - n.CopyID, - n.Material, - n.MatGrade, - n.MatThick, - n.PlateCount - }) - .ToListAsync(); - var plateData = await _db.PlateHeaders - .Select(p => new { p.NestName, p.CopyID, p.PlateWeight, p.TotalArea1 }) - .ToListAsync(); - - var platesByNest = plateData - .GroupBy(p => (p.NestName, p.CopyID)) - .ToDictionary( - g => g.Key, - g => (Weight: g.Sum(x => x.PlateWeight ?? 0), Area: g.Sum(x => x.TotalArea1 ?? 0))); - - var summary = nestData - .GroupBy(n => (n.Material, n.MatGrade, n.MatThick)) - .Select(g => new MaterialUsageSummary - { - MaterialNumber = int.TryParse(g.Key.Material, out var num) ? num : 0, - MaterialGrade = g.Key.MatGrade ?? "", - Thickness = g.Key.MatThick, - NestCount = g.Count(), - PlateCount = g.Sum(x => x.PlateCount), - TotalWeight = g.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Weight : 0), - TotalArea = g.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Area : 0) - }) - .OrderByDescending(m => m.TotalWeight) - .ToList(); - - return Ok(summary); - } + var summary = BuildMaterialSummaries(nestData.Select(x => x.ToBase()), platesByNest); + return Ok(summary); } /// @@ -148,16 +76,8 @@ public class AnalyticsController : ControllerBase [FromQuery] DateTime? endDate = null, [FromQuery] bool cutOnly = true) { - var start = startDate ?? DateTime.Now.AddYears(-1); - var end = endDate ?? DateTime.Now; - - var nestsQuery = _db.NestHeaders - .Where(n => n.DateProgrammed != null - && n.DateProgrammed >= start - && n.DateProgrammed <= end); - - if (cutOnly) - nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status)); + var (start, end) = GetDateRange(startDate, endDate); + var nestsQuery = BuildNestQuery(start, end, cutOnly ? CutStatuses : null); if (materialNumber.HasValue) nestsQuery = nestsQuery.Where(n => n.Material == materialNumber.Value.ToString()); @@ -165,13 +85,7 @@ public class AnalyticsController : ControllerBase if (!string.IsNullOrWhiteSpace(grade)) nestsQuery = nestsQuery.Where(n => n.MatGrade == grade); - var nestKeys = await nestsQuery - .Select(n => new { n.NestName, n.CopyID }) - .ToListAsync(); - - var nestKeySet = nestKeys - .Select(n => (n.NestName, n.CopyID)) - .ToHashSet(); + var nestKeySet = await GetNestKeySet(nestsQuery); var plateData = await _db.PlateHeaders .Where(p => !string.IsNullOrEmpty(p.PlateSize)) @@ -187,9 +101,7 @@ public class AnalyticsController : ControllerBase }) .ToListAsync(); - var filteredPlates = plateData - .Where(p => nestKeySet.Contains((p.NestName, p.CopyID))); - + var filteredPlates = plateData.Where(p => nestKeySet.Contains((p.NestName, p.CopyID))).ToList(); var totalWeight = filteredPlates.Sum(p => p.PlateWeight ?? 0); var plateSizes = filteredPlates @@ -222,26 +134,11 @@ public class AnalyticsController : ControllerBase [FromQuery] DateTime? endDate = null, [FromQuery] bool cutOnly = true) { - DateTime start, end; + var (start, end) = year.HasValue + ? (new DateTime(year.Value, 1, 1), new DateTime(year.Value, 12, 31, 23, 59, 59)) + : GetDateRange(startDate, endDate); - if (year.HasValue) - { - start = new DateTime(year.Value, 1, 1); - end = new DateTime(year.Value, 12, 31, 23, 59, 59); - } - else - { - start = startDate ?? DateTime.Now.AddYears(-1); - end = endDate ?? DateTime.Now; - } - - var nestsQuery = _db.NestHeaders - .Where(n => n.DateProgrammed != null - && n.DateProgrammed >= start - && n.DateProgrammed <= end); - - if (cutOnly) - nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status)); + var nestsQuery = BuildNestQuery(start, end, cutOnly ? CutStatuses : null); var nestData = await nestsQuery .Select(n => new @@ -254,28 +151,23 @@ public class AnalyticsController : ControllerBase }) .ToListAsync(); - var plateData = await _db.PlateHeaders - .Select(p => new { p.NestName, p.CopyID, p.PlateWeight }) - .ToListAsync(); - - var platesByNest = plateData - .GroupBy(p => (p.NestName, p.CopyID)) - .ToDictionary(g => g.Key, g => g.Sum(x => x.PlateWeight ?? 0)); - - var totalWeight = nestData.Sum(n => platesByNest.TryGetValue((n.NestName, n.CopyID), out var w) ? w : 0); + var platesByNest = await GetPlateWeightByNest(); + var totalWeight = nestData.Sum(n => platesByNest.GetValueOrDefault((n.NestName, n.CopyID))); var breakdown = nestData .GroupBy(n => n.MatThick) - .Select(g => new ThicknessBreakdown + .Select(g => { - Thickness = g.Key, - NestCount = g.Count(), - PlateCount = g.Sum(x => x.PlateCount), - TotalWeight = g.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var w) ? w : 0), - PercentageOfTotal = totalWeight > 0 - ? Math.Round(g.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var w) ? w : 0) / totalWeight * 100, 2) - : 0, - MaterialGrades = g.Select(x => x.MatGrade).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList()! + var groupWeight = g.Sum(x => platesByNest.GetValueOrDefault((x.NestName, x.CopyID))); + return new ThicknessBreakdown + { + Thickness = g.Key, + NestCount = g.Count(), + PlateCount = g.Sum(x => x.PlateCount), + TotalWeight = groupWeight, + PercentageOfTotal = totalWeight > 0 ? Math.Round(groupWeight / totalWeight * 100, 2) : 0, + MaterialGrades = g.Select(x => x.MatGrade).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList()! + }; }) .OrderByDescending(t => t.TotalWeight) .ToList(); @@ -293,38 +185,23 @@ public class AnalyticsController : ControllerBase [FromQuery] bool cutOnly = true) { var start = DateTime.Now.AddMonths(-months); - - var nestsQuery = _db.NestHeaders - .Where(n => n.DateProgrammed != null && n.DateProgrammed >= start); - - if (cutOnly) - nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status)); + var nestsQuery = BuildNestQuery(start, DateTime.Now, cutOnly ? CutStatuses : null); if (!string.IsNullOrWhiteSpace(customerId)) nestsQuery = nestsQuery.Where(n => n.CustomerName == customerId || n.CustID == customerId); var nestData = await nestsQuery - .Select(n => new - { + .Select(n => new NestMaterialDataWithCustomer( n.NestName, n.CopyID, - n.CustomerName, n.Material, n.MatGrade, n.MatThick, - n.PlateCount - }) + n.PlateCount, + n.CustomerName)) .ToListAsync(); - var plateData = await _db.PlateHeaders - .Select(p => new { p.NestName, p.CopyID, p.PlateWeight, p.TotalArea1 }) - .ToListAsync(); - - var platesByNest = plateData - .GroupBy(p => (p.NestName, p.CopyID)) - .ToDictionary( - g => g.Key, - g => (Weight: g.Sum(x => x.PlateWeight ?? 0), Area: g.Sum(x => x.TotalArea1 ?? 0))); + var platesByNest = await GetPlateWeightAndAreaByNest(); var customerUsage = nestData .GroupBy(n => n.CustomerName) @@ -332,21 +209,8 @@ public class AnalyticsController : ControllerBase { CustomerName = cg.Key ?? "Unknown", TotalNests = cg.Count(), - TotalWeight = cg.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Weight : 0), - Materials = cg - .GroupBy(n => (n.Material, n.MatGrade, n.MatThick)) - .Select(mg => new MaterialUsageSummary - { - MaterialNumber = int.TryParse(mg.Key.Material, out var num) ? num : 0, - MaterialGrade = mg.Key.MatGrade ?? "", - Thickness = mg.Key.MatThick, - NestCount = mg.Count(), - PlateCount = mg.Sum(x => x.PlateCount), - TotalWeight = mg.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Weight : 0), - TotalArea = mg.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Area : 0) - }) - .OrderByDescending(m => m.TotalWeight) - .ToList() + TotalWeight = cg.Sum(x => platesByNest.GetValueOrDefault((x.NestName, x.CopyID)).Weight), + Materials = BuildMaterialSummaries(cg.Select(x => x.ToBase()), platesByNest) }) .OrderByDescending(c => c.TotalWeight) .ToList(); @@ -366,27 +230,8 @@ public class AnalyticsController : ControllerBase [FromQuery] DateTime? startDate = null, [FromQuery] DateTime? endDate = null) { - DateTime start, end; - int monthsAnalyzed; - - if (startDate.HasValue && endDate.HasValue) - { - start = startDate.Value; - end = endDate.Value; - monthsAnalyzed = (int)Math.Ceiling((end - start).TotalDays / 30.0); - } - else - { - start = DateTime.Now.AddMonths(-months); - end = DateTime.Now; - monthsAnalyzed = months; - } - - var nestsQuery = _db.NestHeaders - .Where(n => n.DateProgrammed != null && n.DateProgrammed >= start && n.DateProgrammed <= end); - - if (cutOnly) - nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status)); + var (start, end, monthsAnalyzed) = GetDateRangeWithMonths(startDate, endDate, months); + var nestsQuery = BuildNestQuery(start, end, cutOnly ? CutStatuses : null); if (!string.IsNullOrWhiteSpace(customerId)) nestsQuery = nestsQuery.Where(n => n.CustomerName == customerId || n.CustID == customerId); @@ -398,34 +243,26 @@ public class AnalyticsController : ControllerBase n.CopyID, n.Material, n.MatGrade, - n.MatThick, - n.PlateCount + n.MatThick }) .ToListAsync(); var nestKeys = nestData.Select(n => (n.NestName, n.CopyID)).ToHashSet(); + var nestMaterialLookup = nestData.ToDictionary( + n => (n.NestName, n.CopyID), + n => (n.Material, n.MatGrade, n.MatThick)); var plateData = await _db.PlateHeaders .Select(p => new { p.NestName, p.CopyID, p.PlateSize, p.PlateWeight }) .ToListAsync(); - // Filter plates to only those belonging to our filtered nests - var filteredPlates = plateData + var platesByMaterial = plateData .Where(p => nestKeys.Contains((p.NestName, p.CopyID))) - .ToList(); - - // Create lookup: nest -> material info - var nestMaterialLookup = nestData.ToDictionary( - n => (n.NestName, n.CopyID), - n => (n.Material, n.MatGrade, n.MatThick)); - - // Group plates by material/grade/thickness, then by plate size to get actual counts - var platesByMaterial = filteredPlates .Select(p => new { p.PlateSize, p.PlateWeight, - Material = nestMaterialLookup.TryGetValue((p.NestName, p.CopyID), out var m) ? m : default + Material = nestMaterialLookup.GetValueOrDefault((p.NestName, p.CopyID)) }) .Where(p => p.Material != default) .GroupBy(p => p.Material) @@ -434,8 +271,7 @@ public class AnalyticsController : ControllerBase g => ( TotalWeight: g.Sum(x => x.PlateWeight ?? 0), TotalCount: g.Count(), - TopSize: g - .Where(x => !string.IsNullOrEmpty(x.PlateSize)) + TopSize: g.Where(x => !string.IsNullOrEmpty(x.PlateSize)) .GroupBy(x => x.PlateSize) .OrderByDescending(sg => sg.Count()) .Select(sg => sg.Key) @@ -447,7 +283,6 @@ public class AnalyticsController : ControllerBase { var (material, matGrade, matThick) = kvp.Key; var (totalWeight, totalPlates, topPlateSize) = kvp.Value; - var avgMonthlyWeight = totalWeight / monthsAnalyzed; var avgMonthlyPlates = (double)totalPlates / monthsAnalyzed; @@ -481,48 +316,20 @@ public class AnalyticsController : ControllerBase [FromQuery] bool cutOnly = true) { var start = DateTime.Now.AddMonths(-months); - - var nestsQuery = _db.NestHeaders - .Where(n => n.DateProgrammed != null && n.DateProgrammed >= start); - - if (cutOnly) - nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status)); + var nestsQuery = BuildNestQuery(start, DateTime.Now, cutOnly ? CutStatuses : null); var nestData = await nestsQuery - .Select(n => new - { + .Select(n => new NestMaterialData( n.NestName, n.CopyID, n.Material, n.MatGrade, n.MatThick, - n.PlateCount - }) + n.PlateCount)) .ToListAsync(); - var plateData = await _db.PlateHeaders - .Select(p => new { p.NestName, p.CopyID, p.PlateWeight, p.TotalArea1 }) - .ToListAsync(); - - var platesByNest = plateData - .GroupBy(p => (p.NestName, p.CopyID)) - .ToDictionary( - g => g.Key, - g => (Weight: g.Sum(x => x.PlateWeight ?? 0), Area: g.Sum(x => x.TotalArea1 ?? 0))); - - var topMaterials = nestData - .GroupBy(n => (n.Material, n.MatGrade, n.MatThick)) - .Select(g => new MaterialUsageSummary - { - MaterialNumber = int.TryParse(g.Key.Material, out var num) ? num : 0, - MaterialGrade = g.Key.MatGrade ?? "", - Thickness = g.Key.MatThick, - NestCount = g.Count(), - PlateCount = g.Sum(x => x.PlateCount), - TotalWeight = g.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Weight : 0), - TotalArea = g.Sum(x => platesByNest.TryGetValue((x.NestName, x.CopyID), out var v) ? v.Area : 0) - }) - .OrderByDescending(m => m.TotalWeight) + var platesByNest = await GetPlateWeightAndAreaByNest(); + var topMaterials = BuildMaterialSummaries(nestData, platesByNest) .Take(count) .ToList(); @@ -535,7 +342,8 @@ public class AnalyticsController : ControllerBase [HttpGet("materials-to-order")] public async Task> GetMaterialsToOrder( [FromQuery] string? customerId = null, - [FromQuery] int? year = null) + [FromQuery] int? year = null, + [FromQuery] string? programmedBy = null) { var targetYear = year ?? DateTime.Now.Year; @@ -546,6 +354,9 @@ public class AnalyticsController : ControllerBase if (!string.IsNullOrWhiteSpace(customerId)) nestsQuery = nestsQuery.Where(n => n.CustomerName == customerId || n.CustID == customerId); + if (!string.IsNullOrWhiteSpace(programmedBy)) + nestsQuery = nestsQuery.Where(n => n.Programmer == programmedBy); + var nestData = await nestsQuery .Select(n => new { @@ -557,49 +368,27 @@ public class AnalyticsController : ControllerBase }) .ToListAsync(); - if (!nestData.Any()) - { - return Ok(new MaterialsToOrderResponse - { - TotalPrograms = 0, - TotalPlates = 0, - TotalWeight = 0, - Materials = [] - }); - } + if (nestData.Count == 0) + return Ok(new MaterialsToOrderResponse { TotalPrograms = 0, TotalPlates = 0, TotalWeight = 0, Materials = [] }); var nestKeys = nestData.Select(n => (n.NestName, n.CopyID)).ToHashSet(); - - var plateData = await _db.PlateHeaders - .Where(p => p.DupNo == 1) // Only count first duplicate entry to avoid double-counting - .Select(p => new - { - p.NestName, - p.CopyID, - p.PlateSize, - p.PlateWeight, - p.PlateDuplicates - }) - .ToListAsync(); - - // Filter plates to only those belonging to our filtered nests - var filteredPlates = plateData - .Where(p => nestKeys.Contains((p.NestName, p.CopyID))) - .ToList(); - - // Create lookup: nest -> material info var nestMaterialLookup = nestData.ToDictionary( n => (n.NestName, n.CopyID), n => (n.Material, n.MatGrade, n.MatThick, n.NestName)); - // Group plates by material/grade/thickness - var materialGroups = filteredPlates + var plateData = await _db.PlateHeaders + .Where(p => p.DupNo == 1) + .Select(p => new { p.NestName, p.CopyID, p.PlateSize, p.PlateWeight, p.PlateDuplicates }) + .ToListAsync(); + + var materialGroups = plateData + .Where(p => nestKeys.Contains((p.NestName, p.CopyID))) .Select(p => new { p.PlateSize, p.PlateWeight, Qty = p.PlateDuplicates ?? 1, - Material = nestMaterialLookup.TryGetValue((p.NestName, p.CopyID), out var m) ? m : default + Material = nestMaterialLookup.GetValueOrDefault((p.NestName, p.CopyID)) }) .Where(p => p.Material != default) .GroupBy(p => (p.Material.Material, p.Material.MatGrade, p.Material.MatThick)) @@ -628,26 +417,103 @@ public class AnalyticsController : ControllerBase .ThenBy(m => m.Thickness) .ToList(); - var response = new MaterialsToOrderResponse + return Ok(new MaterialsToOrderResponse { TotalPrograms = nestData.Select(n => n.NestName).Distinct().Count(), TotalPlates = materialGroups.Sum(m => m.TotalPlates), TotalWeight = materialGroups.Sum(m => m.TotalWeight), Materials = materialGroups - }; - - return Ok(response); + }); + } + + #region Helper Methods + + private static (DateTime Start, DateTime End) GetDateRange(DateTime? startDate, DateTime? endDate) + => (startDate ?? DateTime.Now.AddYears(-1), endDate ?? DateTime.Now); + + private static (DateTime Start, DateTime End, int Months) GetDateRangeWithMonths( + DateTime? startDate, DateTime? endDate, int defaultMonths) + { + if (startDate.HasValue && endDate.HasValue) + { + var months = (int)Math.Ceiling((endDate.Value - startDate.Value).TotalDays / 30.0); + return (startDate.Value, endDate.Value, months); + } + return (DateTime.Now.AddMonths(-defaultMonths), DateTime.Now, defaultMonths); + } + + private IQueryable BuildNestQuery(DateTime start, DateTime end, int[]? statusFilter) + { + var query = _db.NestHeaders + .Where(n => n.DateProgrammed != null && n.DateProgrammed >= start && n.DateProgrammed <= end); + + if (statusFilter != null) + query = query.Where(n => statusFilter.Contains(n.Status)); + + return query; + } + + private async Task> GetNestKeySet(IQueryable nestsQuery) + { + var keys = await nestsQuery.Select(n => new { n.NestName, n.CopyID }).ToListAsync(); + return keys.Select(n => ((string?)n.NestName, n.CopyID)).ToHashSet(); + } + + private async Task> GetPlateWeightByNest() + { + var plateData = await _db.PlateHeaders + .Select(p => new { p.NestName, p.CopyID, p.PlateWeight }) + .ToListAsync(); + + return plateData + .GroupBy(p => ((string?)p.NestName, p.CopyID)) + .ToDictionary(g => g.Key, g => g.Sum(x => x.PlateWeight ?? 0)); + } + + private async Task> GetPlateWeightAndAreaByNest() + { + var plateData = await _db.PlateHeaders + .Select(p => new { p.NestName, p.CopyID, p.PlateWeight, p.TotalArea1 }) + .ToListAsync(); + + return plateData + .GroupBy(p => ((string?)p.NestName, p.CopyID)) + .ToDictionary( + g => g.Key, + g => (Weight: g.Sum(x => x.PlateWeight ?? 0), Area: g.Sum(x => x.TotalArea1 ?? 0))); + } + + private static List BuildMaterialSummaries( + IEnumerable nestData, + Dictionary<(string?, int), (double Weight, double Area)> platesByNest) + { + return nestData + .GroupBy(n => (n.Material, n.MatGrade, n.MatThick)) + .Select(g => + { + var weight = g.Sum(x => platesByNest.GetValueOrDefault((x.NestName, x.CopyID)).Weight); + var area = g.Sum(x => platesByNest.GetValueOrDefault((x.NestName, x.CopyID)).Area); + + return new MaterialUsageSummary + { + MaterialNumber = int.TryParse(g.Key.Material, out var num) ? num : 0, + MaterialGrade = g.Key.MatGrade ?? "", + Thickness = g.Key.MatThick, + NestCount = g.Count(), + PlateCount = g.Sum(x => x.PlateCount), + TotalWeight = weight, + TotalArea = area + }; + }) + .OrderByDescending(m => m.TotalWeight) + .ToList(); } - /// - /// Parse plate size string (e.g., "48 X 120") into width and length. - /// private static (double Width, double Length) ParsePlateSize(string? plateSize) { if (string.IsNullOrWhiteSpace(plateSize)) return (0, 0); - // Split on "X" or "x" with optional surrounding spaces var parts = plateSize.Split(['x', 'X'], StringSplitOptions.TrimEntries); if (parts.Length != 2) return (0, 0); @@ -657,4 +523,39 @@ public class AnalyticsController : ControllerBase return (width, length); } + + #endregion + + private record NestMaterialData( + string? NestName, + int CopyID, + string? Material, + string? MatGrade, + double MatThick, + int PlateCount); + + private record NestMaterialDataWithDate( + string? NestName, + int CopyID, + string? Material, + string? MatGrade, + double MatThick, + int PlateCount, + int Year, + int Month) + { + public NestMaterialData ToBase() => new(NestName, CopyID, Material, MatGrade, MatThick, PlateCount); + } + + private record NestMaterialDataWithCustomer( + string? NestName, + int CopyID, + string? Material, + string? MatGrade, + double MatThick, + int PlateCount, + string? CustomerName) + { + public NestMaterialData ToBase() => new(NestName, CopyID, Material, MatGrade, MatThick, PlateCount); + } }