using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Helpers; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Helpers; using SeaHaven.Services.Implementation; namespace SeaHavenIndustries.Tests; /// /// "Has uplift" (advanced filter) and the board Uplift column count only live uplifts: /// a work order whose uplifts were all cancelled, withdrawn, expired or revoked has none. /// public class WorkOrderUpliftLiveStatusTests { private static readonly DateOnly WeekStart = new(2026, 6, 22); private const int WithdrawnOnly = 1; private const int LegacyCancelledOnly = 2; private const int RevokedOnlyViaLink = 3; private const int RevokedPlusPending = 4; private const int RejectedOnly = 5; private const int ExpiredOnly = 6; private const int NoUplift = 7; private const int NewerRevokedOlderAutoApproved = 8; private static readonly int[] LiveUpliftWorkOrders = { RevokedPlusPending, RejectedOnly, NewerRevokedOlderAutoApproved, }; private static ApplicationDbContext CreateContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } private static async Task SeedAsync() { var context = CreateContext(); context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" }); for (var id = WithdrawnOnly; id <= NewerRevokedOlderAutoApproved; id++) { // The revoked-only work order reaches its uplift through a multi-WO dispatch link. var linkedOnly = id == RevokedOnlyViaLink; context.Dispatches.Add(new Dispatch { Id = 10 + id, VendorId = 1, WorkOrderId = linkedOnly ? null : id, }); if (linkedOnly) context.DispatchWorkOrders.Add(new DispatchWorkOrder { DispatchId = 10 + id, WorkOrderId = id }); context.workOrders.Add(new WorkOrder { Id = id, InternalWONumber = $"1000000000{id}", ScheduledDate = new DateTime(2026, 6, 23), LifecycleStatus = LifecycleStatus.Scheduled, PrimaryDispatchId = linkedOnly ? null : 10 + id, }); } var older = new DateTime(2026, 6, 20, 9, 0, 0, DateTimeKind.Utc); var newer = older.AddHours(2); context.DispatchUpliftRequests.AddRange( Uplift(101, WithdrawnOnly, "Withdrawn", older), Uplift(102, LegacyCancelledOnly, "Cancelled", older), Uplift(103, RevokedOnlyViaLink, "Revoked", older), Uplift(104, RevokedPlusPending, "Revoked", older), Uplift(105, RevokedPlusPending, "Pending", newer), Uplift(106, RejectedOnly, "Rejected", older), Uplift(107, ExpiredOnly, "Expired", older), Uplift(108, NewerRevokedOlderAutoApproved, "NoApprovalRequired", older), Uplift(109, NewerRevokedOlderAutoApproved, "Revoked", newer)); await context.SaveChangesAsync(); return context; } private static DispatchUpliftRequest Uplift(int id, int workOrderId, string status, DateTime createdDate) => new() { Id = id, DispatchId = 10 + workOrderId, RequestedNTE = 1000m + id, Status = status, RequiredTier = 1, NotificationStatus = "Pending", CreatedDate = createdDate, }; private static async Task SearchIdsAsync( ApplicationDbContext context, bool hasUplift, List? upliftStatuses = null) { var service = new WorkOrderAdvancedSearchService( new WorkOrderAdvancedSearchDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var result = await service.SearchAsync( new WorkOrderAdvancedSearchQueryDto { DatePreset = WorkOrderAdvancedSearchDatePreset.Custom, DateFrom = WeekStart, DateTo = WeekStart.AddDays(6), HasUplift = hasUplift, UpliftStatuses = upliftStatuses, PageSize = 50, }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); return result.Items.Select(i => i.Id).OrderBy(id => id).ToArray(); } [Fact] public async Task HasUplift_ExcludesWorkOrdersWhoseOnlyUpliftsAreCancelledOrRevoked() { await using var context = await SeedAsync(); var ids = await SearchIdsAsync(context, hasUplift: true); Assert.Equal(LiveUpliftWorkOrders, ids); Assert.DoesNotContain(WithdrawnOnly, ids); Assert.DoesNotContain(LegacyCancelledOnly, ids); Assert.DoesNotContain(RevokedOnlyViaLink, ids); Assert.DoesNotContain(ExpiredOnly, ids); } [Fact] public async Task HasUplift_IncludesWorkOrderWithRevokedAndPendingUplift() { await using var context = await SeedAsync(); Assert.Contains(RevokedPlusPending, await SearchIdsAsync(context, hasUplift: true)); Assert.Equal( new[] { RevokedPlusPending }, await SearchIdsAsync(context, hasUplift: true, new List { "pending" })); } [Fact] public async Task HasUplift_RejectedUpliftStillCounts() { await using var context = await SeedAsync(); Assert.Equal( new[] { RejectedOnly }, await SearchIdsAsync(context, hasUplift: true, new List { "rejected" })); } [Fact] public async Task HasUplift_ExplicitCancelledStatus_FindsCancelledWithdrawnAndExpiredUplifts() { await using var context = await SeedAsync(); var ids = await SearchIdsAsync(context, hasUplift: true, new List { "cancelled" }); Assert.Equal(new[] { WithdrawnOnly, LegacyCancelledOnly, ExpiredOnly }, ids); } [Fact] public async Task HasUplift_ExplicitRevokedStatus_FindsRevokedUpliftsOnPrimaryAndLinkedDispatches() { await using var context = await SeedAsync(); var ids = await SearchIdsAsync(context, hasUplift: true, new List { "revoked" }); Assert.Equal(new[] { RevokedOnlyViaLink, RevokedPlusPending, NewerRevokedOlderAutoApproved }, ids); } [Fact] public async Task BoardUpliftColumn_AgreesWithHasUpliftFilter() { await using var context = await SeedAsync(); var filtered = await SearchIdsAsync(context, hasUplift: true); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var board = await boardService.GetBoardAsync( new WorkOrderBoardQueryDto { WeekStart = WeekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Equal(8, board.Scheduled.Count); foreach (var row in board.Scheduled) { Assert.Equal(filtered.Contains(row.Id), row.UpliftSummary!.HasUplift); } var searchService = new WorkOrderAdvancedSearchService( new WorkOrderAdvancedSearchDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var unfiltered = await searchService.SearchAsync( new WorkOrderAdvancedSearchQueryDto { DatePreset = WorkOrderAdvancedSearchDatePreset.Custom, DateFrom = WeekStart, DateTo = WeekStart.AddDays(6), PageSize = 50, }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); Assert.Equal(8, unfiltered.Items.Count()); foreach (var item in unfiltered.Items) { Assert.Equal(filtered.Contains(item.Id), item.UpliftSummary!.HasUplift); } } [Fact] public async Task BoardUpliftColumn_ShowsNewestLiveUpliftAndNoStatusForNonLiveOnly() { await using var context = await SeedAsync(); var boardService = new WorkOrderBoardService( new WorkOrderBoardDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var board = await boardService.GetBoardAsync( new WorkOrderBoardQueryDto { WeekStart = WeekStart }, WorkOrderAccountTestHelpers.OrgWideAdmin(), null); var byId = board.Scheduled.ToDictionary(r => r.Id, r => r.UpliftSummary!); foreach (var id in new[] { WithdrawnOnly, LegacyCancelledOnly, RevokedOnlyViaLink, ExpiredOnly, NoUplift }) { Assert.False(byId[id].HasUplift); Assert.Null(byId[id].PrimaryStatus); Assert.Null(byId[id].Amount); } Assert.Equal("pending", byId[RevokedPlusPending].PrimaryStatus); Assert.Equal(1, byId[RevokedPlusPending].PendingCount); Assert.Equal("rejected", byId[RejectedOnly].PrimaryStatus); Assert.Equal("auto_approved", byId[NewerRevokedOlderAutoApproved].PrimaryStatus); Assert.Equal(1108m, byId[NewerRevokedOlderAutoApproved].Amount); } [Theory] [InlineData(UpliftStatus.Pending)] [InlineData(UpliftStatus.Approved)] [InlineData(UpliftStatus.Rejected)] [InlineData(UpliftStatus.ChangesRequested)] [InlineData(UpliftStatus.Withdrawn)] [InlineData(UpliftStatus.Expired)] [InlineData(UpliftStatus.NoApprovalRequired)] [InlineData(UpliftStatus.Revoked)] [InlineData(UpliftStatus.LegacyDenied)] [InlineData(UpliftStatus.LegacyCancelled)] public void LiveUpliftStatus_MatchesFrontendCancelledAndRevokedStatuses(string storedStatus) { var frontendStatus = WorkOrderUpliftContractMapper.ToFrontendStatus(storedStatus); var expectedLive = frontendStatus is not ("cancelled" or "revoked"); Assert.Equal(expectedLive, LiveUpliftStatus.IsLive(storedStatus)); } }