From c2d0bd45e37b52df536eef2ff3144a9fdb04b9cc Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Thu, 9 Jul 2026 10:06:36 -0300 Subject: [PATCH] fix(work-orders): address PR #13 review feedback Return BadRequest for invalid board week window, remove duplicate unused board types, fix POC null guard, and document appt precedence and UTC Phase 1 assumptions. --- .../Controllers/WorkOrderController.cs | 6 ++++++ .../Board/WorkOrderBoardConstants.cs | 7 ------- .../DTOs/WorkOrderBoardQuery.cs | 10 ---------- .../DTOs/WorkOrderBoardRowData.cs | 20 ------------------- .../Exceptions/BoardQueryException.cs | 9 --------- .../Helpers/WorkOrderBoardProjection.cs | 2 +- .../Helpers/WorkOrderBoardQueryFilters.cs | 4 ++++ .../Interfaces/PrimaryDispatchProjection.cs | 9 --------- .../Board/WorkOrderBoardConstants.cs | 3 +++ .../Helpers/WorkOrderDerivedFields.cs | 8 ++++++++ .../Helpers/WorkOrderOperationalWeek.cs | 15 ++++++++++++++ .../Implementation/WorkOrderBoardService.cs | 16 +++++---------- 12 files changed, 42 insertions(+), 67 deletions(-) delete mode 100644 SeaHaven.DataServices/Board/WorkOrderBoardConstants.cs delete mode 100644 SeaHaven.DataServices/DTOs/WorkOrderBoardQuery.cs delete mode 100644 SeaHaven.DataServices/DTOs/WorkOrderBoardRowData.cs delete mode 100644 SeaHaven.DataServices/Exceptions/BoardQueryException.cs delete mode 100644 SeaHaven.DataServices/Interfaces/PrimaryDispatchProjection.cs diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs index 71976b7..1989d84 100644 --- a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs +++ b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs @@ -17,6 +17,7 @@ using System.Security.Claims; using static System.Runtime.InteropServices.JavaScript.JSType; using SeaHaven.Services.Interfaces; using SeaHaven.Services.DTOs; +using SeaHaven.Services.Helpers; using SeaHaven.DataServices.Interfaces; namespace Api.SeaHavenIndustries.Controllers @@ -89,6 +90,11 @@ namespace Api.SeaHavenIndustries.Controllers [FromQuery] List? types = null, [FromQuery] string? search = null) { + var resolvedWeekEnd = weekEnd ?? weekStart.AddDays(4); + var weekValidationError = WorkOrderOperationalWeek.ValidateWeekWindow(weekStart, resolvedWeekEnd); + if (weekValidationError != null) + return BadRequest(weekValidationError); + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var query = new WorkOrderBoardQueryDto { diff --git a/SeaHaven.DataServices/Board/WorkOrderBoardConstants.cs b/SeaHaven.DataServices/Board/WorkOrderBoardConstants.cs deleted file mode 100644 index bceff32..0000000 --- a/SeaHaven.DataServices/Board/WorkOrderBoardConstants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SeaHaven.DataServices.Board -{ - public static class WorkOrderBoardConstants - { - public const int ClientSideThreshold = 300; - } -} diff --git a/SeaHaven.DataServices/DTOs/WorkOrderBoardQuery.cs b/SeaHaven.DataServices/DTOs/WorkOrderBoardQuery.cs deleted file mode 100644 index 6d5d1e0..0000000 --- a/SeaHaven.DataServices/DTOs/WorkOrderBoardQuery.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SeaHaven.DataServices.DTOs -{ - public record WorkOrderBoardQuery( - DateOnly ScheduledFrom, - DateOnly ScheduledTo, - string? Search, - IReadOnlyList? AssigneeIds, - IReadOnlyList? DbStatuses, - int? LocationId); -} diff --git a/SeaHaven.DataServices/DTOs/WorkOrderBoardRowData.cs b/SeaHaven.DataServices/DTOs/WorkOrderBoardRowData.cs deleted file mode 100644 index a7eca60..0000000 --- a/SeaHaven.DataServices/DTOs/WorkOrderBoardRowData.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace SeaHaven.DataServices.DTOs -{ - public record WorkOrderBoardRowData( - int Id, - string? WoNumber, - string? SiteCode, - string? LocationLabel, - DateTime? ScheduledDate, - DateTime? ScheduledStart, - DateTime? DueDate, - string? WorkOrderType, - string? ServiceType, - string? AssigneeId, - string? AssigneeName, - string? VendorCompany, - string? VendorTechnician, - string? Status, - string? PocName, - string? PocPhone); -} diff --git a/SeaHaven.DataServices/Exceptions/BoardQueryException.cs b/SeaHaven.DataServices/Exceptions/BoardQueryException.cs deleted file mode 100644 index b659424..0000000 --- a/SeaHaven.DataServices/Exceptions/BoardQueryException.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SeaHaven.DataServices.Exceptions -{ - public class BoardQueryException : Exception - { - public BoardQueryException(string message) : base(message) - { - } - } -} diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs index 3b2ebac..75b98df 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs @@ -27,7 +27,7 @@ namespace SeaHaven.DataServices.Helpers Name = c.POC != null ? ((c.POC.FirstName ?? "") + " " + (c.POC.LastName ?? "")).Trim() : null, - c.POC!.PhoneNumber, + PhoneNumber = c.POC != null ? c.POC.PhoneNumber : null, c.Notes }) .FirstOrDefault(), diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs index 35465ca..c57fc5f 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs @@ -47,6 +47,8 @@ namespace SeaHaven.DataServices.Helpers return query.Where(w => w.WorkOrderType != null && types.Contains(w.WorkOrderType.Value)); } + #region Phase 2 filter helpers (not wired in Phase 1 board API) + public static IQueryable ApplySiteFilter( IQueryable query, IReadOnlyList? sites) @@ -135,6 +137,8 @@ namespace SeaHaven.DataServices.Helpers && w.TargetWeek <= dateTo)); } + #endregion + public static IQueryable ApplySearchFilter(IQueryable query, string? search) { if (string.IsNullOrWhiteSpace(search)) diff --git a/SeaHaven.DataServices/Interfaces/PrimaryDispatchProjection.cs b/SeaHaven.DataServices/Interfaces/PrimaryDispatchProjection.cs deleted file mode 100644 index ce6ebf6..0000000 --- a/SeaHaven.DataServices/Interfaces/PrimaryDispatchProjection.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SeaHaven.DataServices.Interfaces -{ - public record PrimaryDispatchProjection( - int WorkOrderId, - int? DispatchId, - string? VendorName, - DateTime? ApptDate, - string? DispatchStatus); -} diff --git a/SeaHaven.Services/Board/WorkOrderBoardConstants.cs b/SeaHaven.Services/Board/WorkOrderBoardConstants.cs index a00f3e2..78d1ab5 100644 --- a/SeaHaven.Services/Board/WorkOrderBoardConstants.cs +++ b/SeaHaven.Services/Board/WorkOrderBoardConstants.cs @@ -1,5 +1,8 @@ namespace SeaHaven.Services.Board { + /// + /// Shared board constants for Phase 2+ features. Not referenced in Phase 1. + /// public static class WorkOrderBoardConstants { public const int MaxWindowDays = 90; diff --git a/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs b/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs index cb64549..b64d16c 100644 --- a/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs +++ b/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs @@ -20,6 +20,10 @@ namespace SeaHaven.Services.Helpers return DateOnly.FromDateTime(workOrder.ScheduledDate.Value.Date) < today; } + /// + /// Board read-model past-due check. Phase 1 compares UTC calendar dates; + /// business/location timezone support is planned for a later phase. + /// public static bool IsPastDue( DateTime? scheduledDate, LifecycleStatus? lifecycleStatus, @@ -66,6 +70,10 @@ namespace SeaHaven.Services.Helpers return true; } + /// + /// Maps scheduled date to Mon–Fri board column. Phase 1 uses the stored + /// date's day-of-week (UTC); timezone-aware grouping is planned for a later phase. + /// public static string? GetDayGroup(DateTime? scheduledDate) { if (!scheduledDate.HasValue) diff --git a/SeaHaven.Services/Helpers/WorkOrderOperationalWeek.cs b/SeaHaven.Services/Helpers/WorkOrderOperationalWeek.cs index 5779a17..e8795f1 100644 --- a/SeaHaven.Services/Helpers/WorkOrderOperationalWeek.cs +++ b/SeaHaven.Services/Helpers/WorkOrderOperationalWeek.cs @@ -16,5 +16,20 @@ namespace SeaHaven.Services.Helpers public static string BuildWeekCorrelationId(DateOnly sourceWeekStart) => $"week:{sourceWeekStart:yyyy-MM-dd}"; + + /// + /// Returns a validation error message when the week window is invalid; otherwise null. + /// + public static string? ValidateWeekWindow(DateOnly weekStart, DateOnly weekEnd) + { + if (weekEnd < weekStart) + return "weekEnd must be on or after weekStart."; + + var spanDays = weekEnd.DayNumber - weekStart.DayNumber; + if (spanDays > 6) + return "Week window cannot exceed 7 days."; + + return null; + } } } diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardService.cs index d123f4b..03b5b56 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardService.cs @@ -22,7 +22,9 @@ namespace SeaHaven.Services.Implementation var weekStart = query.WeekStart; var weekEnd = query.WeekEnd ?? weekStart.AddDays(4); - ValidateWeekWindow(weekStart, weekEnd); + var validationError = WorkOrderOperationalWeek.ValidateWeekWindow(weekStart, weekEnd); + if (validationError != null) + throw new ArgumentException(validationError); var dataQuery = new WorkOrderBoardQuery( weekStart, @@ -62,18 +64,10 @@ namespace SeaHaven.Services.Implementation }).ToList(); } - private static void ValidateWeekWindow(DateOnly weekStart, DateOnly weekEnd) - { - if (weekEnd < weekStart) - throw new ArgumentException("weekEnd must be on or after weekStart."); - - var spanDays = weekEnd.DayNumber - weekStart.DayNumber; - if (spanDays > 6) - throw new ArgumentException("Week window cannot exceed 7 days."); - } - private static WorkOrderBoardRowDto MapRow(WorkOrderBoardRawRow row, DateTime utcNow) { + // Appt column: date prefers dispatch appointment (vendor slot), then WO ScheduledDate. + // Time prefers WO ScheduledStart/End (dispatcher window), then dispatch datetime. var apptStart = row.ScheduledStart ?? row.DispatchApptDate; var apptEnd = row.ScheduledEnd;