Skip to content

Commit 58e379e

Browse files
committed
feat: filter and sort active service orders in listing (F2-01)
GET /api/service-orders now excludes Completed and Delivered orders and applies priority ordering: InExecution → AwaitingApproval → InDiagnosis → Received, with CreatedAt ASC as tiebreaker. Unit and integration tests updated to cover exclusion and priority ordering. Closes #148
1 parent 26b5954 commit 58e379e

3 files changed

Lines changed: 124 additions & 13 deletions

File tree

src/MechanicsSoftware.Application/UseCases/ServiceOrders/Handlers/ListServiceOrdersHandler.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ public sealed class ListServiceOrdersHandler(IAppDbContext db)
1010
public async Task<IReadOnlyList<ServiceOrderSummaryResponse>> ExecuteAsync(
1111
ListServiceOrdersQuery query, CancellationToken cancellationToken = default)
1212
{
13-
var orders = db.ServiceOrders.AsQueryable();
13+
var completed = new ServiceOrderStatus(ServiceOrderStatus.Status.Completed);
14+
var delivered = new ServiceOrderStatus(ServiceOrderStatus.Status.Delivered);
15+
var inExecution = new ServiceOrderStatus(ServiceOrderStatus.Status.InExecution);
16+
var awaitingApproval = new ServiceOrderStatus(ServiceOrderStatus.Status.AwaitingApproval);
17+
var inDiagnosis = new ServiceOrderStatus(ServiceOrderStatus.Status.InDiagnosis);
18+
19+
var orders = db.ServiceOrders
20+
.Where(o => o.Status != completed && o.Status != delivered);
1421

1522
if (!string.IsNullOrWhiteSpace(query.Status))
1623
{
@@ -23,7 +30,10 @@ public async Task<IReadOnlyList<ServiceOrderSummaryResponse>> ExecuteAsync(
2330
}
2431

2532
return await orders
26-
.OrderByDescending(o => o.CreatedAt)
33+
.OrderBy(o => o.Status == inExecution ? 1
34+
: o.Status == awaitingApproval ? 2
35+
: o.Status == inDiagnosis ? 3 : 4)
36+
.ThenBy(o => o.CreatedAt)
2737
.Select(o => new ServiceOrderSummaryResponse(
2838
o.Id,
2939
o.CustomerId,

tests/MechanicsSoftware.IntegrationTests/ServiceOrders/ServiceOrderFlowTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,56 @@ await AssertMovementExistsAsync(partId, StockMovementType.Release,
146146
await AssertMovementDoesNotExistAsync(partId, StockMovementType.Outbound);
147147
}
148148

149+
[Fact]
150+
public async Task List_ReturnsActiveOrdersOrderedByStatusPriority()
151+
{
152+
var client = await AuthenticatedClientAsync();
153+
154+
var customerId = await CreateCustomerAsync(
155+
client, document: "98765432100", email: "list@example.com", phone: "11911112222");
156+
var vehicleId = await CreateVehicleAsync(client, customerId, plate: "LST1T01");
157+
var serviceId = await CreateServiceAsync(client, name: "Teste de listagem");
158+
159+
var receivedId = await CreateServiceOrderAsync(client, customerId, vehicleId);
160+
161+
var inDiagnosisId = await CreateServiceOrderAsync(client, customerId, vehicleId);
162+
await StartDiagnosisAsync(client, inDiagnosisId);
163+
164+
var awaitingId = await CreateServiceOrderAsync(client, customerId, vehicleId);
165+
await StartDiagnosisAsync(client, awaitingId);
166+
await AddServiceItemAsync(client, awaitingId, serviceId);
167+
await GenerateBudgetAsync(client, awaitingId);
168+
await SendBudgetAsync(client, awaitingId);
169+
170+
var inExecutionId = await CreateServiceOrderAsync(client, customerId, vehicleId);
171+
await StartDiagnosisAsync(client, inExecutionId);
172+
await AddServiceItemAsync(client, inExecutionId, serviceId);
173+
await GenerateBudgetAsync(client, inExecutionId);
174+
await SendBudgetAsync(client, inExecutionId);
175+
await ApproveAsync(client, inExecutionId);
176+
177+
var deliveredId = await CreateServiceOrderAsync(client, customerId, vehicleId);
178+
await StartDiagnosisAsync(client, deliveredId);
179+
await AddServiceItemAsync(client, deliveredId, serviceId);
180+
await GenerateBudgetAsync(client, deliveredId);
181+
await SendBudgetAsync(client, deliveredId);
182+
await ApproveAsync(client, deliveredId);
183+
await StartExecutionAsync(client, deliveredId);
184+
await CompleteAsync(client, deliveredId);
185+
await DeliverAsync(client, deliveredId);
186+
187+
var response = await client.GetAsync("/api/service-orders");
188+
response.StatusCode.Should().Be(HttpStatusCode.OK);
189+
var orders = await ReadAsync<List<ServiceOrderSummaryDto>>(response);
190+
191+
orders.Should().HaveCount(4);
192+
orders.Should().NotContain(o => o.Id == deliveredId);
193+
orders[0].Status.Should().Be("IN_EXECUTION");
194+
orders[1].Status.Should().Be("AWAITING_APPROVAL");
195+
orders[2].Status.Should().Be("IN_DIAGNOSIS");
196+
orders[3].Status.Should().Be("RECEIVED");
197+
}
198+
149199
// --------------------------------------------------------------------
150200
// HTTP helpers
151201
// --------------------------------------------------------------------
@@ -393,6 +443,7 @@ private sealed record VehicleDto(Guid Id);
393443
private sealed record ServiceDto(Guid Id);
394444
private sealed record PartDto(Guid Id, int StockQuantity, int ReservedQuantity);
395445
private sealed record ServiceOrderDto(Guid Id, string Status);
446+
private sealed record ServiceOrderSummaryDto(Guid Id, string Status);
396447
private sealed record BudgetDto(Guid Id, int TotalInCents, string Status);
397448
private sealed record AddPartItemDto(Guid Id, string Availability, string? Warning);
398449
private sealed record ServiceOrderStatusDto(Guid Id, string Status, DateTime? DeliveredAt);

tests/MechanicsSoftware.UnitTests/Application/ServiceOrders/ListServiceOrdersUseCaseTests.cs

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using FluentAssertions;
1+
using FluentAssertions;
22
using MechanicsSoftware.Application.UseCases.ServiceOrders;
33
using MechanicsSoftware.Application.UseCases.ServiceOrders.Commands;
44
using MechanicsSoftware.Application.UseCases.ServiceOrders.Handlers;
@@ -14,11 +14,11 @@ namespace MechanicsSoftware.UnitTests.Application.ServiceOrders;
1414
public class ListServiceOrdersUseCaseTests
1515
{
1616
[Fact]
17-
public async Task ExecuteAsync_NoFilter_ReturnsAllOrders()
17+
public async Task ExecuteAsync_NoFilter_ReturnsActiveOrders()
1818
{
1919
await using var db = InMemoryDbContextHelper.Create();
20-
db.ServiceOrders.Add(ServiceOrder.Create(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()));
21-
db.ServiceOrders.Add(ServiceOrder.Create(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()));
20+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Received));
21+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Received));
2222
await db.SaveChangesAsync();
2323

2424
var result = await new ListServiceOrdersHandler(db).ExecuteAsync(new ListServiceOrdersQuery());
@@ -30,11 +30,8 @@ public async Task ExecuteAsync_NoFilter_ReturnsAllOrders()
3030
public async Task ExecuteAsync_StatusFilter_ReturnsMatchingOrders()
3131
{
3232
await using var db = InMemoryDbContextHelper.Create();
33-
var received = ServiceOrder.Create(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid());
34-
var inDiagnosis = ServiceOrder.Create(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid());
35-
inDiagnosis.StartDiagnosis();
36-
db.ServiceOrders.Add(received);
37-
db.ServiceOrders.Add(inDiagnosis);
33+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Received));
34+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.InDiagnosis));
3835
await db.SaveChangesAsync();
3936

4037
var result = await new ListServiceOrdersHandler(db).ExecuteAsync(
@@ -45,15 +42,68 @@ public async Task ExecuteAsync_StatusFilter_ReturnsMatchingOrders()
4542
}
4643

4744
[Fact]
48-
public async Task ExecuteAsync_InvalidStatusFilter_ReturnsAllOrders()
45+
public async Task ExecuteAsync_InvalidStatusFilter_ReturnsActiveOrders()
4946
{
5047
await using var db = InMemoryDbContextHelper.Create();
51-
db.ServiceOrders.Add(ServiceOrder.Create(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()));
48+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Received));
5249
await db.SaveChangesAsync();
5350

5451
var result = await new ListServiceOrdersHandler(db).ExecuteAsync(
5552
new ListServiceOrdersQuery(Status: "UNKNOWN_STATUS"));
5653

5754
result.Should().HaveCount(1);
5855
}
56+
57+
[Fact]
58+
public async Task ExecuteAsync_NoFilter_ExcludesCompletedAndDeliveredOrders()
59+
{
60+
await using var db = InMemoryDbContextHelper.Create();
61+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Received));
62+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.InDiagnosis));
63+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Completed));
64+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Delivered));
65+
await db.SaveChangesAsync();
66+
67+
var result = await new ListServiceOrdersHandler(db).ExecuteAsync(new ListServiceOrdersQuery());
68+
69+
result.Should().HaveCount(2);
70+
result.Should().NotContain(o => o.Status == "COMPLETED" || o.Status == "DELIVERED");
71+
}
72+
73+
[Fact]
74+
public async Task ExecuteAsync_NoFilter_OrdersByStatusPriorityThenCreatedAt()
75+
{
76+
await using var db = InMemoryDbContextHelper.Create();
77+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.Received));
78+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.InDiagnosis));
79+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.AwaitingApproval));
80+
db.ServiceOrders.Add(CreateOrderInState(ServiceOrderStatus.Status.InExecution));
81+
await db.SaveChangesAsync();
82+
83+
var result = await new ListServiceOrdersHandler(db).ExecuteAsync(new ListServiceOrdersQuery());
84+
85+
result.Should().HaveCount(4);
86+
result[0].Status.Should().Be("IN_EXECUTION");
87+
result[1].Status.Should().Be("AWAITING_APPROVAL");
88+
result[2].Status.Should().Be("IN_DIAGNOSIS");
89+
result[3].Status.Should().Be("RECEIVED");
90+
}
91+
92+
private static ServiceOrder CreateOrderInState(ServiceOrderStatus.Status target)
93+
{
94+
var order = ServiceOrder.Create(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid());
95+
if (target == ServiceOrderStatus.Status.Received) return order;
96+
order.StartDiagnosis();
97+
if (target == ServiceOrderStatus.Status.InDiagnosis) return order;
98+
order.AddServiceItem(Guid.NewGuid(), "Service", new Money(1_000), 1);
99+
order.GenerateBudget();
100+
order.SendBudget();
101+
if (target == ServiceOrderStatus.Status.AwaitingApproval) return order;
102+
order.Approve();
103+
if (target == ServiceOrderStatus.Status.InExecution) return order;
104+
order.Complete();
105+
if (target == ServiceOrderStatus.Status.Completed) return order;
106+
order.Deliver();
107+
return order;
108+
}
59109
}

0 commit comments

Comments
 (0)