mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
feat(dashboard): add regional work order metrics (SH-348)
This commit is contained in:
parent
0194748e3b
commit
d4afcced87
8 changed files with 174 additions and 2 deletions
|
|
@ -301,4 +301,27 @@ public class DashboardServiceTests
|
|||
result.Items[0].AverageResolutionDays.Should().Be(3m);
|
||||
result.Items[0].Color.Should().Be("#123456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRegionsAsync_UsesCanonicalBucketsAndUnmappedOther()
|
||||
{
|
||||
await using var ctx = NewContext();
|
||||
ctx.Locations.AddRange(
|
||||
new Locations { Id = 1, State = "NY" },
|
||||
new Locations { Id = 2, State = "ca" },
|
||||
new Locations { Id = 3, State = "XX" });
|
||||
ctx.workOrders.AddRange(
|
||||
new WorkOrder { AccountId = 1, LocationId = 1 },
|
||||
new WorkOrder { AccountId = 1, LocationId = 2 },
|
||||
new WorkOrder { AccountId = 1, LocationId = 3 },
|
||||
new WorkOrder { AccountId = 1, LocationId = null });
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var result = await NewService(ctx).GetRegionsAsync(
|
||||
AccountUser(1), new DashboardStatsQueryDTO(), CancellationToken.None);
|
||||
|
||||
result.Items.Select(row => row.Region).Should().Equal(
|
||||
"East", "Central", "West", "California", "Unmapped/Other");
|
||||
result.Items.Select(row => row.WorkOrderCount).Should().Equal(1, 0, 0, 1, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,5 +89,25 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("Regions")]
|
||||
public async Task<IActionResult> GetRegions(
|
||||
[FromQuery] DashboardStatsQueryDTO query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dashboardService.GetRegionsAsync(User, query, cancellationToken));
|
||||
}
|
||||
catch (SeaHaven.Services.Exceptions.WorkOrderBoardValidationException ex)
|
||||
when (ex.Code == "Forbidden")
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new
|
||||
{
|
||||
code = ex.Code,
|
||||
message = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ namespace SeaHaven.DataServices.Implementation
|
|||
w.LegacyStatus ?? w.Status,
|
||||
w.ScheduledDate,
|
||||
w.OriginalDate,
|
||||
w.CompletedDate))
|
||||
w.CompletedDate,
|
||||
w.Locations == null ? null : w.Locations.State))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
string? LegacyStatus,
|
||||
DateTime? ScheduledDate,
|
||||
DateOnly? OriginalDate,
|
||||
DateTime? CompletedDate);
|
||||
DateTime? CompletedDate,
|
||||
string? LocationState = null);
|
||||
|
||||
public sealed record DashboardDispatcherWorkload(
|
||||
string DispatcherId,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,17 @@ namespace SeaHaven.Services.DTOs
|
|||
public decimal? AverageResolutionDays { get; init; }
|
||||
}
|
||||
|
||||
public sealed class DashboardRegionResponseDTO
|
||||
{
|
||||
public IReadOnlyList<DashboardRegionRowDTO> Items { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed class DashboardRegionRowDTO
|
||||
{
|
||||
public string Region { get; init; } = string.Empty;
|
||||
public int WorkOrderCount { get; init; }
|
||||
}
|
||||
|
||||
public class DashboardStatsDTO
|
||||
{
|
||||
public int Total { get; set; }
|
||||
|
|
|
|||
84
SeaHaven.Services/Helpers/DashboardRegions.cs
Normal file
84
SeaHaven.Services/Helpers/DashboardRegions.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.Services.Helpers;
|
||||
|
||||
public static class DashboardRegions
|
||||
{
|
||||
public static readonly IReadOnlyList<string> Names =
|
||||
["East", "Central", "West", "California", "Unmapped/Other"];
|
||||
|
||||
public static string Resolve(string? state)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(state))
|
||||
return "Unmapped/Other";
|
||||
|
||||
return StateToRegion.TryGetValue(state.Trim().ToUpperInvariant(), out var region)
|
||||
? region
|
||||
: "Unmapped/Other";
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, int> Count(
|
||||
IReadOnlyList<DashboardWorkOrder> workOrders)
|
||||
{
|
||||
var counts = Names.ToDictionary(name => name, _ => 0, StringComparer.Ordinal);
|
||||
foreach (var workOrder in workOrders)
|
||||
counts[Resolve(workOrder.LocationState)]++;
|
||||
|
||||
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"
|
||||
};
|
||||
}
|
||||
|
|
@ -152,5 +152,32 @@ namespace SeaHaven.Services.Implementation
|
|||
TotalDispatchers = rows.Count
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<DashboardRegionResponseDTO> GetRegionsAsync(
|
||||
ClaimsPrincipal user,
|
||||
DashboardStatsQueryDTO query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var accountId = _accountResolver.ResolveAccountFilter(user);
|
||||
var dispatcherId = user.IsInRole("Dispatcher")
|
||||
? user.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
: null;
|
||||
if (user.IsInRole("Dispatcher") && string.IsNullOrWhiteSpace(dispatcherId))
|
||||
throw new WorkOrderBoardValidationException("Forbidden", "Dispatcher identity is required.");
|
||||
|
||||
var workOrders = await _dataService.GetDashboardWorkOrdersAsync(
|
||||
accountId, dispatcherId, query.DateFrom, query.DateTo, cancellationToken);
|
||||
var counts = DashboardRegions.Count(workOrders);
|
||||
return new DashboardRegionResponseDTO
|
||||
{
|
||||
Items = DashboardRegions.Names
|
||||
.Select(region => new DashboardRegionRowDTO
|
||||
{
|
||||
Region = region,
|
||||
WorkOrderCount = counts[region]
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,5 +21,10 @@ namespace SeaHaven.Services.Interfaces
|
|||
DashboardStatsQueryDTO query,
|
||||
int page,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<DashboardRegionResponseDTO> GetRegionsAsync(
|
||||
ClaimsPrincipal user,
|
||||
DashboardStatsQueryDTO query,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue