mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
fix(work-orders): audit and lock vendor when replacing an inactive primary
Creating a Pending dispatch now stages VendorId FieldChanged from the previous assignment so field lock and concurrency checks run like a live vendor PATCH.
This commit is contained in:
parent
af593fe2d2
commit
14443aff18
2 changed files with 41 additions and 10 deletions
|
|
@ -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>
|
||||
{
|
||||
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<FieldChange> ApplyWoNumber(WorkOrder workOrder, string? value, string auditField)
|
||||
{
|
||||
if (!WorkOrderNumberNormalizer.TryNormalize(value, out var normalized, out var error))
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue