refactor(analytics): extract helper methods and add programmedBy filter
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
if (cutOnly)
|
||||
nestsQuery = nestsQuery.Where(n => CutStatuses.Contains(n.Status));
|
||||
|
||||
if (groupBy?.ToLower() == "month")
|
||||
{
|
||||
var nestData = await nestsQuery
|
||||
.Select(n => new
|
||||
{
|
||||
.Select(n => new NestMaterialDataWithDate(
|
||||
n.NestName,
|
||||
n.CopyID,
|
||||
n.Material,
|
||||
n.MatGrade,
|
||||
n.MatThick,
|
||||
n.PlateCount,
|
||||
Year = n.DateProgrammed!.Value.Year,
|
||||
Month = n.DateProgrammed!.Value.Month
|
||||
})
|
||||
n.DateProgrammed!.Value.Year,
|
||||
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 platesByNest = await GetPlateWeightAndAreaByNest();
|
||||
|
||||
if (groupBy?.ToLower() == "month")
|
||||
{
|
||||
var grouped = nestData
|
||||
.GroupBy(n => (n.Year, n.Month))
|
||||
.OrderBy(g => g.Key.Year).ThenBy(g => g.Key.Month)
|
||||
@@ -76,66 +55,15 @@ 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();
|
||||
|
||||
var summary = BuildMaterialSummaries(nestData.Select(x => x.ToBase()), platesByNest);
|
||||
return Ok(summary);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get most commonly used plate sizes, optionally filtered by material.
|
||||
@@ -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 =>
|
||||
{
|
||||
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 = 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,
|
||||
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<ActionResult<MaterialsToOrderResponse>> 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<NestHeader> 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<HashSet<(string?, int)>> GetNestKeySet(IQueryable<NestHeader> 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<Dictionary<(string?, int), double>> 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<Dictionary<(string?, int), (double Weight, double Area)>> 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<MaterialUsageSummary> BuildMaterialSummaries(
|
||||
IEnumerable<NestMaterialData> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse plate size string (e.g., "48 X 120") into width and length.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user