fix(completion-templates): read legacy status when counting open linked work orders

Work orders without a LifecycleStatus are open or closed according to
their legacy status text. The linked work-order count now goes through
the shared board status filter, so a legacy completed or cancelled row
is no longer reported as depending on the template.
This commit is contained in:
Alexandre Brandizzi 2026-09-25 11:19:59 -03:00
parent 7c097c2750
commit cd23ad5b68
2 changed files with 55 additions and 5 deletions

View file

@ -8,6 +8,10 @@ namespace SeaHaven.DataServices.Implementation
{
public class CompletionDocTemplateDataService : ICompletionDocTemplateDataService
{
private static readonly LifecycleStatus[] OpenStatuses = Enum.GetValues<LifecycleStatus>()
.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);

View file

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