Compare commits

...

2 commits

Author SHA1 Message Date
Alexandre Brandizzi
c1910e5310 test(work-orders): pin severity-then-type correction from Overdue
The board sends one PATCH per field, severity before workOrderType, so
correcting an Overdue work order to Emergency or Reactive patches the
severity while the stored type is still Overdue. Dropping or rejecting a
severity patch for the current type would leave the corrected Emergency
work order with no severity. Overdue's no-severity rule is enforced when
the type changes, not on the severity patch.
2026-09-25 11:41:33 -03:00
Alexandre Brandizzi
d82fb18a3d fix(work-orders): map Overdue to PM catalog in service and template lists 2026-09-25 11:30:34 -03:00
3 changed files with 96 additions and 3 deletions

View file

@ -5,6 +5,7 @@ using SeaHaven.DataServices.Interfaces;
using SeaHaven.DataServices.Models;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Exceptions;
using SeaHaven.Services.Helpers;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
@ -34,8 +35,9 @@ namespace SeaHaven.Services.Implementation
public async Task<IReadOnlyList<ServiceDto>> GetAllAsync(bool? isActive, WorkOrderType? workOrderType, CancellationToken cancellationToken)
{
ValidateWorkOrderTypeFilter(workOrderType);
var rows = await _dataService.GetServiceRowsAsync(isActive, workOrderType, cancellationToken);
var catalogType = WorkOrderCatalogType.For(workOrderType);
ValidateWorkOrderTypeFilter(catalogType);
var rows = await _dataService.GetServiceRowsAsync(isActive, catalogType, cancellationToken);
return rows.Select(MapToDto).ToList();
}

View file

@ -33,7 +33,7 @@ namespace SeaHaven.Services.Implementation
public async Task<IReadOnlyList<CompletionDocTemplateDto>> GetTemplatesAsync(string? serviceKey, WorkOrderType? workOrderType)
{
var rows = await _templateData.GetActiveAsync(serviceKey, workOrderType);
var rows = await _templateData.GetActiveAsync(serviceKey, WorkOrderCatalogType.For(workOrderType));
return rows.Select(WorkOrderDetailService.MapTemplate).ToList();
}

View file

@ -183,6 +183,47 @@ public class WorkOrderOverdueTypeTests
Assert.Null(persisted.Severity);
}
// The board sends one request per field, severity before type, so correcting
// Overdue to Emergency patches severity while the stored type is still Overdue.
// A severity patch must not be dropped or rejected for the current type; the
// no-severity invariant is enforced when the type itself changes.
[Fact]
public async Task PatchSeverityThenType_OverdueToEmergency_KeepsSeverity()
{
await using var context = NewContext();
var wo = new WorkOrder
{
Id = 1,
LifecycleStatus = LifecycleStatus.Scheduled,
WorkOrderType = WorkOrderType.Overdue,
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
};
context.workOrders.Add(wo);
await context.SaveChangesAsync();
var service = NewUpdateService(context);
await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
{
Field = WorkOrderBoardFieldNames.Severity,
Value = "2",
WorkOrderVersion = ToVersion(wo)
}, "actor-1");
var updated = await context.workOrders.SingleAsync(w => w.Id == 1);
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
{
Field = WorkOrderBoardFieldNames.WorkOrderType,
Value = "Emergency",
WorkOrderVersion = ToVersion(updated)
}, "actor-1");
Assert.Equal(WorkOrderType.Emergency, result.WorkOrderType);
Assert.Equal(2, result.Severity);
var persisted = await context.workOrders.SingleAsync(w => w.Id == 1);
Assert.Equal(WorkOrderType.Emergency, persisted.WorkOrderType);
Assert.Equal("2", persisted.Severity);
}
[Fact]
public async Task PatchType_PmWithPmService_ToOverdue_KeepsService()
{
@ -278,6 +319,56 @@ public class WorkOrderOverdueTypeTests
Assert.Equal("PM Completion", detail.Completion.Template!.Name);
}
[Fact]
public async Task ListServices_Overdue_ReturnsPmCatalog()
{
await using var context = NewContext();
SeedServices(context);
await context.SaveChangesAsync();
var services = await new ServicesRegistryService(new ServicesRegistryDataService(context))
.GetAllAsync(true, WorkOrderType.Overdue, CancellationToken.None);
Assert.Equal(new[] { PmServiceId }, services.Select(s => s.Id).ToArray());
}
[Fact]
public async Task ListTemplates_Overdue_ReturnsPmTemplates()
{
await using var context = NewContext();
context.CompletionDocTemplates.AddRange(
new CompletionDocTemplate
{
Name = "PM Completion",
ServiceKey = "pm-generic",
WorkOrderType = WorkOrderType.PM,
TemplateUrl = "https://example.com/pm.pdf",
IsActive = true
},
new CompletionDocTemplate
{
Name = "Reactive Completion",
ServiceKey = "reactive-generic",
WorkOrderType = WorkOrderType.Reactive,
TemplateUrl = "https://example.com/reactive.pdf",
IsActive = true
});
await context.SaveChangesAsync();
var service = new WorkOrderCompletionService(
new WorkOrderCompletionDataService(context),
new CompletionDocTemplateDataService(context),
new WorkOrderDetailDataService(context),
new WorkOrderAuditService(
new WorkOrderAuditDataService(context),
new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context))),
WorkOrderAccountTestHelpers.Resolver(context));
var templates = await service.GetTemplatesAsync(null, WorkOrderType.Overdue);
Assert.Equal(new[] { "PM Completion" }, templates.Select(t => t.Name).ToArray());
}
[Fact]
public async Task Search_TypeOverdue_ReturnsOverdueTypeOnly_NotPastDueRows()
{