diff --git a/src/PowderCoating.Web/Controllers/InventoryController.cs b/src/PowderCoating.Web/Controllers/InventoryController.cs index a374faa..dbfadd4 100644 --- a/src/PowderCoating.Web/Controllers/InventoryController.cs +++ b/src/PowderCoating.Web/Controllers/InventoryController.cs @@ -56,15 +56,16 @@ public class InventoryController : Controller /// /// Displays the paginated inventory list with optional keyword search, category filter, - /// and a low-stock quick-filter. When lowStockOnly is active the default sort switches - /// to QuantityOnHand ascending so the most depleted items surface immediately. Stats - /// (total value, active count, low-stock count) are computed directly on the DbSet - /// using aggregate SQL to avoid loading all rows into memory. + /// color family filter, and a low-stock quick-filter. When lowStockOnly is active the + /// default sort switches to QuantityOnHand ascending so the most depleted items surface + /// immediately. Stats (total value, active count, low-stock count) are computed directly + /// on the DbSet using aggregate SQL to avoid loading all rows into memory. /// public async Task Index( string? searchTerm, string? category, string? location, + string? colorFamily, string? sortColumn, string sortDirection = "asc", bool lowStockOnly = false, @@ -88,64 +89,35 @@ public class InventoryController : Controller }; gridRequest.Validate(); - // Build filter — compose search, category, location, and low-stock predicates + var hasSearch = !string.IsNullOrWhiteSpace(searchTerm); + var hasCategory = !string.IsNullOrWhiteSpace(category); + var hasLocation = !string.IsNullOrWhiteSpace(location); + var hasColorFamily = !string.IsNullOrWhiteSpace(colorFamily); + + var search = searchTerm?.ToLower() ?? ""; + var cat = category ?? ""; + var loc = location ?? ""; + var colorFam = colorFamily ?? ""; + + // Single composable predicate — EF Core evaluates the captured booleans as constants + // so inactive conditions fold to true and are omitted from the generated SQL WHERE clause. System.Linq.Expressions.Expression>? filter = null; - - var hasSearch = !string.IsNullOrWhiteSpace(searchTerm); - var hasCategory = !string.IsNullOrWhiteSpace(category); - var hasLocation = !string.IsNullOrWhiteSpace(location); - - var search = searchTerm?.ToLower() ?? ""; - var cat = category ?? ""; - var loc = location ?? ""; - - if (lowStockOnly && hasSearch && hasLocation) - filter = i => i.IsActive && i.QuantityOnHand <= i.ReorderPoint - && (i.Location != null && i.Location.ToLower() == loc.ToLower()) - && (i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) - || (i.ColorName != null && i.ColorName.ToLower().Contains(search)) - || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search))); - else if (lowStockOnly && hasSearch) - filter = i => i.IsActive && i.QuantityOnHand <= i.ReorderPoint - && (i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) - || (i.ColorName != null && i.ColorName.ToLower().Contains(search)) - || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search))); - else if (lowStockOnly && hasLocation) - filter = i => i.IsActive && i.QuantityOnHand <= i.ReorderPoint - && (i.Location != null && i.Location.ToLower() == loc.ToLower()); - else if (lowStockOnly) - filter = i => i.IsActive && i.QuantityOnHand <= i.ReorderPoint; - else if (hasSearch && hasCategory && hasLocation) - filter = i => (i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) + if (hasSearch || hasCategory || hasLocation || hasColorFamily || lowStockOnly) + { + filter = i => + (!lowStockOnly || (i.IsActive && i.QuantityOnHand <= i.ReorderPoint)) && + (!hasSearch || (i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) || (i.Description != null && i.Description.ToLower().Contains(search)) || (i.ColorName != null && i.ColorName.ToLower().Contains(search)) - || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search))) - && i.Category.ToLower() == cat.ToLower() - && (i.Location != null && i.Location.ToLower() == loc.ToLower()); - else if (hasSearch && hasCategory) - filter = i => (i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) - || (i.Description != null && i.Description.ToLower().Contains(search)) - || (i.ColorName != null && i.ColorName.ToLower().Contains(search)) - || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search))) - && i.Category.ToLower() == cat.ToLower(); - else if (hasSearch && hasLocation) - filter = i => (i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) - || (i.Description != null && i.Description.ToLower().Contains(search)) - || (i.ColorName != null && i.ColorName.ToLower().Contains(search)) - || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search))) - && (i.Location != null && i.Location.ToLower() == loc.ToLower()); - else if (hasSearch) - filter = i => i.SKU.ToLower().Contains(search) || i.Name.ToLower().Contains(search) - || (i.Description != null && i.Description.ToLower().Contains(search)) - || (i.ColorName != null && i.ColorName.ToLower().Contains(search)) - || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search)); - else if (hasCategory && hasLocation) - filter = i => i.Category.ToLower() == cat.ToLower() - && (i.Location != null && i.Location.ToLower() == loc.ToLower()); - else if (hasCategory) - filter = i => i.Category.ToLower() == cat.ToLower(); - else if (hasLocation) - filter = i => i.Location != null && i.Location.ToLower() == loc.ToLower(); + || (i.Manufacturer != null && i.Manufacturer.ToLower().Contains(search)))) && + (!hasCategory || i.Category.ToLower() == cat.ToLower()) && + (!hasLocation || (i.Location != null && i.Location.ToLower() == loc.ToLower())) && + (!hasColorFamily || (i.ColorFamilies != null && ( + i.ColorFamilies == colorFam || + i.ColorFamilies.StartsWith(colorFam + ",") || + i.ColorFamilies.EndsWith("," + colorFam) || + i.ColorFamilies.Contains("," + colorFam + ",")))); + } // Build orderBy function Func, IOrderedQueryable> orderBy = gridRequest.SortColumn switch @@ -179,6 +151,14 @@ public class InventoryController : Controller var allItems = (await _unitOfWork.InventoryItems.FindAsync(i => i.CompanyId == companyId)).ToList(); ViewBag.Categories = allItems.Select(i => i.Category).Where(c => c != null).Distinct().OrderBy(c => c).ToList(); ViewBag.Locations = allItems.Select(i => i.Location).Where(l => !string.IsNullOrWhiteSpace(l)).Distinct().OrderBy(l => l).ToList(); + ViewBag.ColorFamilies = allItems + .Where(i => !string.IsNullOrEmpty(i.ColorFamilies)) + .SelectMany(i => i.ColorFamilies!.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .Select(f => f.Trim()) + .Where(f => f.Length > 0) + .Distinct() + .OrderBy(f => f) + .ToList(); ViewBag.StatsLowStockCount = allItems.Count(i => i.IsActive && i.QuantityOnHand <= i.ReorderPoint); ViewBag.StatsActiveCount = allItems.Count(i => i.IsActive); ViewBag.StatsTotalValue = allItems.Sum(i => (decimal?)i.QuantityOnHand * i.UnitCost) ?? 0m; @@ -187,6 +167,7 @@ public class InventoryController : Controller ViewBag.SearchTerm = searchTerm; ViewBag.Category = category; ViewBag.Location = location; + ViewBag.ColorFamily = colorFamily; ViewBag.LowStockOnly = lowStockOnly; ViewBag.SortColumn = gridRequest.SortColumn; ViewBag.SortDirection = gridRequest.SortDirection; diff --git a/src/PowderCoating.Web/Views/Inventory/Index.cshtml b/src/PowderCoating.Web/Views/Inventory/Index.cshtml index bed3762..504bf62 100644 --- a/src/PowderCoating.Web/Views/Inventory/Index.cshtml +++ b/src/PowderCoating.Web/Views/Inventory/Index.cshtml @@ -124,8 +124,9 @@ @{ var lowStockOnly = (bool)(ViewBag.LowStockOnly ?? false); var activeLocation = ViewBag.Location as string; + var activeColorFamily = ViewBag.ColorFamily as string; } -@if (!string.IsNullOrEmpty(ViewBag.SearchTerm) || !string.IsNullOrEmpty(ViewBag.Category) || !string.IsNullOrEmpty(activeLocation) || lowStockOnly) +@if (!string.IsNullOrEmpty(ViewBag.SearchTerm) || !string.IsNullOrEmpty(ViewBag.Category) || !string.IsNullOrEmpty(activeLocation) || !string.IsNullOrEmpty(activeColorFamily) || lowStockOnly) {
@@ -149,6 +150,10 @@ { in bin "@activeLocation" } + @if (!string.IsNullOrEmpty(activeColorFamily)) + { + in color family "@activeColorFamily" + } }
@@ -182,6 +187,16 @@ } + @if (((IEnumerable)ViewBag.ColorFamilies).Any()) + { + + } @if (((IEnumerable)ViewBag.Locations).Any()) {