diff --git a/Api.SeaHavenIndustries.Tests/UpliftWorkflowTests.cs b/Api.SeaHavenIndustries.Tests/UpliftWorkflowTests.cs index 4518910..87f07cf 100644 --- a/Api.SeaHavenIndustries.Tests/UpliftWorkflowTests.cs +++ b/Api.SeaHavenIndustries.Tests/UpliftWorkflowTests.cs @@ -419,6 +419,44 @@ public sealed class UpliftWorkflowTests context.WorkOrderAuditLogs.Should().Contain(a => a.Action == "uplift_revised"); } + [Fact] + public async Task Revise_WorkOrderRequest_IsRefusedAsNotFound_AndLeavesTheRowUnchanged() + { + using var context = NewContext(); + var (vendor, workOrder, dispatch) = await SeedAsync(context, nte: 600m); + context.VendorCompletionDocuments.Add(EvidenceDocument(vendor.Id, dispatch.Id, workOrder.Id)); + // Raised from the work order by an internal user and sent back for changes: + // RequestedNTE holds the 90 increase, not a total. + var workOrderRequest = new DispatchUpliftRequest + { + DispatchId = dispatch.Id, + CurrentNTE = 600m, + RequestedNTE = 90m, + Status = UpliftStatus.ChangesRequested, + RequiredTier = 1, + VendorReason = "Extra parts", + RequestedByVendorName = "Alex Dispatcher", + NotificationStatus = "Sent", + createdby = "dispatcher-1", + CreatedDate = new DateTime(2026, 3, 1), + }; + context.DispatchUpliftRequests.Add(workOrderRequest); + await context.SaveChangesAsync(); + var service = NewPortalService(context, new FakeEmailSender(deliver: true)); + var session = await service.ResolveSessionAsync(Token, CancellationToken.None); + + var act = () => service.ReviseUpliftAsync(session!, dispatch.Id, workOrderRequest.Id, 900m, "vendor total", 1, CancellationToken.None); + + await act.Should().ThrowAsync(); + var after = context.DispatchUpliftRequests.AsNoTracking().Single(u => u.Id == workOrderRequest.Id); + after.Status.Should().Be(UpliftStatus.ChangesRequested); + after.RequestedNTE.Should().Be(90m); + after.CurrentNTE.Should().Be(600m); + after.VendorReason.Should().Be("Extra parts"); + after.createdby.Should().Be("dispatcher-1"); + context.WorkOrderAuditLogs.Should().NotContain(a => a.Action == "uplift_revised"); + } + [Fact] public async Task Withdraw_Pending_TransitionsToWithdrawn_SetsDecidedAt() { diff --git a/SeaHaven.Services/Implementation/VendorPortalService.cs b/SeaHaven.Services/Implementation/VendorPortalService.cs index c595809..047abd0 100644 --- a/SeaHaven.Services/Implementation/VendorPortalService.cs +++ b/SeaHaven.Services/Implementation/VendorPortalService.cs @@ -736,7 +736,9 @@ namespace SeaHaven.Services.Implementation } var req = await _upliftData.GetByIdAndDispatchAsync(requestId, id, cancellationToken); - if (req == null) throw new KeyNotFoundException("Uplift request not found"); + // A vendor revises only requests it raised. Work-order requests (createdby set) + // store the increase, not a total, so a vendor revise would corrupt their amount. + if (req == null || req.createdby != null) throw new KeyNotFoundException("Uplift request not found"); if (UpliftStatus.ToCanonical(req.Status) != UpliftStatus.ChangesRequested) { diff --git a/SeaHavenIndustries.Tests/UpliftAmountPerCreationPathTests.cs b/SeaHavenIndustries.Tests/UpliftAmountPerCreationPathTests.cs index a378b60..11502ef 100644 --- a/SeaHavenIndustries.Tests/UpliftAmountPerCreationPathTests.cs +++ b/SeaHavenIndustries.Tests/UpliftAmountPerCreationPathTests.cs @@ -261,6 +261,21 @@ public sealed class UpliftAmountPerCreationPathTests .SumAutoApprovedAmountForWorkOrderAsync(WorkOrderId, CancellationToken.None)); } + [Fact] + public async Task WorkOrderUpliftList_ShowsEachRequestsIncrease() + { + await using var context = CreateContext(); + await SeedWorkOrderAsync(context); + var workOrderRequestId = await CreatePendingWorkOrderRequestAsync(context, 90m); + var vendorRequestId = await SeedVendorRequestAsync(context, SecondDispatchId, currentNte: 600m, requestedTotal: 900m); + + var uplifts = await NewWorkOrderUpliftService(context) + .ListAsync(WorkOrderId, Admin(), CancellationToken.None); + + Assert.Equal(90m, Assert.Single(uplifts!, u => u.Id == workOrderRequestId).Amount); + Assert.Equal(300m, Assert.Single(uplifts!, u => u.Id == vendorRequestId).Amount); + } + [Fact] public void AmountDefinition_TranslatesToSqlServer_ForRowAndGroupedSums() {