mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 16:19:07 +00:00
Merge remote-tracking branch 'origin/codex/backend-layering-refactor' into codex/backend-governance-quality-system
This commit is contained in:
commit
332b7e713e
1 changed files with 76 additions and 76 deletions
|
|
@ -33,98 +33,98 @@ namespace SeaHaven.Services.Implementation
|
|||
{
|
||||
await _mutationData.ExecuteTransactionalAsync(async ct =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Field))
|
||||
throw new WorkOrderBoardValidationException("InvalidField", "Field is required.");
|
||||
if (string.IsNullOrWhiteSpace(request.Field))
|
||||
throw new WorkOrderBoardValidationException("InvalidField", "Field is required.");
|
||||
|
||||
var field = request.Field.Trim();
|
||||
var canonicalField = WorkOrderBoardFieldNames.Canonicalize(field);
|
||||
if (canonicalField == null)
|
||||
throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.");
|
||||
var field = request.Field.Trim();
|
||||
var canonicalField = WorkOrderBoardFieldNames.Canonicalize(field);
|
||||
if (canonicalField == null)
|
||||
throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.");
|
||||
|
||||
var workOrderVersion = ParseRowVersion(request.WorkOrderVersion, "workOrderVersion");
|
||||
if (workOrderVersion == null)
|
||||
throw new WorkOrderBoardValidationException("WorkOrderVersionRequired", "workOrderVersion is required.");
|
||||
var workOrderVersion = ParseRowVersion(request.WorkOrderVersion, "workOrderVersion");
|
||||
if (workOrderVersion == null)
|
||||
throw new WorkOrderBoardValidationException("WorkOrderVersionRequired", "workOrderVersion is required.");
|
||||
|
||||
var dispatchVersion = ParseRowVersion(request.DispatchVersion, "dispatchVersion");
|
||||
var dispatchVersion = ParseRowVersion(request.DispatchVersion, "dispatchVersion");
|
||||
|
||||
var workOrder = await _mutationData.GetTrackedWorkOrderAsync(workOrderId, ct);
|
||||
var workOrder = await _mutationData.GetTrackedWorkOrderAsync(workOrderId, ct);
|
||||
|
||||
if (workOrder == null)
|
||||
throw new WorkOrderBoardValidationException("NotFound", "Work order not found.");
|
||||
if (workOrder == null)
|
||||
throw new WorkOrderBoardValidationException("NotFound", "Work order not found.");
|
||||
|
||||
if (!RowVersionsMatch(workOrder.RowVersion, workOrderVersion))
|
||||
{
|
||||
var staleState = await LoadBoardRowAsync(workOrderId);
|
||||
throw new WorkOrderBoardConcurrencyException(staleState);
|
||||
}
|
||||
|
||||
if (WorkOrderBoardMutationRules.IsReadOnly(workOrder.LifecycleStatus))
|
||||
throw new WorkOrderBoardValidationException("CanceledReadOnly", "Work order is read-only in its current status.");
|
||||
|
||||
var isPastDue = WorkOrderDerivedFields.IsPastDue(workOrder.ScheduledDate, workOrder.LifecycleStatus);
|
||||
if (WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(canonicalField, isPastDue))
|
||||
throw new WorkOrderBoardValidationException("PastDueStatusBlocked", "Status cannot be changed while work order is past due. Reschedule first.");
|
||||
|
||||
_mutationData.SetExpectedWorkOrderVersion(workOrder, workOrderVersion);
|
||||
|
||||
Dispatch? dispatch = null;
|
||||
if (WorkOrderBoardFieldNames.IsDispatchField(canonicalField))
|
||||
{
|
||||
var vendorIdHint = canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase)
|
||||
? ParseVendorIdHint(request.Value)
|
||||
: null;
|
||||
|
||||
var hadPrimaryDispatch = workOrder.PrimaryDispatchId.HasValue;
|
||||
dispatch = await ResolveDispatchAsync(workOrder, request.PrimaryDispatchId, vendorIdHint, ct);
|
||||
|
||||
if (dispatchVersion != null && !RowVersionsMatch(dispatch.RowVersion, dispatchVersion))
|
||||
if (!RowVersionsMatch(workOrder.RowVersion, workOrderVersion))
|
||||
{
|
||||
var staleState = await LoadBoardRowAsync(workOrderId);
|
||||
throw new WorkOrderBoardConcurrencyException(staleState);
|
||||
}
|
||||
|
||||
if (dispatchVersion != null)
|
||||
_mutationData.SetExpectedDispatchVersion(dispatch, dispatchVersion);
|
||||
else if (hadPrimaryDispatch && dispatch.RowVersion != null)
|
||||
throw new WorkOrderBoardValidationException("DispatchVersionRequired", "dispatchVersion is required for dispatch fields.");
|
||||
}
|
||||
if (WorkOrderBoardMutationRules.IsReadOnly(workOrder.LifecycleStatus))
|
||||
throw new WorkOrderBoardValidationException("CanceledReadOnly", "Work order is read-only in its current status.");
|
||||
|
||||
var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField);
|
||||
var changes = await ApplyFieldMutationAsync(canonicalField, workOrder, dispatch, request.Value, auditField);
|
||||
changes = changes.Where(c => c.HasChanged).ToList();
|
||||
var isPastDue = WorkOrderDerivedFields.IsPastDue(workOrder.ScheduledDate, workOrder.LifecycleStatus);
|
||||
if (WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(canonicalField, isPastDue))
|
||||
throw new WorkOrderBoardValidationException("PastDueStatusBlocked", "Status cannot be changed while work order is past due. Reschedule first.");
|
||||
|
||||
if (changes.Count == 0 && !_mutationData.HasPendingChanges())
|
||||
return;
|
||||
_mutationData.SetExpectedWorkOrderVersion(workOrder, workOrderVersion);
|
||||
|
||||
if (changes.Count == 0)
|
||||
{
|
||||
await _mutationData.SaveAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var change in changes)
|
||||
{
|
||||
var dispatchId = change.DispatchId ?? (dispatch?.Id > 0 ? dispatch.Id : null);
|
||||
switch (change.Action)
|
||||
Dispatch? dispatch = null;
|
||||
if (WorkOrderBoardFieldNames.IsDispatchField(canonicalField))
|
||||
{
|
||||
case AuditActionType.StatusChanged:
|
||||
await _auditService.StageStatusChangedAsync(workOrderId, change.OldValue, change.NewValue, actorId);
|
||||
break;
|
||||
case AuditActionType.AssignmentChanged:
|
||||
await _auditService.StageAssignmentChangedAsync(workOrderId, change.OldValue, change.NewValue, actorId);
|
||||
break;
|
||||
default:
|
||||
await _auditService.StageFieldChangedAsync(workOrderId, change.FieldName, change.OldValue, change.NewValue, actorId, dispatchId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var vendorIdHint = canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase)
|
||||
? ParseVendorIdHint(request.Value)
|
||||
: null;
|
||||
|
||||
var outcome = await _mutationData.SaveAsync(ct);
|
||||
if (outcome == BoardSaveOutcome.ConcurrencyConflict)
|
||||
{
|
||||
var currentState = await LoadBoardRowAsync(workOrderId);
|
||||
throw new WorkOrderBoardConcurrencyException(currentState);
|
||||
}
|
||||
var hadPrimaryDispatch = workOrder.PrimaryDispatchId.HasValue;
|
||||
dispatch = await ResolveDispatchAsync(workOrder, request.PrimaryDispatchId, vendorIdHint, ct);
|
||||
|
||||
if (dispatchVersion != null && !RowVersionsMatch(dispatch.RowVersion, dispatchVersion))
|
||||
{
|
||||
var staleState = await LoadBoardRowAsync(workOrderId);
|
||||
throw new WorkOrderBoardConcurrencyException(staleState);
|
||||
}
|
||||
|
||||
if (dispatchVersion != null)
|
||||
_mutationData.SetExpectedDispatchVersion(dispatch, dispatchVersion);
|
||||
else if (hadPrimaryDispatch && dispatch.RowVersion != null)
|
||||
throw new WorkOrderBoardValidationException("DispatchVersionRequired", "dispatchVersion is required for dispatch fields.");
|
||||
}
|
||||
|
||||
var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField);
|
||||
var changes = await ApplyFieldMutationAsync(canonicalField, workOrder, dispatch, request.Value, auditField);
|
||||
changes = changes.Where(c => c.HasChanged).ToList();
|
||||
|
||||
if (changes.Count == 0 && !_mutationData.HasPendingChanges())
|
||||
return;
|
||||
|
||||
if (changes.Count == 0)
|
||||
{
|
||||
await _mutationData.SaveAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var change in changes)
|
||||
{
|
||||
var dispatchId = change.DispatchId ?? (dispatch?.Id > 0 ? dispatch.Id : null);
|
||||
switch (change.Action)
|
||||
{
|
||||
case AuditActionType.StatusChanged:
|
||||
await _auditService.StageStatusChangedAsync(workOrderId, change.OldValue, change.NewValue, actorId);
|
||||
break;
|
||||
case AuditActionType.AssignmentChanged:
|
||||
await _auditService.StageAssignmentChangedAsync(workOrderId, change.OldValue, change.NewValue, actorId);
|
||||
break;
|
||||
default:
|
||||
await _auditService.StageFieldChangedAsync(workOrderId, change.FieldName, change.OldValue, change.NewValue, actorId, dispatchId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var outcome = await _mutationData.SaveAsync(ct);
|
||||
if (outcome == BoardSaveOutcome.ConcurrencyConflict)
|
||||
{
|
||||
var currentState = await LoadBoardRowAsync(workOrderId);
|
||||
throw new WorkOrderBoardConcurrencyException(currentState);
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
|
||||
var row = await LoadBoardRowAsync(workOrderId);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue