mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-04 14:52:10 +00:00
Merge pull request #146 from Sea-Haven-Industries/feat/ab/sh-348-region-filter
SH-348: Work Orders board region filter
This commit is contained in:
commit
cc9a5ade21
8 changed files with 170 additions and 60 deletions
|
|
@ -121,6 +121,40 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
return query.Where(w => w.SiteCode != null && normalized.Contains(w.SiteCode));
|
return query.Where(w => w.SiteCode != null && normalized.Contains(w.SiteCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IQueryable<WorkOrder> ApplyRegionFilter(
|
||||||
|
IQueryable<WorkOrder> query,
|
||||||
|
IReadOnlyList<string>? regions)
|
||||||
|
{
|
||||||
|
if (regions == null || regions.Count == 0)
|
||||||
|
return query;
|
||||||
|
|
||||||
|
var normalized = regions
|
||||||
|
.Where(region => !string.IsNullOrWhiteSpace(region))
|
||||||
|
.Select(region => region.Trim())
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
if (normalized.Count == 0)
|
||||||
|
return query;
|
||||||
|
|
||||||
|
var knownRegions = normalized
|
||||||
|
.Where(region => WorkOrderBoardRegions.Names.Contains(region) && region != "Unmapped/Other")
|
||||||
|
.SelectMany(WorkOrderBoardRegions.StorageValuesFor)
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
var includeUnmapped = normalized.Contains("Unmapped/Other", StringComparer.Ordinal);
|
||||||
|
var hasKnownRegion = knownRegions.Count > 0;
|
||||||
|
|
||||||
|
if (!hasKnownRegion && !includeUnmapped)
|
||||||
|
return query.Where(_ => false);
|
||||||
|
|
||||||
|
return query.Where(w =>
|
||||||
|
(hasKnownRegion && w.Locations != null && w.Locations.State != null && knownRegions.Contains(w.Locations.State))
|
||||||
|
|| (includeUnmapped && (w.Locations == null
|
||||||
|
|| w.Locations.State == null
|
||||||
|
|| w.Locations.State == ""
|
||||||
|
|| !WorkOrderBoardRegions.KnownStorageValues.Contains(w.Locations.State))));
|
||||||
|
}
|
||||||
|
|
||||||
public static IQueryable<WorkOrder> ApplyStatusFilter(
|
public static IQueryable<WorkOrder> ApplyStatusFilter(
|
||||||
IQueryable<WorkOrder> query,
|
IQueryable<WorkOrder> query,
|
||||||
IReadOnlyList<LifecycleStatus>? statuses)
|
IReadOnlyList<LifecycleStatus>? statuses)
|
||||||
|
|
|
||||||
82
SeaHaven.DataServices/Helpers/WorkOrderBoardRegions.cs
Normal file
82
SeaHaven.DataServices/Helpers/WorkOrderBoardRegions.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
namespace SeaHaven.DataServices.Helpers
|
||||||
|
{
|
||||||
|
public static class WorkOrderBoardRegions
|
||||||
|
{
|
||||||
|
public static readonly IReadOnlyList<string> Names =
|
||||||
|
["East", "Central", "West", "California", "Unmapped/Other"];
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, string[]> RegionStateCodes =
|
||||||
|
new Dictionary<string, string[]>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["East"] = ["CT", "DE", "GA", "ME", "MD", "MA", "NH", "NJ", "NY", "NC", "PA", "RI", "SC", "VT", "VA", "WV"],
|
||||||
|
["Central"] = ["AL", "AR", "FL", "IL", "IN", "IA", "KS", "KY", "LA", "MI", "MN", "MS", "MO", "NE", "ND", "OH", "OK", "SD", "TN", "WI"],
|
||||||
|
["West"] = ["AK", "AZ", "CO", "HI", "ID", "MT", "NV", "NM", "OR", "TX", "UT", "WA", "WY"],
|
||||||
|
["California"] = ["CA"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, string> StateNames =
|
||||||
|
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["AL"] = "ALABAMA",
|
||||||
|
["AK"] = "ALASKA",
|
||||||
|
["AZ"] = "ARIZONA",
|
||||||
|
["AR"] = "ARKANSAS",
|
||||||
|
["CA"] = "CALIFORNIA",
|
||||||
|
["CO"] = "COLORADO",
|
||||||
|
["CT"] = "CONNECTICUT",
|
||||||
|
["DE"] = "DELAWARE",
|
||||||
|
["FL"] = "FLORIDA",
|
||||||
|
["GA"] = "GEORGIA",
|
||||||
|
["HI"] = "HAWAII",
|
||||||
|
["ID"] = "IDAHO",
|
||||||
|
["IL"] = "ILLINOIS",
|
||||||
|
["IN"] = "INDIANA",
|
||||||
|
["IA"] = "IOWA",
|
||||||
|
["KS"] = "KANSAS",
|
||||||
|
["KY"] = "KENTUCKY",
|
||||||
|
["LA"] = "LOUISIANA",
|
||||||
|
["ME"] = "MAINE",
|
||||||
|
["MD"] = "MARYLAND",
|
||||||
|
["MA"] = "MASSACHUSETTS",
|
||||||
|
["MI"] = "MICHIGAN",
|
||||||
|
["MN"] = "MINNESOTA",
|
||||||
|
["MS"] = "MISSISSIPPI",
|
||||||
|
["MO"] = "MISSOURI",
|
||||||
|
["MT"] = "MONTANA",
|
||||||
|
["NE"] = "NEBRASKA",
|
||||||
|
["NV"] = "NEVADA",
|
||||||
|
["NH"] = "NEW HAMPSHIRE",
|
||||||
|
["NJ"] = "NEW JERSEY",
|
||||||
|
["NM"] = "NEW MEXICO",
|
||||||
|
["NY"] = "NEW YORK",
|
||||||
|
["NC"] = "NORTH CAROLINA",
|
||||||
|
["ND"] = "NORTH DAKOTA",
|
||||||
|
["OH"] = "OHIO",
|
||||||
|
["OK"] = "OKLAHOMA",
|
||||||
|
["OR"] = "OREGON",
|
||||||
|
["PA"] = "PENNSYLVANIA",
|
||||||
|
["RI"] = "RHODE ISLAND",
|
||||||
|
["SC"] = "SOUTH CAROLINA",
|
||||||
|
["SD"] = "SOUTH DAKOTA",
|
||||||
|
["TN"] = "TENNESSEE",
|
||||||
|
["TX"] = "TEXAS",
|
||||||
|
["UT"] = "UTAH",
|
||||||
|
["VT"] = "VERMONT",
|
||||||
|
["VA"] = "VIRGINIA",
|
||||||
|
["WA"] = "WASHINGTON",
|
||||||
|
["WV"] = "WEST VIRGINIA",
|
||||||
|
["WI"] = "WISCONSIN",
|
||||||
|
["WY"] = "WYOMING"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyCollection<string> StorageValuesFor(string region)
|
||||||
|
=> RegionStateCodes.TryGetValue(region, out var codes)
|
||||||
|
? codes.SelectMany(code => new[] { code, StateNames[code] }).ToArray()
|
||||||
|
: Array.Empty<string>();
|
||||||
|
|
||||||
|
public static IReadOnlySet<string> KnownStorageValues { get; } =
|
||||||
|
RegionStateCodes.Values
|
||||||
|
.SelectMany(codes => codes.SelectMany(code => new[] { code, StateNames[code] }))
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
else
|
else
|
||||||
baseQuery = WorkOrderBoardQueryFilters.ApplyDateRangeFilter(baseQuery, query.DateFrom, query.DateTo);
|
baseQuery = WorkOrderBoardQueryFilters.ApplyDateRangeFilter(baseQuery, query.DateFrom, query.DateTo);
|
||||||
baseQuery = WorkOrderBoardQueryFilters.ApplySiteFilter(baseQuery, query.Sites);
|
baseQuery = WorkOrderBoardQueryFilters.ApplySiteFilter(baseQuery, query.Sites);
|
||||||
|
baseQuery = WorkOrderBoardQueryFilters.ApplyRegionFilter(baseQuery, query.Regions);
|
||||||
baseQuery = WorkOrderBoardQueryFilters.ApplyTypeFilter(baseQuery, query.Types, query.Overdue);
|
baseQuery = WorkOrderBoardQueryFilters.ApplyTypeFilter(baseQuery, query.Types, query.Overdue);
|
||||||
baseQuery = WorkOrderBoardQueryFilters.ApplyDispatcherFilter(
|
baseQuery = WorkOrderBoardQueryFilters.ApplyDispatcherFilter(
|
||||||
baseQuery, query.Dispatchers, query.MyWorkOrders, query.CurrentUserId);
|
baseQuery, query.Dispatchers, query.MyWorkOrders, query.CurrentUserId);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
DateOnly DateTo,
|
DateOnly DateTo,
|
||||||
bool UnscheduledOnly,
|
bool UnscheduledOnly,
|
||||||
IReadOnlyList<string>? Sites,
|
IReadOnlyList<string>? Sites,
|
||||||
|
IReadOnlyList<string>? Regions,
|
||||||
IReadOnlyList<WorkOrderType>? Types,
|
IReadOnlyList<WorkOrderType>? Types,
|
||||||
bool Overdue,
|
bool Overdue,
|
||||||
IReadOnlyList<string>? Dispatchers,
|
IReadOnlyList<string>? Dispatchers,
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ namespace SeaHaven.Services.DTOs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UnscheduledOnly { get; set; }
|
public bool UnscheduledOnly { get; set; }
|
||||||
public List<string>? Sites { get; set; }
|
public List<string>? Sites { get; set; }
|
||||||
|
public List<string>? Regions { get; set; }
|
||||||
public List<WorkOrderType>? Types { get; set; }
|
public List<WorkOrderType>? Types { get; set; }
|
||||||
/// <summary>When true, include past-due (isPastDue) rows. Combined with Types via OR.</summary>
|
/// <summary>When true, include past-due (isPastDue) rows. Combined with Types via OR.</summary>
|
||||||
public bool Overdue { get; set; }
|
public bool Overdue { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
|
using SeaHaven.DataServices.Helpers;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
|
||||||
namespace SeaHaven.Services.Helpers;
|
namespace SeaHaven.Services.Helpers;
|
||||||
|
|
||||||
public static class DashboardRegions
|
public static class DashboardRegions
|
||||||
{
|
{
|
||||||
public static readonly IReadOnlyList<string> Names =
|
public static readonly IReadOnlyList<string> Names = WorkOrderBoardRegions.Names;
|
||||||
["East", "Central", "West", "California", "Unmapped/Other"];
|
|
||||||
|
|
||||||
public static string Resolve(string? state)
|
public static string Resolve(string? state)
|
||||||
{
|
{
|
||||||
|
|
@ -13,9 +13,13 @@ public static class DashboardRegions
|
||||||
if (code is null)
|
if (code is null)
|
||||||
return "Unmapped/Other";
|
return "Unmapped/Other";
|
||||||
|
|
||||||
return StateToRegion.TryGetValue(code, out var region)
|
foreach (var region in Names.Where(region => region != "Unmapped/Other"))
|
||||||
? region
|
{
|
||||||
: "Unmapped/Other";
|
if (WorkOrderBoardRegions.StorageValuesFor(region).Contains(code))
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unmapped/Other";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyDictionary<string, int> Count(
|
public static IReadOnlyDictionary<string, int> Count(
|
||||||
|
|
@ -27,59 +31,4 @@ public static class DashboardRegions
|
||||||
|
|
||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly IReadOnlyDictionary<string, string> StateToRegion =
|
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
|
||||||
{
|
|
||||||
["CT"] = "East",
|
|
||||||
["DE"] = "East",
|
|
||||||
["GA"] = "East",
|
|
||||||
["ME"] = "East",
|
|
||||||
["MD"] = "East",
|
|
||||||
["MA"] = "East",
|
|
||||||
["NH"] = "East",
|
|
||||||
["NJ"] = "East",
|
|
||||||
["NY"] = "East",
|
|
||||||
["NC"] = "East",
|
|
||||||
["PA"] = "East",
|
|
||||||
["RI"] = "East",
|
|
||||||
["SC"] = "East",
|
|
||||||
["VT"] = "East",
|
|
||||||
["VA"] = "East",
|
|
||||||
["WV"] = "East",
|
|
||||||
["AL"] = "Central",
|
|
||||||
["AR"] = "Central",
|
|
||||||
["FL"] = "Central",
|
|
||||||
["IL"] = "Central",
|
|
||||||
["IN"] = "Central",
|
|
||||||
["IA"] = "Central",
|
|
||||||
["KS"] = "Central",
|
|
||||||
["KY"] = "Central",
|
|
||||||
["LA"] = "Central",
|
|
||||||
["MI"] = "Central",
|
|
||||||
["MN"] = "Central",
|
|
||||||
["MS"] = "Central",
|
|
||||||
["MO"] = "Central",
|
|
||||||
["NE"] = "Central",
|
|
||||||
["ND"] = "Central",
|
|
||||||
["OH"] = "Central",
|
|
||||||
["OK"] = "Central",
|
|
||||||
["SD"] = "Central",
|
|
||||||
["TN"] = "Central",
|
|
||||||
["WI"] = "Central",
|
|
||||||
["AK"] = "West",
|
|
||||||
["AZ"] = "West",
|
|
||||||
["CO"] = "West",
|
|
||||||
["HI"] = "West",
|
|
||||||
["ID"] = "West",
|
|
||||||
["MT"] = "West",
|
|
||||||
["NV"] = "West",
|
|
||||||
["NM"] = "West",
|
|
||||||
["OR"] = "West",
|
|
||||||
["TX"] = "West",
|
|
||||||
["UT"] = "West",
|
|
||||||
["WA"] = "West",
|
|
||||||
["WY"] = "West",
|
|
||||||
["CA"] = "California"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
dateRange.To,
|
dateRange.To,
|
||||||
query.UnscheduledOnly,
|
query.UnscheduledOnly,
|
||||||
query.Sites,
|
query.Sites,
|
||||||
|
query.Regions,
|
||||||
query.Types,
|
query.Types,
|
||||||
query.Overdue,
|
query.Overdue,
|
||||||
query.Dispatchers,
|
query.Dispatchers,
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,47 @@ public class WorkOrderAdvancedSearchServiceTests
|
||||||
Assert.Equal("BK5", result.Items.First().SiteCode);
|
Assert.Equal("BK5", result.Items.First().SiteCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SearchAsync_FiltersByCanonicalRegionAndUnmappedBucket()
|
||||||
|
{
|
||||||
|
await using var context = CreateContext();
|
||||||
|
var date = new DateTime(2026, 6, 23);
|
||||||
|
context.Locations.AddRange(
|
||||||
|
new Locations { Id = 1, State = "WA" },
|
||||||
|
new Locations { Id = 2, State = "CALIFORNIA" },
|
||||||
|
new Locations { Id = 3, State = "Atlantis" },
|
||||||
|
new Locations { Id = 4, State = null });
|
||||||
|
context.workOrders.AddRange(
|
||||||
|
new WorkOrder { Id = 1, LocationId = 1, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
|
||||||
|
new WorkOrder { Id = 2, LocationId = 2, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
|
||||||
|
new WorkOrder { Id = 3, LocationId = 3, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled },
|
||||||
|
new WorkOrder { Id = 4, LocationId = 4, ScheduledDate = date, LifecycleStatus = LifecycleStatus.Scheduled });
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
||||||
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
|
||||||
|
dataService, WorkOrderAccountTestHelpers.Resolver(context));
|
||||||
|
var query = new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
||||||
|
{
|
||||||
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
||||||
|
DateFrom = new DateOnly(2026, 6, 22),
|
||||||
|
DateTo = new DateOnly(2026, 6, 30),
|
||||||
|
PageSize = 50
|
||||||
|
};
|
||||||
|
|
||||||
|
query.Regions = new List<string> { "West" };
|
||||||
|
var west = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||||
|
Assert.Equal(new[] { 1 }, west.Items.Select(item => item.Id));
|
||||||
|
|
||||||
|
query.Regions = new List<string> { "California" };
|
||||||
|
var california = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||||
|
Assert.Equal(new[] { 2 }, california.Items.Select(item => item.Id));
|
||||||
|
|
||||||
|
query.Regions = new List<string> { "Unmapped/Other" };
|
||||||
|
var unmapped = await service.SearchAsync(query, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
|
||||||
|
Assert.Equal(new[] { 3, 4 }, unmapped.Items.Select(item => item.Id));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SearchAsync_ClampsPageSizeToMax()
|
public async Task SearchAsync_ClampsPageSizeToMax()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue