using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Implementation; namespace SeaHavenIndustries.Tests; public class WorkOrderBoardDataServiceTests { private static ApplicationDbContext CreateContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } [Fact] public async Task GetBoardRows_ReturnsScheduledInWeekAndOmitsUnscheduledDump() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC", ContactName = "Tech", Phone = "555-0100" }); context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0) }); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10, SiteCode = "BK5" }, new WorkOrder { Id = 2, InternalWONumber = "10000000002", ScheduledDate = null, LifecycleStatus = LifecycleStatus.Incomplete, SiteCode = "BK6" }, new WorkOrder { Id = 3, InternalWONumber = "10000000003", ScheduledDate = new DateTime(2026, 7, 1), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(1, result.ScheduledRows[0].Id); Assert.Equal("Acme HVAC", result.ScheduledRows[0].VendorName); Assert.Empty(result.UnscheduledRows); Assert.Equal(1, result.ScheduledTotalBeforeSearch); } [Fact] public async Task GetBoardRows_DifferentWeeksReturnDifferentScheduledSets() { await using var context = CreateContext(); var weekA = new DateOnly(2026, 6, 22); var weekB = new DateOnly(2026, 6, 29); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 2, InternalWONumber = "10000000002", ScheduledDate = new DateTime(2026, 6, 30), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 3, InternalWONumber = "10000000003", ScheduledDate = null, LifecycleStatus = LifecycleStatus.Incomplete }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var resultA = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekA, weekA.AddDays(4), null, false, null, false, null, null)); var resultB = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekB, weekB.AddDays(4), null, false, null, false, null, null)); Assert.Equal(1, resultA.ScheduledRows[0].Id); Assert.Equal(2, resultB.ScheduledRows.Count); Assert.Contains(resultB.ScheduledRows, r => r.Id == 1); Assert.Contains(resultB.ScheduledRows, r => r.Id == 2); Assert.Empty(resultA.UnscheduledRows); Assert.Empty(resultB.UnscheduledRows); Assert.Equal(1, resultA.ScheduledTotalBeforeSearch); Assert.Equal(2, resultB.ScheduledTotalBeforeSearch); } [Fact] public async Task GetBoardRows_IncludesPriorWeekUnresolvedAndKeepsScheduledDate() { await using var context = CreateContext(); var viewedWeek = new DateOnly(2026, 5, 18); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 5, 13), LifecycleStatus = LifecycleStatus.Scheduled, CarriedOver = 2, OriginalDate = new DateOnly(2026, 5, 13), OriginalWeek = new DateOnly(2026, 5, 11) }, new WorkOrder { Id = 2, InternalWONumber = "10000000002", ScheduledDate = new DateTime(2026, 5, 13), LifecycleStatus = LifecycleStatus.Completed }, new WorkOrder { Id = 3, InternalWONumber = "10000000003", ScheduledDate = new DateTime(2026, 5, 13), LifecycleStatus = LifecycleStatus.Canceled }, new WorkOrder { Id = 4, InternalWONumber = "10000000004", ScheduledDate = new DateTime(2026, 5, 20), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( viewedWeek, viewedWeek.AddDays(4), null, false, null, false, null, null)); Assert.Equal(2, result.ScheduledRows.Count); var carried = Assert.Single(result.ScheduledRows, r => r.Id == 1); Assert.Equal(new DateTime(2026, 5, 13), carried.ScheduledDate); Assert.Equal(2, carried.CarriedOver); Assert.Equal(new DateOnly(2026, 5, 11), carried.OriginalWeek); Assert.Equal(new DateOnly(2026, 5, 13), carried.OriginalDate); Assert.Contains(result.ScheduledRows, r => r.Id == 4); Assert.DoesNotContain(result.ScheduledRows, r => r.Id == 2); Assert.DoesNotContain(result.ScheduledRows, r => r.Id == 3); Assert.Equal(2, result.ScheduledTotalBeforeSearch); } [Fact] public async Task GetBoardRows_FiltersByDispatcher() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.AddRange( new WorkOrder { Id = 1, AssignTo = "user-a", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 2, AssignTo = "user-b", ScheduledDate = new DateTime(2026, 6, 24), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), new[] { "user-a" }, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal("user-a", result.ScheduledRows[0].AssignTo); } [Fact] public async Task GetBoardRows_DatedUnassigned_AppearsInScheduledNotUnscheduled() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "10000000001", AssignTo = null, ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 2, InternalWONumber = "10000000002", AssignTo = "user-a", ScheduledDate = new DateTime(2026, 6, 24), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 3, InternalWONumber = "10000000003", AssignTo = null, ScheduledDate = null, LifecycleStatus = LifecycleStatus.Incomplete }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Equal(2, result.ScheduledRows.Count); Assert.Contains(result.ScheduledRows, r => r.Id == 1 && r.AssignTo == null); Assert.Contains(result.ScheduledRows, r => r.Id == 2 && r.AssignTo == "user-a"); Assert.DoesNotContain(result.ScheduledRows, r => r.Id == 3); Assert.Empty(result.UnscheduledRows); Assert.Equal(2, result.ScheduledTotalBeforeSearch); } [Fact] public async Task GetBoardRows_FiltersByUnassignedDispatcher_ReturnsOnlyUnassignedInWeek() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "10000000001", AssignTo = null, ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 2, InternalWONumber = "10000000002", AssignTo = "user-a", ScheduledDate = new DateTime(2026, 6, 24), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 3, InternalWONumber = "10000000003", AssignTo = "", ScheduledDate = new DateTime(2026, 6, 25), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 4, InternalWONumber = "10000000004", AssignTo = null, ScheduledDate = null, LifecycleStatus = LifecycleStatus.Incomplete }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), new[] { "__unassigned__" }, false, null, false, null, null)); Assert.Equal(2, result.ScheduledRows.Count); Assert.Contains(result.ScheduledRows, r => r.Id == 1); Assert.Contains(result.ScheduledRows, r => r.Id == 3); Assert.DoesNotContain(result.ScheduledRows, r => r.Id == 2); Assert.DoesNotContain(result.ScheduledRows, r => r.Id == 4); Assert.Empty(result.UnscheduledRows); Assert.Equal(2, result.ScheduledTotalBeforeSearch); } [Fact] public async Task GetBoardRows_MyWorkOrders_FiltersCurrentUser() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.AddRange( new WorkOrder { Id = 1, AssignTo = "me", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 2, AssignTo = "other", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, true, null, false, null, "me")); Assert.Single(result.ScheduledRows); Assert.Equal(1, result.ScheduledRows[0].Id); } } public class WorkOrderBoardPendingUpliftTests { private static ApplicationDbContext CreateContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } [Fact] public async Task GetBoardRows_PendingUpliftOnPrimaryDispatch_SurfacesCount() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, WorkOrderId = 1, ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0) }); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10, SiteCode = "BK5" }); context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { Id = 100, DispatchId = 10, RequestedNTE = 1500m, Status = "Pending", RequiredTier = 1, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(1, result.ScheduledRows[0].PendingUpliftCount); var dto = WorkOrderBoardService.MapRawRow(result.ScheduledRows[0], DateTime.UtcNow); Assert.Equal(1, dto.PendingUpliftCount); Assert.NotNull(dto.UpliftSummary); Assert.True(dto.UpliftSummary!.HasUplift); Assert.Equal(1, dto.UpliftSummary.PendingCount); Assert.Equal("pending", dto.UpliftSummary.PrimaryStatus); Assert.Equal(1500m, dto.UpliftSummary.Amount); } [Fact] public async Task GetBoardRows_VendorPortalUplift_SummaryAmountIsTheRequestedIncrease() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, WorkOrderId = 1, NTEAmount = 600m, ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0) }); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10, SiteCode = "BK5" }); // A vendor-portal request (no identity user) stores the requested NTE total. context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { Id = 100, DispatchId = 10, CurrentNTE = 600m, RequestedNTE = 900m, Status = "Pending", RequiredTier = 1, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var result = await new WorkOrderBoardDataService(context).GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); var dto = WorkOrderBoardService.MapRawRow(Assert.Single(result.ScheduledRows), DateTime.UtcNow); Assert.Equal(300m, dto.UpliftSummary!.Amount); } [Fact] public async Task GetBoardRows_ApprovedUplift_DoesNotBlock() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, WorkOrderId = 1 }); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10 }); context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { Id = 100, DispatchId = 10, RequestedNTE = 1500m, Status = "Approved", RequiredTier = 1, NotificationStatus = "Sent" }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(0, result.ScheduledRows[0].PendingUpliftCount); } [Fact] public async Task GetBoardRows_PendingUpliftViaDispatchWorkOrderLink_SurfacesCount() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); // Secondary link only via DispatchWorkOrders (no WorkOrderId / PrimaryDispatch). context.Dispatches.Add(new Dispatch { Id = 20, VendorId = 1, WorkOrderId = null }); context.DispatchWorkOrders.Add(new DispatchWorkOrder { DispatchId = 20, WorkOrderId = 2 }); context.workOrders.Add(new WorkOrder { Id = 2, InternalWONumber = "10000000002", ScheduledDate = new DateTime(2026, 6, 24), LifecycleStatus = LifecycleStatus.Scheduled }); context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { Id = 200, DispatchId = 20, RequestedNTE = 900m, Status = "Pending", RequiredTier = 1, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(2, result.ScheduledRows[0].Id); Assert.Equal(1, result.ScheduledRows[0].PendingUpliftCount); } [Fact] public async Task GetBoardRows_PendingUpliftOnSoftDeletedDispatch_DoesNotCount() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, WorkOrderId = 1, ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0), IsDeleted = true }); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10, SiteCode = "BK5" }); context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { Id = 100, DispatchId = 10, RequestedNTE = 1500m, Status = "Pending", RequiredTier = 1, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(0, result.ScheduledRows[0].PendingUpliftCount); } [Fact] public async Task GetBoardRows_PendingUpliftOnPrimaryAndDispatchWorkOrderLink_DoesNotDoubleCount() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); context.Dispatches.Add(new Dispatch { Id = 10, VendorId = 1, WorkOrderId = 1 }); context.DispatchWorkOrders.Add(new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 1 }); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = 10 }); context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { Id = 100, DispatchId = 10, RequestedNTE = 1500m, Status = "Pending", RequiredTier = 1, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(1, result.ScheduledRows[0].PendingUpliftCount); Assert.True(result.ScheduledRows[0].HasUplift); } [Fact] public async Task GetBoardRows_ScheduledDateWithTimeOnWeekEnd_IsIncluded() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); var weekEnd = weekStart.AddDays(4); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = weekEnd.ToDateTime(new TimeOnly(23, 59, 59)), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var service = new WorkOrderBoardDataService(context); var result = await service.GetBoardRowsAsync(new WorkOrderBoardQuery( weekStart, weekEnd, null, false, null, false, null, null)); Assert.Single(result.ScheduledRows); Assert.Equal(1, result.ScheduledRows[0].Id); } [Fact] public async Task GetBoardRows_CanceledToken_ThrowsOperationCanceled() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); using var cts = new CancellationTokenSource(); cts.Cancel(); var service = new WorkOrderBoardDataService(context); await Assert.ThrowsAnyAsync(() => service.GetBoardRowsAsync( new WorkOrderBoardQuery( weekStart, weekStart.AddDays(4), null, false, null, false, null, null), cts.Token)); } } public class WorkOrderBoardServiceTests { private static ApplicationDbContext CreateContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } [Fact] public async Task GetBoardAsync_ComputesCountsAndPastDue() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", DueDate = new DateTime(2026, 6, 20), ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var dataService = new WorkOrderBoardDataService(context); var boardService = new WorkOrderBoardService(dataService, WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Equal(1, response.Counts.Total); Assert.Single(response.Scheduled); Assert.True(response.Scheduled[0].IsPastDue); Assert.Equal("wednesday", response.Scheduled[0].DayGroup); } [Fact] public async Task GetBoardAsync_MapsIsAddOn() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", WorkOrderType = WorkOrderType.PM, IsAddOn = true, ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Single(response.Scheduled); Assert.True(response.Scheduled[0].IsAddOn); Assert.Equal(WorkOrderType.PM, response.Scheduled[0].WorkOrderType); } [Fact] public async Task GetBoardAsync_MapsAvetaRequiredAndOriginalDate() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", WorkOrderType = WorkOrderType.PM, AvetaRequired = true, OriginalDate = new DateOnly(2026, 5, 8), OriginalWeek = new DateOnly(2026, 5, 4), ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Single(response.Scheduled); Assert.True(response.Scheduled[0].AvetaRequired); Assert.Equal(new DateOnly(2026, 5, 8), response.Scheduled[0].OriginalDate); Assert.Equal(new DateOnly(2026, 5, 4), response.Scheduled[0].OriginalWeek); Assert.False(response.Scheduled[0].HasAvetaDocument); } [Fact] public async Task GetBoardAsync_HasAvetaDocument_WhenAvetaAttachmentExists() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", AvetaRequired = true, ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); context.workOrderAttachments.Add(new WorkOrderAttachments { WorkorderId = 1, Attachments = "https://example.com/aveta.pdf", Category = WorkOrderMediaCategory.Aveta }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Single(response.Scheduled); Assert.True(response.Scheduled[0].AvetaRequired); Assert.True(response.Scheduled[0].HasAvetaDocument); } [Fact] public async Task GetBoardAsync_HasAvetaDocumentFalse_WhenOnlyExtraAttachment() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", AvetaRequired = true, ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); context.workOrderAttachments.Add(new WorkOrderAttachments { WorkorderId = 1, Attachments = "https://example.com/extra.pdf", Category = WorkOrderMediaCategory.Extra }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Single(response.Scheduled); Assert.True(response.Scheduled[0].AvetaRequired); Assert.False(response.Scheduled[0].HasAvetaDocument); } [Fact] public async Task GetBoardAsync_MediaCount_CountsLiveAttachmentsOnly() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", WorkOrderType = WorkOrderType.Emergency, ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); context.workOrderAttachments.Add(new WorkOrderAttachments { WorkorderId = 1, Attachments = "https://example.com/before.jpg", Category = WorkOrderMediaCategory.Before }); context.workOrderAttachments.Add(new WorkOrderAttachments { WorkorderId = 1, Attachments = "https://example.com/extra.jpg", Category = WorkOrderMediaCategory.Extra }); context.workOrderAttachments.Add(new WorkOrderAttachments { WorkorderId = 1, Attachments = "https://example.com/removed.jpg", Category = WorkOrderMediaCategory.Extra, IsDeleted = true }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Single(response.Scheduled); Assert.Equal(2, response.Scheduled[0].MediaCount); } [Fact] public async Task GetBoardAsync_MediaCount_ZeroWithoutAttachments() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", WorkOrderType = WorkOrderType.Emergency, ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Single(response.Scheduled); Assert.Equal(0, response.Scheduled[0].MediaCount); } [Fact] public async Task GetBoardAsync_InvalidWeekWindow_Throws() { await using var context = CreateContext(); var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); await Assert.ThrowsAsync(() => boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = new DateOnly(2026, 6, 22), WeekEnd = new DateOnly(2026, 6, 15) }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null)); } [Fact] public async Task GetBoardAsync_CanceledToken_ThrowsOperationCanceled() { await using var context = CreateContext(); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); using var cts = new CancellationTokenSource(); cts.Cancel(); await Assert.ThrowsAnyAsync(() => boardService.GetBoardAsync( new WorkOrderBoardQueryDto { WeekStart = new DateOnly(2026, 6, 22) }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null, cts.Token)); } [Fact] public void MapRawRow_DateOnlyDispatch_KeepsApptDate_LeavesApptTimeNull() { var dispatchApptDate = new DateTime(2026, 6, 25); var row = RawRow( scheduledDate: new DateTime(2026, 6, 23), scheduledStart: null, scheduledEnd: null, dispatchApptDate: dispatchApptDate); var dto = WorkOrderBoardService.MapRawRow(row, DateTime.UtcNow); Assert.Equal(dispatchApptDate, dto.ApptDate); Assert.Null(dto.ApptTime); } [Fact] public void MapRawRow_DispatchWithClock_FallsBackToDispatchTime() { var dispatchApptDate = new DateTime(2026, 6, 25, 9, 0, 0); var row = RawRow( scheduledDate: new DateTime(2026, 6, 23), scheduledStart: null, scheduledEnd: null, dispatchApptDate: dispatchApptDate); var dto = WorkOrderBoardService.MapRawRow(row, DateTime.UtcNow); Assert.Equal(dispatchApptDate, dto.ApptDate); Assert.Equal("09:00", dto.ApptTime); } [Fact] public void MapRawRow_PmEditTakesPrecedenceOverServiceSnapshot() { var row = RawRow( scheduledDate: new DateTime(2026, 6, 23), scheduledStart: null, scheduledEnd: null, dispatchApptDate: null) with { Trade = "Updated service label", ServiceNameSnapshot = "Original service label" }; var dto = WorkOrderBoardService.MapRawRow(row, DateTime.UtcNow); Assert.Equal("Updated service label", dto.Pm); } private static WorkOrderBoardRawRow RawRow( DateTime? scheduledDate, DateTime? scheduledStart, DateTime? scheduledEnd, DateTime? dispatchApptDate) => new( Id: 1, InternalWONumber: "10000000001", RescheduleCount: 0, CarriedOver: 0, IsAddOn: false, WorkOrderType: null, ServiceId: null, ServiceNameSnapshot: null, SiteCode: null, LocationName: null, PocName: null, PocPhone: null, PocNotes: null, LifecycleStatus: LifecycleStatus.Scheduled, LegacyStatus: null, AssignTo: null, DispatcherFirstName: null, DispatcherLastName: null, Initials: null, Color: null, DueDate: null, ScheduledDate: scheduledDate, ScheduledStart: scheduledStart, ScheduledEnd: scheduledEnd, TargetWeek: null, ScheduleWeekOnly: null, VendorId: null, VendorName: null, TechName: null, TechPhone: null, DispatchApptDate: dispatchApptDate, Trade: null, Problem: null, ServiceNotes: null, ExtraServices: null, AdditionalContacts: null, DocStatus: null, CompletedDate: null, FlagColor: null, PrimaryDispatchId: 10, RowVersion: null, DispatchRowVersion: null, PendingUpliftCount: 0, HasUplift: false, PrimaryUpliftStatus: null, PrimaryUpliftAmount: null, PrimaryDispatchStatus: null, AvetaRequired: false, HasAvetaDocument: false, OriginalDate: null, OriginalWeek: null, IsUnscheduled: false); [Fact] public async Task GetBoardAsync_MapsNumericSeverity() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", WorkOrderType = WorkOrderType.Emergency, Severity = "2", ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Equal(2, response.Scheduled[0].Severity); } [Fact] public async Task GetBoardAsync_MapsNonNumericSeverityAsNull() { await using var context = CreateContext(); var weekStart = new DateOnly(2026, 6, 22); context.workOrders.Add(new WorkOrder { Id = 1, InternalWONumber = "10000000001", WorkOrderType = WorkOrderType.Reactive, Severity = "Sev 2", ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto { WeekStart = weekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Null(response.Scheduled[0].Severity); } }