diff --git a/SeaHaven.DataServices/Implementation/CompletionDocTemplateDataService.cs b/SeaHaven.DataServices/Implementation/CompletionDocTemplateDataService.cs index 0b46c61..0e7a9a7 100644 --- a/SeaHaven.DataServices/Implementation/CompletionDocTemplateDataService.cs +++ b/SeaHaven.DataServices/Implementation/CompletionDocTemplateDataService.cs @@ -8,6 +8,10 @@ namespace SeaHaven.DataServices.Implementation { public class CompletionDocTemplateDataService : ICompletionDocTemplateDataService { + private static readonly LifecycleStatus[] OpenStatuses = Enum.GetValues() + .Where(status => !LifecycleStatusSets.Terminal.Contains(status)) + .ToArray(); + private readonly ApplicationDbContext _context; public CompletionDocTemplateDataService(ApplicationDbContext context) @@ -153,9 +157,12 @@ namespace SeaHaven.DataServices.Implementation .Where(s => s.CompletionDocTemplateId == templateId) .Select(s => (int?)s.Id); + // Shared status filter so legacy rows without a LifecycleStatus are read + // through their legacy status text (a legacy "completed" row is not open). + workOrders = WorkOrderBoardQueryFilters.ApplyStatusFilter(workOrders, OpenStatuses); + return await workOrders .Where(w => w.ServiceId != null && linkedServiceIds.Contains(w.ServiceId)) - .Where(w => !LifecycleStatusSets.Terminal.Contains(w.LifecycleStatus)) .OrderBy(w => w.Id) .Select(w => w.Id) .ToListAsync(cancellationToken); diff --git a/SeaHavenIndustries.Tests/CompletionDocTemplateServiceTests.cs b/SeaHavenIndustries.Tests/CompletionDocTemplateServiceTests.cs index ccb49ee..cc53bbe 100644 --- a/SeaHavenIndustries.Tests/CompletionDocTemplateServiceTests.cs +++ b/SeaHavenIndustries.Tests/CompletionDocTemplateServiceTests.cs @@ -376,6 +376,7 @@ public class CompletionDocTemplateServiceTests new WorkOrder { Id = 107, ServiceId = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, IsDeleted = true }, new WorkOrder { Id = 108, ServiceId = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, istemplate = true }, new WorkOrder { Id = 205, ServiceId = 1, AccountId = 2, LifecycleStatus = LifecycleStatus.InProgress }); + context.workOrders.AddRange(LegacyStatusWorkOrders(serviceId: 1, accountId: 1)); await context.SaveChangesAsync(); var service = NewService(context); @@ -383,13 +384,21 @@ public class CompletionDocTemplateServiceTests var tenantTwo = await service.GetLinkedWorkOrdersAsync(Admin(accountId: 2), 10, CancellationToken.None); var orgWide = await service.GetLinkedWorkOrdersAsync(Admin(), 10, CancellationToken.None); - Assert.Equal(2, tenantOne.Count); - Assert.Equal(new[] { 101, 102 }, tenantOne.WorkOrderIds); + Assert.Equal(3, tenantOne.Count); + Assert.Equal(new[] { 101, 102, 111 }, tenantOne.WorkOrderIds); Assert.Equal(new[] { 205 }, tenantTwo.WorkOrderIds); - Assert.Equal(new[] { 101, 102, 205 }, orgWide.WorkOrderIds); - Assert.Equal(3, orgWide.Count); + Assert.Equal(new[] { 101, 102, 111, 205 }, orgWide.WorkOrderIds); + Assert.Equal(4, orgWide.Count); } + // Legacy rows carry no LifecycleStatus; their legacy status text decides whether they are open. + private static WorkOrder[] LegacyStatusWorkOrders(int serviceId, int accountId) => new[] + { + new WorkOrder { Id = 109, ServiceId = serviceId, AccountId = accountId, LegacyStatus = "Completed" }, + new WorkOrder { Id = 110, ServiceId = serviceId, AccountId = accountId, Status = " Cancelled " }, + new WorkOrder { Id = 111, ServiceId = serviceId, AccountId = accountId, LegacyStatus = "On Hold" } + }; + [Fact] public async Task LinkedWorkOrders_UnknownTemplateOrMissingScope_Throws() { @@ -485,6 +494,40 @@ public class CompletionDocTemplateServiceTests Assert.True((await context.CompletionDocTemplates.SingleAsync()).IsDeleted); } + [Fact] + public async Task Relational_LinkedWorkOrdersExcludeTerminalAndLegacyTerminalRows() + { + await using var connection = new SqliteConnection("DataSource=:memory:"); + await connection.OpenAsync(); + await using var context = await NewSqliteContextAsync(connection); + await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1); + await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "Other Corp"); + context.CompletionDocTemplates.Add(new CompletionDocTemplate { Id = 10, Name = "Backflow" }); + context.Services.Add(new Service + { + Id = 1, + Name = "Backflow", + NormalizedName = "BACKFLOW", + RequiresCompletionDocument = true, + CompletionDocTemplateId = 10 + }); + context.workOrders.AddRange( + new WorkOrder { Id = 101, ServiceId = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled }, + new WorkOrder { Id = 102, ServiceId = 1, AccountId = 1 }, + new WorkOrder { Id = 103, ServiceId = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Completed }, + new WorkOrder { Id = 104, ServiceId = 1, AccountId = 1, LifecycleStatus = LifecycleStatus.Canceled }, + new WorkOrder { Id = 205, ServiceId = 1, AccountId = 2, LifecycleStatus = LifecycleStatus.OnSite }); + context.workOrders.AddRange(LegacyStatusWorkOrders(serviceId: 1, accountId: 1)); + await context.SaveChangesAsync(); + var data = new CompletionDocTemplateDataService(context); + + var tenantOne = await data.GetOpenLinkedWorkOrderIdsAsync(10, 1, CancellationToken.None); + var orgWide = await data.GetOpenLinkedWorkOrderIdsAsync(10, null, CancellationToken.None); + + Assert.Equal(new[] { 101, 102, 111 }, tenantOne); + Assert.Equal(new[] { 101, 102, 111, 205 }, orgWide); + } + [Fact] public async Task Relational_UpdateReplacesProceduresInOrder() {