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.
This commit is contained in:
Arthur Bassi 2026-07-09 10:06:36 -03:00
parent 8efe4460bb
commit c2d0bd45e3
12 changed files with 42 additions and 67 deletions

View file

@ -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<WorkOrderType>? 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
{

View file

@ -1,7 +0,0 @@
namespace SeaHaven.DataServices.Board
{
public static class WorkOrderBoardConstants
{
public const int ClientSideThreshold = 300;
}
}

View file

@ -1,10 +0,0 @@
namespace SeaHaven.DataServices.DTOs
{
public record WorkOrderBoardQuery(
DateOnly ScheduledFrom,
DateOnly ScheduledTo,
string? Search,
IReadOnlyList<string>? AssigneeIds,
IReadOnlyList<string>? DbStatuses,
int? LocationId);
}

View file

@ -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);
}

View file

@ -1,9 +0,0 @@
namespace SeaHaven.DataServices.Exceptions
{
public class BoardQueryException : Exception
{
public BoardQueryException(string message) : base(message)
{
}
}
}

View file

@ -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(),

View file

@ -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<WorkOrder> ApplySiteFilter(
IQueryable<WorkOrder> query,
IReadOnlyList<string>? sites)
@ -135,6 +137,8 @@ namespace SeaHaven.DataServices.Helpers
&& w.TargetWeek <= dateTo));
}
#endregion
public static IQueryable<WorkOrder> ApplySearchFilter(IQueryable<WorkOrder> query, string? search)
{
if (string.IsNullOrWhiteSpace(search))

View file

@ -1,9 +0,0 @@
namespace SeaHaven.DataServices.Interfaces
{
public record PrimaryDispatchProjection(
int WorkOrderId,
int? DispatchId,
string? VendorName,
DateTime? ApptDate,
string? DispatchStatus);
}

View file

@ -1,5 +1,8 @@
namespace SeaHaven.Services.Board
{
/// <summary>
/// Shared board constants for Phase 2+ features. Not referenced in Phase 1.
/// </summary>
public static class WorkOrderBoardConstants
{
public const int MaxWindowDays = 90;

View file

@ -20,6 +20,10 @@ namespace SeaHaven.Services.Helpers
return DateOnly.FromDateTime(workOrder.ScheduledDate.Value.Date) < today;
}
/// <summary>
/// Board read-model past-due check. Phase 1 compares UTC calendar dates;
/// business/location timezone support is planned for a later phase.
/// </summary>
public static bool IsPastDue(
DateTime? scheduledDate,
LifecycleStatus? lifecycleStatus,
@ -66,6 +70,10 @@ namespace SeaHaven.Services.Helpers
return true;
}
/// <summary>
/// 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.
/// </summary>
public static string? GetDayGroup(DateTime? scheduledDate)
{
if (!scheduledDate.HasValue)

View file

@ -16,5 +16,20 @@ namespace SeaHaven.Services.Helpers
public static string BuildWeekCorrelationId(DateOnly sourceWeekStart)
=> $"week:{sourceWeekStart:yyyy-MM-dd}";
/// <summary>
/// Returns a validation error message when the week window is invalid; otherwise null.
/// </summary>
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;
}
}
}

View file

@ -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;