diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs index 0da5f96..7e9fce2 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs @@ -69,6 +69,7 @@ namespace SeaHaven.Services.Implementation _mutationData.SetExpectedWorkOrderVersion(workOrder, workOrderVersion); Dispatch? dispatch = null; + ResolvedDispatch? resolved = null; if (WorkOrderBoardFieldNames.IsDispatchField(canonicalField)) { var vendorIdHint = canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase) @@ -77,7 +78,7 @@ namespace SeaHaven.Services.Implementation var hadPrimaryDispatch = workOrder.PrimaryDispatchId.HasValue; var replaceInactivePrimary = vendorIdHint.HasValue; - var resolved = await ResolveDispatchAsync( + resolved = await ResolveDispatchAsync( workOrder, request.PrimaryDispatchId, vendorIdHint, @@ -108,14 +109,30 @@ namespace SeaHaven.Services.Implementation var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField); var changes = await ApplyFieldMutationAsync(canonicalField, workOrder, dispatch, request.Value, auditField); - changes = changes.Where(c => c.HasChanged).ToList(); + if (resolved is { Created: true, Dispatch: var createdDispatch } + && canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase)) + { + var previousVendor = resolved.Previous?.VendorId.ToString() ?? ""; + changes = new List + { + FieldChange.ForField( + auditField, + previousVendor, + createdDispatch.VendorId.ToString(), + createdDispatch.Id) + }; + } + else + { + changes = changes.Where(c => c.HasChanged).ToList(); + } if (changes.Count == 0 && !_mutationData.HasPendingChanges()) return; if (changes.Count == 0) { - await _mutationData.SaveAsync(ct); + await SaveTrackedOrThrowAsync(workOrderId, ct); return; } @@ -136,12 +153,7 @@ namespace SeaHaven.Services.Implementation } } - var outcome = await _mutationData.SaveAsync(ct); - if (outcome == BoardSaveOutcome.ConcurrencyConflict) - { - var currentState = await LoadBoardRowAsync(workOrderId); - throw new WorkOrderBoardConcurrencyException(currentState); - } + await SaveTrackedOrThrowAsync(workOrderId, ct); }, CancellationToken.None); var row = await LoadBoardRowAsync(workOrderId); @@ -211,7 +223,7 @@ namespace SeaHaven.Services.Implementation CreatedDate = DateTime.UtcNow }; _mutationData.TrackNewDispatch(created); - await _mutationData.SaveAsync(cancellationToken); + await SaveTrackedOrThrowAsync(workOrder.Id, cancellationToken); workOrder.PrimaryDispatchId = created.Id; return new ResolvedDispatch(created, Created: true, Previous: existing); } @@ -250,6 +262,16 @@ namespace SeaHaven.Services.Implementation throw new WorkOrderBoardValidationException("VendorNotFound", "vendorId does not exist."); } + private async Task SaveTrackedOrThrowAsync(int workOrderId, CancellationToken cancellationToken) + { + var outcome = await _mutationData.SaveAsync(cancellationToken); + if (outcome != BoardSaveOutcome.ConcurrencyConflict) + return; + + var currentState = await LoadBoardRowAsync(workOrderId); + throw new WorkOrderBoardConcurrencyException(currentState); + } + private async Task ApplyWoNumber(WorkOrder workOrder, string? value, string auditField) { if (!WorkOrderNumberNormalizer.TryNormalize(value, out var normalized, out var error)) diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs index 7bb6f9b..edf9487 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs @@ -380,6 +380,15 @@ public class WorkOrderBoardUpdateServiceTests var leftover = await context.Dispatches.SingleAsync(d => d.Id == 10); Assert.Equal(1, leftover.VendorId); Assert.Equal(inactiveStatus, leftover.Status); + + var vendorAudit = Assert.Single( + await context.WorkOrderAuditLogs + .Where(a => a.Action == AuditActionType.FieldChanged.ToString() && a.FieldName == "VendorId") + .ToListAsync()); + Assert.Equal("1", vendorAudit.OldValue); + Assert.Equal("5", vendorAudit.NewValue); + Assert.Equal(result.PrimaryDispatchId, vendorAudit.DispatchId); + Assert.True(await context.WorkOrderFieldLocks.AnyAsync(l => l.FieldName == "VendorId")); } [Theory]