mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
Merge pull request #100 from Sea-Haven-Industries/fix/sh-296-vendor-invalid-dispatch
fix(work-orders): accept linked primary dispatch on vendor board patch
This commit is contained in:
commit
5f6069547d
5 changed files with 478 additions and 16 deletions
|
|
@ -25,12 +25,57 @@ namespace SeaHaven.DataServices.Implementation
|
|||
cancellationToken);
|
||||
|
||||
public Task<Dispatch?> GetTrackedDispatchAsync(int dispatchId, int workOrderId, CancellationToken cancellationToken)
|
||||
=> _context.Dispatches.FirstOrDefaultAsync(
|
||||
d => d.Id == dispatchId && d.WorkOrderId == workOrderId,
|
||||
cancellationToken);
|
||||
=> _context.Dispatches
|
||||
.Include(d => d.DispatchWorkOrders)
|
||||
.FirstOrDefaultAsync(
|
||||
d => d.Id == dispatchId
|
||||
&& (d.WorkOrderId == workOrderId
|
||||
|| d.DispatchWorkOrders!.Any(link => link.WorkOrderId == workOrderId)),
|
||||
cancellationToken);
|
||||
|
||||
public Task<Dispatch?> GetTrackedDispatchByIdAsync(int dispatchId, CancellationToken cancellationToken)
|
||||
=> _context.Dispatches.FirstOrDefaultAsync(d => d.Id == dispatchId, cancellationToken);
|
||||
=> _context.Dispatches
|
||||
.Include(d => d.DispatchWorkOrders)
|
||||
.FirstOrDefaultAsync(d => d.Id == dispatchId, cancellationToken);
|
||||
|
||||
public void DetachWorkOrderFromDispatch(Dispatch dispatch, int workOrderId)
|
||||
{
|
||||
var links = dispatch.DispatchWorkOrders?
|
||||
.Where(link => link.WorkOrderId == workOrderId)
|
||||
.ToList();
|
||||
if (links == null || links.Count == 0)
|
||||
{
|
||||
links = _context.DispatchWorkOrders
|
||||
.Where(link => link.DispatchId == dispatch.Id && link.WorkOrderId == workOrderId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
foreach (var link in links)
|
||||
_context.DispatchWorkOrders.Remove(link);
|
||||
|
||||
if (dispatch.WorkOrderId != workOrderId)
|
||||
return;
|
||||
|
||||
var remainingIds = (dispatch.DispatchWorkOrders ?? Enumerable.Empty<DispatchWorkOrder>())
|
||||
.Where(link => link.WorkOrderId != workOrderId)
|
||||
.Select(link => link.WorkOrderId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (remainingIds.Count == 0)
|
||||
{
|
||||
dispatch.WorkOrderId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var preferredOwnerId = _context.workOrders
|
||||
.Where(workOrder => remainingIds.Contains(workOrder.Id)
|
||||
&& workOrder.PrimaryDispatchId == dispatch.Id)
|
||||
.Select(workOrder => (int?)workOrder.Id)
|
||||
.FirstOrDefault();
|
||||
|
||||
dispatch.WorkOrderId = preferredOwnerId ?? remainingIds[0];
|
||||
}
|
||||
|
||||
public void TrackNewWorkOrder(WorkOrder workOrder)
|
||||
=> _context.workOrders.Add(workOrder);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
Task<WorkOrder?> GetTrackedWorkOrderAsync(int workOrderId, CancellationToken cancellationToken);
|
||||
Task<Dispatch?> GetTrackedDispatchAsync(int dispatchId, int workOrderId, CancellationToken cancellationToken);
|
||||
Task<Dispatch?> GetTrackedDispatchByIdAsync(int dispatchId, CancellationToken cancellationToken);
|
||||
void DetachWorkOrderFromDispatch(Dispatch dispatch, int workOrderId);
|
||||
void TrackNewWorkOrder(WorkOrder workOrder);
|
||||
void TrackNewDispatch(Dispatch dispatch);
|
||||
void TrackWorkOrderContact(int workOrderId, int contactId, string? notes);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ namespace SeaHaven.Services.Implementation
|
|||
request.PrimaryDispatchId,
|
||||
vendorIdHint,
|
||||
replaceInactivePrimary,
|
||||
relaxInvalidDispatch: vendorIdHint.HasValue,
|
||||
ct);
|
||||
dispatch = resolved.Dispatch;
|
||||
|
||||
|
|
@ -110,6 +111,9 @@ namespace SeaHaven.Services.Implementation
|
|||
"DispatchVersionRequired",
|
||||
"dispatchVersion is required for dispatch fields.");
|
||||
}
|
||||
|
||||
if (resolved is { Created: true, Previous: { } previousDispatch })
|
||||
_mutationData.DetachWorkOrderFromDispatch(previousDispatch, workOrder.Id);
|
||||
}
|
||||
|
||||
var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField);
|
||||
|
|
@ -206,12 +210,22 @@ namespace SeaHaven.Services.Implementation
|
|||
int? primaryDispatchId,
|
||||
int? vendorIdForCreate,
|
||||
bool replaceInactivePrimary,
|
||||
bool relaxInvalidDispatch,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var existing = await LoadExistingDispatchAsync(workOrder, primaryDispatchId, cancellationToken);
|
||||
if (existing != null
|
||||
&& !(replaceInactivePrimary
|
||||
&& WorkOrderBoardDispatchAssignment.IsInactivePrimaryDispatch(existing.Status)))
|
||||
var existing = await LoadExistingDispatchAsync(
|
||||
workOrder,
|
||||
primaryDispatchId,
|
||||
relaxInvalidDispatch,
|
||||
cancellationToken);
|
||||
var vendorChanged = existing != null
|
||||
&& vendorIdForCreate.HasValue
|
||||
&& existing.VendorId != vendorIdForCreate.Value;
|
||||
var mustReplace = existing != null
|
||||
&& replaceInactivePrimary
|
||||
&& (WorkOrderBoardDispatchAssignment.IsInactivePrimaryDispatch(existing.Status)
|
||||
|| (IsSharedDispatch(existing, workOrder.Id) && vendorChanged));
|
||||
if (existing != null && !mustReplace)
|
||||
{
|
||||
workOrder.PrimaryDispatchId = existing.Id;
|
||||
return new ResolvedDispatch(existing, Created: false, Previous: null);
|
||||
|
|
@ -238,29 +252,60 @@ namespace SeaHaven.Services.Implementation
|
|||
private async Task<Dispatch?> LoadExistingDispatchAsync(
|
||||
WorkOrder workOrder,
|
||||
int? primaryDispatchId,
|
||||
bool relaxInvalidDispatch,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (primaryDispatchId.HasValue)
|
||||
var clientId = NormalizeClientPrimaryDispatchId(primaryDispatchId);
|
||||
if (clientId.HasValue)
|
||||
{
|
||||
var dispatch = await _mutationData.GetTrackedDispatchAsync(
|
||||
primaryDispatchId.Value,
|
||||
clientId.Value,
|
||||
workOrder.Id,
|
||||
cancellationToken);
|
||||
|
||||
if (dispatch == null)
|
||||
if (dispatch != null)
|
||||
return dispatch;
|
||||
|
||||
if (!relaxInvalidDispatch)
|
||||
throw new WorkOrderBoardValidationException(
|
||||
"InvalidDispatch",
|
||||
"Primary dispatch does not belong to this work order.");
|
||||
|
||||
return dispatch;
|
||||
}
|
||||
|
||||
if (!workOrder.PrimaryDispatchId.HasValue)
|
||||
var storedId = NormalizeClientPrimaryDispatchId(workOrder.PrimaryDispatchId);
|
||||
if (!storedId.HasValue)
|
||||
return null;
|
||||
|
||||
return await _mutationData.GetTrackedDispatchByIdAsync(
|
||||
workOrder.PrimaryDispatchId.Value,
|
||||
var stored = await _mutationData.GetTrackedDispatchByIdAsync(
|
||||
storedId.Value,
|
||||
cancellationToken);
|
||||
if (stored == null || !DispatchBelongsToWorkOrder(stored, workOrder.Id))
|
||||
return null;
|
||||
|
||||
return stored;
|
||||
}
|
||||
|
||||
private static int? NormalizeClientPrimaryDispatchId(int? primaryDispatchId)
|
||||
=> primaryDispatchId is > 0 ? primaryDispatchId : null;
|
||||
|
||||
private static bool DispatchBelongsToWorkOrder(Dispatch dispatch, int workOrderId)
|
||||
=> dispatch.WorkOrderId == workOrderId
|
||||
|| dispatch.DispatchWorkOrders?.Any(link => link.WorkOrderId == workOrderId) == true;
|
||||
|
||||
private static bool IsSharedDispatch(Dispatch dispatch, int workOrderId)
|
||||
{
|
||||
if (dispatch.WorkOrderId.HasValue && dispatch.WorkOrderId.Value != workOrderId)
|
||||
return true;
|
||||
|
||||
var linkedIds = dispatch.DispatchWorkOrders?
|
||||
.Select(link => link.WorkOrderId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (linkedIds == null || linkedIds.Count == 0)
|
||||
return false;
|
||||
if (linkedIds.Count > 1)
|
||||
return true;
|
||||
return dispatch.WorkOrderId.HasValue && linkedIds[0] != dispatch.WorkOrderId.Value;
|
||||
}
|
||||
|
||||
private async Task EnsureVendorExistsAsync(int vendorId)
|
||||
|
|
|
|||
|
|
@ -392,6 +392,9 @@ public class WorkOrderBoardCancelServiceTests
|
|||
public Task<Dispatch?> GetTrackedDispatchByIdAsync(int dispatchId, CancellationToken cancellationToken)
|
||||
=> _inner.GetTrackedDispatchByIdAsync(dispatchId, cancellationToken);
|
||||
|
||||
public void DetachWorkOrderFromDispatch(Dispatch dispatch, int workOrderId)
|
||||
=> _inner.DetachWorkOrderFromDispatch(dispatch, workOrderId);
|
||||
|
||||
public void TrackNewWorkOrder(WorkOrder workOrder) => _inner.TrackNewWorkOrder(workOrder);
|
||||
|
||||
public void TrackNewDispatch(Dispatch dispatch) => _inner.TrackNewDispatch(dispatch);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,27 @@ public class WorkOrderBoardUpdateServiceTests
|
|||
private static string ToVersion(Dispatch dispatch)
|
||||
=> Convert.ToBase64String(dispatch.RowVersion ?? new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 });
|
||||
|
||||
private static Task<int[]> DispatchIdsForWorkOrderAsync(ApplicationDbContext context, int workOrderId)
|
||||
=> context.Dispatches
|
||||
.Where(dispatch =>
|
||||
dispatch.WorkOrderId == workOrderId
|
||||
|| context.DispatchWorkOrders.Any(link =>
|
||||
link.DispatchId == dispatch.Id && link.WorkOrderId == workOrderId))
|
||||
.Select(dispatch => dispatch.Id)
|
||||
.OrderBy(id => id)
|
||||
.ToArrayAsync();
|
||||
|
||||
private static Task<bool> UpliftAssociatedAsync(
|
||||
ApplicationDbContext context,
|
||||
int workOrderId,
|
||||
int dispatchId)
|
||||
=> context.DispatchUpliftRequests.AnyAsync(uplift =>
|
||||
uplift.DispatchId == dispatchId
|
||||
&& (context.Dispatches.Any(dispatch =>
|
||||
dispatch.Id == dispatchId && dispatch.WorkOrderId == workOrderId)
|
||||
|| context.DispatchWorkOrders.Any(link =>
|
||||
link.DispatchId == dispatchId && link.WorkOrderId == workOrderId)));
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_AutoSchedulesIncompleteToScheduled()
|
||||
{
|
||||
|
|
@ -483,6 +504,353 @@ public class WorkOrderBoardUpdateServiceTests
|
|||
Assert.Equal(liveStatus, updated.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_VendorId_SucceedsWhenClientPrimaryDispatchIdDoesNotBelong()
|
||||
{
|
||||
var (context, service) = CreateSut();
|
||||
context.Vendors.AddRange(
|
||||
new Vendor { Id = 1, CompanyName = "Old Co" },
|
||||
new Vendor { Id = 5, CompanyName = "New Co" });
|
||||
var ownDispatch = new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
WorkOrderId = 1,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||
};
|
||||
var foreignDispatch = new Dispatch
|
||||
{
|
||||
Id = 99,
|
||||
WorkOrderId = 2,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 3 }
|
||||
};
|
||||
var wo = new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||
};
|
||||
var otherWo = new WorkOrder
|
||||
{
|
||||
Id = 2,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 99,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 4 }
|
||||
};
|
||||
context.Dispatches.AddRange(ownDispatch, foreignDispatch);
|
||||
context.workOrders.AddRange(wo, otherWo);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||
{
|
||||
Field = WorkOrderBoardFieldNames.VendorId,
|
||||
Value = "5",
|
||||
WorkOrderVersion = ToVersion(wo),
|
||||
DispatchVersion = ToVersion(ownDispatch),
|
||||
PrimaryDispatchId = 99
|
||||
}, "actor-1");
|
||||
|
||||
Assert.Equal(5, result.VendorId);
|
||||
Assert.Equal(10, result.PrimaryDispatchId);
|
||||
var leftoverForeign = await context.Dispatches.SingleAsync(d => d.Id == 99);
|
||||
Assert.Equal(1, leftoverForeign.VendorId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_VendorId_TreatsZeroPrimaryDispatchIdAsOmitted()
|
||||
{
|
||||
var (context, service) = CreateSut();
|
||||
context.Vendors.AddRange(
|
||||
new Vendor { Id = 1, CompanyName = "Old Co" },
|
||||
new Vendor { Id = 5, CompanyName = "New Co" });
|
||||
var dispatch = new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
WorkOrderId = 1,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||
};
|
||||
var wo = new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||
};
|
||||
context.Dispatches.Add(dispatch);
|
||||
context.workOrders.Add(wo);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||
{
|
||||
Field = WorkOrderBoardFieldNames.VendorId,
|
||||
Value = "5",
|
||||
WorkOrderVersion = ToVersion(wo),
|
||||
DispatchVersion = ToVersion(dispatch),
|
||||
PrimaryDispatchId = 0
|
||||
}, "actor-1");
|
||||
|
||||
Assert.Equal(5, result.VendorId);
|
||||
Assert.Equal(10, result.PrimaryDispatchId);
|
||||
Assert.Equal(1, await context.Dispatches.CountAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_VendorId_SharedDispatch_CreatesWoOwnedDispatch()
|
||||
{
|
||||
var (context, service) = CreateSut();
|
||||
context.Vendors.AddRange(
|
||||
new Vendor { Id = 1, CompanyName = "Old Co" },
|
||||
new Vendor { Id = 5, CompanyName = "New Co" });
|
||||
var shared = new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
WorkOrderId = 1,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||
};
|
||||
var primaryWo = new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||
};
|
||||
var secondaryWo = new WorkOrder
|
||||
{
|
||||
Id = 2,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 5 }
|
||||
};
|
||||
context.Dispatches.Add(shared);
|
||||
context.workOrders.AddRange(primaryWo, secondaryWo);
|
||||
context.DispatchWorkOrders.AddRange(
|
||||
new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 1 },
|
||||
new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 2 });
|
||||
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
|
||||
{
|
||||
Id = 200,
|
||||
DispatchId = 10,
|
||||
RequestedNTE = 900m,
|
||||
Status = "Pending",
|
||||
RequiredTier = 1,
|
||||
NotificationStatus = "Pending"
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await service.PatchFieldAsync(2, new WorkOrderBoardPatchRequestDto
|
||||
{
|
||||
Field = WorkOrderBoardFieldNames.VendorId,
|
||||
Value = "5",
|
||||
WorkOrderVersion = ToVersion(secondaryWo),
|
||||
DispatchVersion = ToVersion(shared),
|
||||
PrimaryDispatchId = 10
|
||||
}, "actor-1");
|
||||
|
||||
Assert.Equal(5, result.VendorId);
|
||||
Assert.NotEqual(10, result.PrimaryDispatchId);
|
||||
Assert.Equal("Pending", result.PrimaryDispatchStatus);
|
||||
var original = await context.Dispatches.SingleAsync(d => d.Id == 10);
|
||||
Assert.Equal(1, original.VendorId);
|
||||
Assert.Equal("Sent", original.Status);
|
||||
Assert.Equal(1, original.WorkOrderId);
|
||||
var created = await context.Dispatches.SingleAsync(d => d.Id == result.PrimaryDispatchId);
|
||||
Assert.Equal(2, created.WorkOrderId);
|
||||
Assert.Equal(5, created.VendorId);
|
||||
var primaryUnchanged = await context.workOrders.SingleAsync(w => w.Id == 1);
|
||||
Assert.Equal(10, primaryUnchanged.PrimaryDispatchId);
|
||||
Assert.False(await context.DispatchWorkOrders.AnyAsync(link =>
|
||||
link.DispatchId == 10 && link.WorkOrderId == 2));
|
||||
Assert.True(await context.DispatchWorkOrders.AnyAsync(link =>
|
||||
link.DispatchId == 10 && link.WorkOrderId == 1));
|
||||
var secondaryDispatchIds = await DispatchIdsForWorkOrderAsync(context, 2);
|
||||
var primaryDispatchIds = await DispatchIdsForWorkOrderAsync(context, 1);
|
||||
Assert.Equal(new int[] { result.PrimaryDispatchId!.Value }, secondaryDispatchIds);
|
||||
Assert.Equal(new int[] { 10 }, primaryDispatchIds);
|
||||
Assert.False(await UpliftAssociatedAsync(context, 2, 10));
|
||||
Assert.True(await UpliftAssociatedAsync(context, 1, 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_VendorId_SharedDispatch_OwnerChange_RehomesDirectOwnership()
|
||||
{
|
||||
var (context, service) = CreateSut();
|
||||
context.Vendors.AddRange(
|
||||
new Vendor { Id = 1, CompanyName = "Old Co" },
|
||||
new Vendor { Id = 5, CompanyName = "New Co" });
|
||||
var shared = new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
WorkOrderId = 1,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||
};
|
||||
var primaryWo = new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||
};
|
||||
var secondaryWo = new WorkOrder
|
||||
{
|
||||
Id = 2,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 5 }
|
||||
};
|
||||
context.Dispatches.Add(shared);
|
||||
context.workOrders.AddRange(primaryWo, secondaryWo);
|
||||
context.DispatchWorkOrders.AddRange(
|
||||
new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 1 },
|
||||
new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 2 });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||
{
|
||||
Field = WorkOrderBoardFieldNames.VendorId,
|
||||
Value = "5",
|
||||
WorkOrderVersion = ToVersion(primaryWo),
|
||||
DispatchVersion = ToVersion(shared),
|
||||
PrimaryDispatchId = 10
|
||||
}, "actor-1");
|
||||
|
||||
Assert.Equal(5, result.VendorId);
|
||||
Assert.NotEqual(10, result.PrimaryDispatchId);
|
||||
var original = await context.Dispatches.SingleAsync(d => d.Id == 10);
|
||||
Assert.Equal(1, original.VendorId);
|
||||
Assert.Equal(2, original.WorkOrderId);
|
||||
Assert.False(await context.DispatchWorkOrders.AnyAsync(link =>
|
||||
link.DispatchId == 10 && link.WorkOrderId == 1));
|
||||
Assert.True(await context.DispatchWorkOrders.AnyAsync(link =>
|
||||
link.DispatchId == 10 && link.WorkOrderId == 2));
|
||||
var secondaryUnchanged = await context.workOrders.SingleAsync(w => w.Id == 2);
|
||||
Assert.Equal(10, secondaryUnchanged.PrimaryDispatchId);
|
||||
var ownerDispatchIds = await DispatchIdsForWorkOrderAsync(context, 1);
|
||||
var remainingDispatchIds = await DispatchIdsForWorkOrderAsync(context, 2);
|
||||
Assert.Equal(new int[] { result.PrimaryDispatchId!.Value }, ownerDispatchIds);
|
||||
Assert.Equal(new int[] { 10 }, remainingDispatchIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_VendorId_SharedDispatch_SameVendor_IsIdempotent()
|
||||
{
|
||||
var (context, service) = CreateSut();
|
||||
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Old Co" });
|
||||
var appointment = new DateTime(2026, 6, 26);
|
||||
var shared = new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
WorkOrderId = 1,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
ScheduledDate = appointment,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||
};
|
||||
var primaryWo = new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||
};
|
||||
var secondaryWo = new WorkOrder
|
||||
{
|
||||
Id = 2,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 5 }
|
||||
};
|
||||
context.Dispatches.Add(shared);
|
||||
context.workOrders.AddRange(primaryWo, secondaryWo);
|
||||
context.DispatchWorkOrders.AddRange(
|
||||
new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 1 },
|
||||
new DispatchWorkOrder { DispatchId = 10, WorkOrderId = 2 });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await service.PatchFieldAsync(2, new WorkOrderBoardPatchRequestDto
|
||||
{
|
||||
Field = WorkOrderBoardFieldNames.VendorId,
|
||||
Value = "1",
|
||||
WorkOrderVersion = ToVersion(secondaryWo),
|
||||
DispatchVersion = ToVersion(shared),
|
||||
PrimaryDispatchId = 10
|
||||
}, "actor-1");
|
||||
|
||||
Assert.Equal(1, result.VendorId);
|
||||
Assert.Equal(10, result.PrimaryDispatchId);
|
||||
Assert.Equal("Sent", result.PrimaryDispatchStatus);
|
||||
Assert.Equal(1, await context.Dispatches.CountAsync());
|
||||
var unchanged = await context.Dispatches.SingleAsync(d => d.Id == 10);
|
||||
Assert.Equal(appointment, unchanged.ScheduledDate);
|
||||
Assert.Equal("Sent", unchanged.Status);
|
||||
Assert.Equal(1, unchanged.VendorId);
|
||||
Assert.Equal(2, await context.DispatchWorkOrders.CountAsync());
|
||||
Assert.Empty(await context.WorkOrderAuditLogs
|
||||
.Where(a => a.FieldName == "VendorId")
|
||||
.ToListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_ApptDate_ClientIdNotBelonging_ThrowsInvalidDispatch()
|
||||
{
|
||||
var (context, service) = CreateSut();
|
||||
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Vendor A" });
|
||||
var ownDispatch = new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
WorkOrderId = 1,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||
};
|
||||
var foreignDispatch = new Dispatch
|
||||
{
|
||||
Id = 99,
|
||||
WorkOrderId = 2,
|
||||
VendorId = 1,
|
||||
Status = "Sent",
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 3 }
|
||||
};
|
||||
var wo = new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||
};
|
||||
var otherWo = new WorkOrder
|
||||
{
|
||||
Id = 2,
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 99,
|
||||
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 4 }
|
||||
};
|
||||
context.Dispatches.AddRange(ownDispatch, foreignDispatch);
|
||||
context.workOrders.AddRange(wo, otherWo);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
||||
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||
{
|
||||
Field = WorkOrderBoardFieldNames.ApptDate,
|
||||
Value = "2026-06-26",
|
||||
WorkOrderVersion = ToVersion(wo),
|
||||
DispatchVersion = ToVersion(ownDispatch),
|
||||
PrimaryDispatchId = 99
|
||||
}, "actor-1"));
|
||||
|
||||
Assert.Equal("InvalidDispatch", ex.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PatchField_CreatesFieldLockAndAuditPerField()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue