mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
Fix SH-387 uplift creation without primary dispatch
This commit is contained in:
parent
b36b69df83
commit
d282799127
4 changed files with 81 additions and 12 deletions
|
|
@ -122,25 +122,27 @@ namespace SeaHaven.DataServices.Implementation
|
|||
return await _context.Dispatches.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetByWorkOrderIdAsync(int workOrderId)
|
||||
public async Task<IEnumerable<Dispatch>> GetByWorkOrderIdAsync(
|
||||
int workOrderId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var dispatchIds = await _context.DispatchWorkOrders
|
||||
.Where(dwo => dwo.WorkOrderId == workOrderId)
|
||||
.Select(dwo => dwo.DispatchId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var directIds = await _context.Dispatches
|
||||
.Where(d => d.WorkOrderId == workOrderId)
|
||||
.Select(d => d.Id)
|
||||
.ToListAsync();
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var allIds = dispatchIds.Union(directIds).Distinct().ToList();
|
||||
|
||||
return await _context.Dispatches
|
||||
.Where(d => allIds.Contains(d.Id))
|
||||
.Include(d => d.Vendor)
|
||||
.ToListAsync();
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetByWorkOrderIdsAsync(IEnumerable<int> workOrderIds)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
Task<Dispatch?> GetByIdWithVendorAndWorkOrderAsync(int id);
|
||||
Task<IEnumerable<Dispatch>> GetAllAsync();
|
||||
Task<IEnumerable<Dispatch>> GetByVendorIdAsync(int vendorId);
|
||||
Task<IEnumerable<Dispatch>> GetByWorkOrderIdAsync(int workOrderId);
|
||||
Task<IEnumerable<Dispatch>> GetByWorkOrderIdAsync(
|
||||
int workOrderId,
|
||||
CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<Dispatch>> GetByWorkOrderIdsAsync(IEnumerable<int> workOrderIds);
|
||||
Task<IEnumerable<Dispatch>> GetByStatusAsync(string status);
|
||||
Task<(IEnumerable<Dispatch> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ namespace SeaHaven.Services.Implementation
|
|||
workOrderId,
|
||||
cancellationToken,
|
||||
accountFilter);
|
||||
if (workOrder?.PrimaryDispatchId is not int dispatchId)
|
||||
if (workOrder == null)
|
||||
throw new InvalidOperationException("Work order has no primary dispatch for uplift requests");
|
||||
|
||||
if (workOrder.LifecycleStatus is LifecycleStatus.Completed or LifecycleStatus.Canceled)
|
||||
|
|
@ -104,14 +104,30 @@ namespace SeaHaven.Services.Implementation
|
|||
$"Cannot create an uplift on a '{workOrder.LifecycleStatus}' work order");
|
||||
}
|
||||
|
||||
var dispatch = await _dispatchData.GetByIdAsync(dispatchId);
|
||||
if (dispatch == null)
|
||||
throw new KeyNotFoundException("Dispatch not found");
|
||||
Dispatch? dispatch;
|
||||
if (workOrder.PrimaryDispatchId is int primaryDispatchId)
|
||||
{
|
||||
dispatch = await _dispatchData.GetByIdAsync(primaryDispatchId);
|
||||
if (dispatch == null)
|
||||
throw new KeyNotFoundException("Dispatch not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch = (await _dispatchData.GetByWorkOrderIdAsync(workOrderId, cancellationToken))
|
||||
.Where(candidate => candidate.IsDeleted != true)
|
||||
.OrderByDescending(candidate => candidate.DispatchedAt ?? candidate.CreatedDate)
|
||||
.ThenByDescending(candidate => candidate.Id)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (dispatch == null)
|
||||
throw new InvalidOperationException("Work order has no primary dispatch for uplift requests");
|
||||
}
|
||||
|
||||
if (IsTerminalForUplift(dispatch.Status))
|
||||
throw new InvalidOperationException($"Cannot request uplift on a '{dispatch.Status}' dispatch");
|
||||
|
||||
if (await _upliftData.HasPendingForWorkOrderAsync(workOrderId, cancellationToken))
|
||||
if (await _upliftData.HasPendingForWorkOrderAsync(workOrderId, cancellationToken)
|
||||
|| await _upliftData.HasActiveAsync(dispatch.Id, cancellationToken))
|
||||
throw new InvalidOperationException("An open uplift request already exists for this work order");
|
||||
|
||||
var now = _timeProvider.GetUtcNow().UtcDateTime;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ public sealed class WorkOrderUpliftServiceTests
|
|||
|
||||
private static async Task<(WorkOrder WorkOrder, Dispatch Dispatch)> SeedWorkOrderAsync(
|
||||
ApplicationDbContext context,
|
||||
WorkOrderType type = WorkOrderType.PM)
|
||||
WorkOrderType type = WorkOrderType.PM,
|
||||
int? primaryDispatchId = 10)
|
||||
{
|
||||
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context);
|
||||
context.Users.Add(new ApplicationUser
|
||||
|
|
@ -70,7 +71,7 @@ public sealed class WorkOrderUpliftServiceTests
|
|||
{
|
||||
Id = 1,
|
||||
InternalWONumber = "10000000001",
|
||||
PrimaryDispatchId = 10,
|
||||
PrimaryDispatchId = primaryDispatchId,
|
||||
AccountId = 1,
|
||||
WorkOrderType = type,
|
||||
};
|
||||
|
|
@ -123,6 +124,25 @@ public sealed class WorkOrderUpliftServiceTests
|
|||
Assert.Equal(1400m, context.Dispatches.Single(d => d.Id == 10).NTEAmount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_WithoutPrimaryDispatch_UsesWorkOrderDispatch()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
var (workOrder, dispatch) = await SeedWorkOrderAsync(context, primaryDispatchId: null);
|
||||
var service = NewService(context);
|
||||
|
||||
var created = await service.CreateAsync(
|
||||
workOrder.Id,
|
||||
new CreateWorkOrderUpliftRequestDto { Amount = 400m, Notes = "Missing primary" },
|
||||
Dispatcher(),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.NotNull(created);
|
||||
Assert.Equal("auto_approved", created!.Status);
|
||||
Assert.Equal(1400m, context.Dispatches.Single(d => d.Id == dispatch.Id).NTEAmount);
|
||||
Assert.Equal(1, await context.DispatchUpliftRequests.CountAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_PmAmountAboveCap_CreatesPendingRequest()
|
||||
{
|
||||
|
|
@ -233,6 +253,35 @@ public sealed class WorkOrderUpliftServiceTests
|
|||
Assert.Contains("open uplift request", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_ActiveRequestOnPrimaryDispatch_BlocksDuplicate()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
var (workOrder, dispatch) = await SeedWorkOrderAsync(context);
|
||||
dispatch.WorkOrderId = null;
|
||||
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
|
||||
{
|
||||
Id = 100,
|
||||
DispatchId = dispatch.Id,
|
||||
RequestedNTE = 300m,
|
||||
Status = "Pending",
|
||||
RequiredTier = 1,
|
||||
NotificationStatus = "Pending",
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = NewService(context);
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
service.CreateAsync(
|
||||
workOrder.Id,
|
||||
new CreateWorkOrderUpliftRequestDto { Amount = 100m, Notes = "Duplicate open" },
|
||||
Dispatcher(),
|
||||
CancellationToken.None));
|
||||
|
||||
Assert.Contains("open uplift request", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Single(context.DispatchUpliftRequests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WithdrawPendingForWorkOrderAsync_CancelsOpenRequestsAndAudits()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue