mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-03 13:53:30 +00:00
Merge remote-tracking branch 'origin/feat/ab/sh-207-uplift-queue' into feat/ab/sh-210-uplift-decisions
This commit is contained in:
commit
e0ec8dbc73
2 changed files with 145 additions and 0 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -372,6 +373,149 @@ public sealed class UpliftQueueReadTests
|
||||||
item.EvidenceSizeBytes.Should().Be(512);
|
item.EvidenceSizeBytes.Should().Be(512);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Queue contract fields (SH-208/SH-213) ---
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_MarksWorkOrderClosed_OnlyForTerminalLifecycle()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendorA, workOrderA) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
|
||||||
|
var (vendorB, workOrderB) = await SeedWorkOrderAsync(context, "WO-B", "SITE-B", "Plumbing");
|
||||||
|
workOrderA.LifecycleStatus = LifecycleStatus.Completed;
|
||||||
|
workOrderB.LifecycleStatus = LifecycleStatus.Canceled;
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var dispatchA = await SeedDispatchAsync(context, vendorA, workOrderA, "DIS-A");
|
||||||
|
var dispatchB = await SeedDispatchAsync(context, vendorB, workOrderB, "DIS-B");
|
||||||
|
var (vendorC, workOrderC) = await SeedWorkOrderAsync(context, "WO-C", "SITE-C", "Electrical");
|
||||||
|
var dispatchC = await SeedDispatchAsync(context, vendorC, workOrderC, "DIS-C");
|
||||||
|
context.DispatchUpliftRequests.AddRange(
|
||||||
|
Request(dispatchA, "Approved", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 2)),
|
||||||
|
Request(dispatchB, "Approved", new DateTime(2026, 3, 3), decided: new DateTime(2026, 3, 4)),
|
||||||
|
Request(dispatchC, "Approved", new DateTime(2026, 3, 5), decided: new DateTime(2026, 3, 6)));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Admin"), "Approved", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Single(i => i.WorkOrderNumber == "WO-A").WorkOrderClosed.Should().BeTrue();
|
||||||
|
result.Items.Single(i => i.WorkOrderNumber == "WO-B").WorkOrderClosed.Should().BeTrue();
|
||||||
|
result.Items.Single(i => i.WorkOrderNumber == "WO-C").WorkOrderClosed.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_ApprovedRow_CarriesDecidedByNameFromDecidingUser()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-A");
|
||||||
|
context.Users.Add(new ApplicationUser { Id = "user-9", FirstName = "Grace", LastName = "Hopper" });
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
|
||||||
|
{
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
Status = "Approved",
|
||||||
|
CreatedDate = new DateTime(2026, 3, 1),
|
||||||
|
DecidedAt = new DateTime(2026, 3, 2),
|
||||||
|
DecidedByUserId = "user-9",
|
||||||
|
RequiredTier = 1,
|
||||||
|
RequestedNTE = 300m,
|
||||||
|
NotificationStatus = "Pending"
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Approved", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Single().DecidedByName.Should().Be("Grace Hopper");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_AttachmentCount_CountsActiveDocumentsOnTheDispatch()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-A");
|
||||||
|
context.VendorCompletionDocuments.AddRange(
|
||||||
|
new VendorCompletionDocument
|
||||||
|
{
|
||||||
|
VendorId = vendor.Id,
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
WorkOrderId = workOrder.Id,
|
||||||
|
OriginalFileName = "quote.pdf",
|
||||||
|
StoredFileName = "a.bin",
|
||||||
|
ContentType = "application/pdf",
|
||||||
|
SizeBytes = 512,
|
||||||
|
ScanStatus = "Passed",
|
||||||
|
ReviewStatus = "Approved",
|
||||||
|
Purpose = "UpliftEvidence",
|
||||||
|
Version = 1
|
||||||
|
},
|
||||||
|
new VendorCompletionDocument
|
||||||
|
{
|
||||||
|
VendorId = vendor.Id,
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
WorkOrderId = workOrder.Id,
|
||||||
|
OriginalFileName = "photo.jpg",
|
||||||
|
StoredFileName = "b.bin",
|
||||||
|
ContentType = "image/jpeg",
|
||||||
|
SizeBytes = 2048,
|
||||||
|
ScanStatus = "Passed",
|
||||||
|
ReviewStatus = "Approved",
|
||||||
|
Purpose = "Completion",
|
||||||
|
Version = 1
|
||||||
|
},
|
||||||
|
new VendorCompletionDocument
|
||||||
|
{
|
||||||
|
VendorId = vendor.Id,
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
WorkOrderId = workOrder.Id,
|
||||||
|
OriginalFileName = "old.pdf",
|
||||||
|
StoredFileName = "c.bin",
|
||||||
|
ContentType = "application/pdf",
|
||||||
|
SizeBytes = 128,
|
||||||
|
ScanStatus = "Passed",
|
||||||
|
ReviewStatus = "Approved",
|
||||||
|
Purpose = "Completion",
|
||||||
|
Version = 1,
|
||||||
|
IsDeleted = true
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
context.DispatchUpliftRequests.Add(Request(dispatch, "Pending", new DateTime(2026, 3, 2)));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Pending", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Single().AttachmentCount.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_PendingExposureTotal_SumsGrantedAmountsAcrossAllPendingRequests()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-A");
|
||||||
|
context.Users.Add(new ApplicationUser { Id = "user-7", FirstName = "Ada", LastName = "Smith" });
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
context.DispatchUpliftRequests.AddRange(
|
||||||
|
// Vendor-portal rows (createdby null) store the requested new NTE total,
|
||||||
|
// so the pending exposure is the delta: 400 - 100 = 300 and 600 - 250 = 350.
|
||||||
|
Request(dispatch, "Pending", new DateTime(2026, 3, 1), requested: 400m, currentNte: 100m),
|
||||||
|
Request(dispatch, "Pending", new DateTime(2026, 3, 2), requested: 600m, currentNte: 250m),
|
||||||
|
// Work-order-path rows (createdby set) store the requested increment: 90.
|
||||||
|
Request(dispatch, "Pending", new DateTime(2026, 3, 3), requested: 90m, currentNte: 600m, createdBy: "user-7"),
|
||||||
|
// Non-pending rows never add pending exposure.
|
||||||
|
Request(dispatch, "Approved", new DateTime(2026, 2, 1), decided: new DateTime(2026, 2, 2), requested: 500m, currentNte: 100m));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
// Page 1 with a single row still reports the total across ALL pending rows.
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Pending", null, 1, 1, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Should().HaveCount(1);
|
||||||
|
result.PendingExposureTotal.Should().Be(740m);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Approved-on-WO exposure totals ---
|
// --- Approved-on-WO exposure totals ---
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -431,6 +431,7 @@ public class WorkOrderBoardCancelServiceTests
|
||||||
public Task<(int TotalCount, IReadOnlyList<UpliftListItemData> Items)> GetPagedAsync(
|
public Task<(int TotalCount, IReadOnlyList<UpliftListItemData> Items)> GetPagedAsync(
|
||||||
string? status, int? tier, int page, int pageSize, CancellationToken cancellationToken) =>
|
string? status, int? tier, int page, int pageSize, CancellationToken cancellationToken) =>
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
|
public Task<decimal> GetPendingExposureAsync(CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<IReadOnlyList<UpliftForDispatchData>> GetForDispatchAsync(int dispatchId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<IReadOnlyList<UpliftForDispatchData>> GetForDispatchAsync(int dispatchId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<IReadOnlyList<UpliftForWorkOrderData>> GetForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<IReadOnlyList<UpliftForWorkOrderData>> GetForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<DispatchUpliftRequest?> GetByIdAndWorkOrderAsync(int requestId, int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<DispatchUpliftRequest?> GetByIdAndWorkOrderAsync(int requestId, int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue