mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-02 08:33:22 +00:00
feat(uplifts): expose queue closed flag, attachments, decider, pending exposure (SH-207, SH-208)
The approvals queue frontend needs four list-contract additions the read contract PRs do not carry yet: workOrderClosed so the Approved tab can disable Revoke on terminal work orders (mirroring the SH-196 revoke guard), attachmentCount from non-deleted UpliftEvidence documents so the +N chip renders, decidedByName for the Approved By column, and the queue-wide pendingExposureTotal for the header total.
This commit is contained in:
parent
d366f319e9
commit
dfd248cfcb
8 changed files with 201 additions and 2 deletions
|
|
@ -79,13 +79,23 @@ public class UpliftControllerTests
|
||||||
WorkOrderNumber = "WO-77",
|
WorkOrderNumber = "WO-77",
|
||||||
WorkOrderSite = "SITE-EAST",
|
WorkOrderSite = "SITE-EAST",
|
||||||
WorkOrderService = "HVAC",
|
WorkOrderService = "HVAC",
|
||||||
|
WorkOrderClosed = true,
|
||||||
|
AttachmentCount = 2,
|
||||||
RequestedByName = "Gateway",
|
RequestedByName = "Gateway",
|
||||||
|
DecidedByName = "Grace Hopper",
|
||||||
WorkOrderAutoApprovedTotal = 150m,
|
WorkOrderAutoApprovedTotal = 150m,
|
||||||
WorkOrderAdminApprovedTotal = 300m,
|
WorkOrderAdminApprovedTotal = 300m,
|
||||||
WorkOrderApprovedExposureTotal = 450m
|
WorkOrderApprovedExposureTotal = 450m
|
||||||
};
|
};
|
||||||
service.Setup(x => x.ListAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<string?>(), It.IsAny<int?>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
service.Setup(x => x.ListAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<string?>(), It.IsAny<int?>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync(new UpliftListResultDTO { Total = 1, Page = 1, PageSize = 25, Items = new[] { item } });
|
.ReturnsAsync(new UpliftListResultDTO
|
||||||
|
{
|
||||||
|
Total = 1,
|
||||||
|
Page = 1,
|
||||||
|
PageSize = 25,
|
||||||
|
PendingExposureTotal = 200m,
|
||||||
|
Items = new[] { item }
|
||||||
|
});
|
||||||
|
|
||||||
var controller = NewController(service);
|
var controller = NewController(service);
|
||||||
|
|
||||||
|
|
@ -98,8 +108,12 @@ public class UpliftControllerTests
|
||||||
returned.WorkOrderNumber.Should().Be("WO-77");
|
returned.WorkOrderNumber.Should().Be("WO-77");
|
||||||
returned.WorkOrderSite.Should().Be("SITE-EAST");
|
returned.WorkOrderSite.Should().Be("SITE-EAST");
|
||||||
returned.WorkOrderService.Should().Be("HVAC");
|
returned.WorkOrderService.Should().Be("HVAC");
|
||||||
|
returned.WorkOrderClosed.Should().BeTrue();
|
||||||
|
returned.AttachmentCount.Should().Be(2);
|
||||||
returned.RequestedByName.Should().Be("Gateway");
|
returned.RequestedByName.Should().Be("Gateway");
|
||||||
|
returned.DecidedByName.Should().Be("Grace Hopper");
|
||||||
returned.WorkOrderApprovedExposureTotal.Should().Be(450m);
|
returned.WorkOrderApprovedExposureTotal.Should().Be(450m);
|
||||||
|
data!.PendingExposureTotal.Should().Be(200m);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -462,4 +463,138 @@ public sealed class UpliftQueueReadTests
|
||||||
tier2User.Items.Single(i => i.RequiredTier == 2).CanDecide.Should().BeTrue();
|
tier2User.Items.Single(i => i.RequiredTier == 2).CanDecide.Should().BeTrue();
|
||||||
tier2User.Items.Single(i => i.RequiredTier == 1).CanDecide.Should().BeFalse();
|
tier2User.Items.Single(i => i.RequiredTier == 1).CanDecide.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Closed-work-order flag and decider (SH-208) ---
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_MarksWorkOrderClosedOnlyForTerminalLifecycleStatuses()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendorA, workOrderA) = await SeedWorkOrderAsync(context, "WO-OPEN", "SITE-A", "HVAC");
|
||||||
|
var (vendorB, workOrderB) = await SeedWorkOrderAsync(context, "WO-DONE", "SITE-B", "HVAC");
|
||||||
|
var (vendorC, workOrderC) = await SeedWorkOrderAsync(context, "WO-CANCEL", "SITE-C", "HVAC");
|
||||||
|
workOrderA.LifecycleStatus = LifecycleStatus.InProgress;
|
||||||
|
workOrderB.LifecycleStatus = LifecycleStatus.Completed;
|
||||||
|
workOrderC.LifecycleStatus = LifecycleStatus.Canceled;
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var dispatchA = await SeedDispatchAsync(context, vendorA, workOrderA, "DIS-A");
|
||||||
|
var dispatchB = await SeedDispatchAsync(context, vendorB, workOrderB, "DIS-B");
|
||||||
|
var dispatchC = await SeedDispatchAsync(context, vendorC, workOrderC, "DIS-C");
|
||||||
|
context.DispatchUpliftRequests.AddRange(
|
||||||
|
Request(dispatchA, "Approved", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 2)),
|
||||||
|
Request(dispatchB, "Approved", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 2)),
|
||||||
|
Request(dispatchC, "Approved", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 2)));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Approved", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Single(i => i.WorkOrderNumber == "WO-OPEN").WorkOrderClosed.Should().BeFalse();
|
||||||
|
result.Items.Single(i => i.WorkOrderNumber == "WO-DONE").WorkOrderClosed.Should().BeTrue();
|
||||||
|
result.Items.Single(i => i.WorkOrderNumber == "WO-CANCEL").WorkOrderClosed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_MapsDecidedByNameFromTheDecidingUser()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-77", "SITE-EAST", "HVAC");
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-1");
|
||||||
|
context.Users.Add(new ApplicationUser { Id = "user-9", FirstName = "Grace", LastName = "Hopper" });
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var request = Request(dispatch, "Approved", new DateTime(2026, 3, 1), decided: new DateTime(2026, 3, 2));
|
||||||
|
request.DecidedByUserId = "user-9";
|
||||||
|
context.DispatchUpliftRequests.Add(request);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Approved", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Single().DecidedByName.Should().Be("Grace Hopper");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Attachment count (SH-207) ---
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_CountsOnlyNonDeletedUpliftEvidenceAttachments()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendor, workOrder) = await SeedWorkOrderAsync(context, "WO-77", "SITE-EAST", "HVAC");
|
||||||
|
var dispatch = await SeedDispatchAsync(context, vendor, workOrder, "DIS-1");
|
||||||
|
context.VendorCompletionDocuments.AddRange(
|
||||||
|
Document(dispatch, vendor, workOrder, "quote.pdf"),
|
||||||
|
Document(dispatch, vendor, workOrder, "photo.jpg"),
|
||||||
|
Document(dispatch, vendor, workOrder, "signed-completion.pdf", purpose: "Completion"),
|
||||||
|
Document(dispatch, vendor, workOrder, "deleted-estimate.pdf", isDeleted: true));
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
context.DispatchUpliftRequests.Add(new DispatchUpliftRequest
|
||||||
|
{
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
Status = "Pending",
|
||||||
|
CreatedDate = new DateTime(2026, 3, 2),
|
||||||
|
RequiredTier = 1,
|
||||||
|
RequestedNTE = 200m
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Pending", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Items.Single().AttachmentCount.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VendorCompletionDocument Document(
|
||||||
|
Dispatch dispatch,
|
||||||
|
Vendor vendor,
|
||||||
|
WorkOrder workOrder,
|
||||||
|
string fileName,
|
||||||
|
string purpose = "UpliftEvidence",
|
||||||
|
bool isDeleted = false) => new()
|
||||||
|
{
|
||||||
|
VendorId = vendor.Id,
|
||||||
|
DispatchId = dispatch.Id,
|
||||||
|
WorkOrderId = workOrder.Id,
|
||||||
|
OriginalFileName = fileName,
|
||||||
|
StoredFileName = $"{fileName}.bin",
|
||||||
|
ContentType = "application/octet-stream",
|
||||||
|
SizeBytes = 128,
|
||||||
|
ScanStatus = "Passed",
|
||||||
|
ReviewStatus = "Approved",
|
||||||
|
Purpose = purpose,
|
||||||
|
Version = 1,
|
||||||
|
IsDeleted = isDeleted
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Queue-wide pending exposure (SH-207) ---
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_PendingExposureTotal_SumsPendingRequestsAcrossTheQueueRegardlessOfFilter()
|
||||||
|
{
|
||||||
|
using var context = NewContext();
|
||||||
|
var (vendorA, workOrderA) = await SeedWorkOrderAsync(context, "WO-A", "SITE-A", "HVAC");
|
||||||
|
var (vendorB, workOrderB) = await SeedWorkOrderAsync(context, "WO-B", "SITE-B", "HVAC");
|
||||||
|
var dispatchA = await SeedDispatchAsync(context, vendorA, workOrderA, "DIS-A");
|
||||||
|
var dispatchB = await SeedDispatchAsync(context, vendorB, workOrderB, "DIS-B");
|
||||||
|
context.DispatchUpliftRequests.AddRange(
|
||||||
|
Request(dispatchA, "Pending", new DateTime(2026, 3, 1), requested: 125m),
|
||||||
|
Request(dispatchB, "Pending", new DateTime(2026, 3, 2), requested: 75m),
|
||||||
|
Request(dispatchA, "Approved", new DateTime(2026, 3, 3), decided: new DateTime(2026, 3, 4), requested: 999m),
|
||||||
|
new DispatchUpliftRequest
|
||||||
|
{
|
||||||
|
DispatchId = dispatchA.Id,
|
||||||
|
Status = "Pending",
|
||||||
|
CreatedDate = new DateTime(2026, 3, 5),
|
||||||
|
RequiredTier = 1,
|
||||||
|
RequestedNTE = 500m,
|
||||||
|
IsDeleted = true
|
||||||
|
});
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
var service = NewService(context);
|
||||||
|
|
||||||
|
// Even when the caller reads the Approved decision log, the header total
|
||||||
|
// still reflects the whole pending queue.
|
||||||
|
var result = await service.ListAsync(UserWithRoles("Approver"), "Approved", null, 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.PendingExposureTotal.Should().Be(200m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,13 @@ namespace Data.SeaHavenIndustries
|
||||||
public string? WorkOrderNumber { get; set; }
|
public string? WorkOrderNumber { get; set; }
|
||||||
public string? WorkOrderSiteCode { get; set; }
|
public string? WorkOrderSiteCode { get; set; }
|
||||||
public string? WorkOrderService { get; set; }
|
public string? WorkOrderService { get; set; }
|
||||||
|
public bool WorkOrderClosed { get; set; }
|
||||||
|
public int AttachmentCount { get; set; }
|
||||||
public string? RequestedByVendorName { get; set; }
|
public string? RequestedByVendorName { get; set; }
|
||||||
public string? RequestedByFirstName { get; set; }
|
public string? RequestedByFirstName { get; set; }
|
||||||
public string? RequestedByLastName { get; set; }
|
public string? RequestedByLastName { get; set; }
|
||||||
|
public string? DecidedByFirstName { get; set; }
|
||||||
|
public string? DecidedByLastName { get; set; }
|
||||||
public decimal? CurrentNTE { get; set; }
|
public decimal? CurrentNTE { get; set; }
|
||||||
public decimal RequestedNTE { get; set; }
|
public decimal RequestedNTE { get; set; }
|
||||||
public string? VendorReason { get; set; }
|
public string? VendorReason { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
|
||||||
|
|
@ -28,9 +29,11 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
from wo in workOrders.DefaultIfEmpty()
|
from wo in workOrders.DefaultIfEmpty()
|
||||||
join reqUser in _context.Users on u.createdby equals reqUser.Id into requestUsers
|
join reqUser in _context.Users on u.createdby equals reqUser.Id into requestUsers
|
||||||
from reqUser in requestUsers.DefaultIfEmpty()
|
from reqUser in requestUsers.DefaultIfEmpty()
|
||||||
|
join decUser in _context.Users on u.DecidedByUserId equals decUser.Id into decUsers
|
||||||
|
from decUser in decUsers.DefaultIfEmpty()
|
||||||
where (u.IsDeleted == null || u.IsDeleted == false)
|
where (u.IsDeleted == null || u.IsDeleted == false)
|
||||||
&& (d.IsDeleted == null || d.IsDeleted == false)
|
&& (d.IsDeleted == null || d.IsDeleted == false)
|
||||||
select new { u, d, v, ev, wo, reqUser };
|
select new { u, d, v, ev, wo, reqUser, decUser };
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(status))
|
if (!string.IsNullOrWhiteSpace(status))
|
||||||
query = query.Where(x => x.u.Status == status);
|
query = query.Where(x => x.u.Status == status);
|
||||||
|
|
@ -64,9 +67,21 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
WorkOrderNumber = x.wo != null ? x.wo.WorkerOrderNumber : null,
|
WorkOrderNumber = x.wo != null ? x.wo.WorkerOrderNumber : null,
|
||||||
WorkOrderSiteCode = x.wo != null ? x.wo.SiteCode : null,
|
WorkOrderSiteCode = x.wo != null ? x.wo.SiteCode : null,
|
||||||
WorkOrderService = x.wo != null ? x.wo.Service : null,
|
WorkOrderService = x.wo != null ? x.wo.Service : null,
|
||||||
|
// SH-208: a work order is closed for uplift decisions once its
|
||||||
|
// lifecycle reaches a terminal state; mirrors the SH-196 revoke guard.
|
||||||
|
WorkOrderClosed = x.wo != null
|
||||||
|
&& (x.wo.LifecycleStatus == LifecycleStatus.Completed
|
||||||
|
|| x.wo.LifecycleStatus == LifecycleStatus.Canceled),
|
||||||
|
// SH-207: every non-deleted UpliftEvidence document on the dispatch is
|
||||||
|
// an attachment reviewers can open; the linked evidence is one of them.
|
||||||
|
AttachmentCount = x.d.CompletionDocuments.Count(doc =>
|
||||||
|
doc.Purpose == "UpliftEvidence"
|
||||||
|
&& (doc.IsDeleted == null || doc.IsDeleted == false)),
|
||||||
RequestedByVendorName = x.u.RequestedByVendorName,
|
RequestedByVendorName = x.u.RequestedByVendorName,
|
||||||
RequestedByFirstName = x.reqUser != null ? x.reqUser.FirstName : null,
|
RequestedByFirstName = x.reqUser != null ? x.reqUser.FirstName : null,
|
||||||
RequestedByLastName = x.reqUser != null ? x.reqUser.LastName : null,
|
RequestedByLastName = x.reqUser != null ? x.reqUser.LastName : null,
|
||||||
|
DecidedByFirstName = x.decUser != null ? x.decUser.FirstName : null,
|
||||||
|
DecidedByLastName = x.decUser != null ? x.decUser.LastName : null,
|
||||||
CurrentNTE = x.u.CurrentNTE,
|
CurrentNTE = x.u.CurrentNTE,
|
||||||
RequestedNTE = x.u.RequestedNTE,
|
RequestedNTE = x.u.RequestedNTE,
|
||||||
VendorReason = x.u.VendorReason,
|
VendorReason = x.u.VendorReason,
|
||||||
|
|
@ -366,6 +381,19 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
return totals.Values.ToList();
|
return totals.Values.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SH-207: queue-wide pending exposure for the approvals header. Mirrors the
|
||||||
|
// approved-exposure convention (sum of RequestedNTE) over every non-deleted
|
||||||
|
// Pending request on a non-deleted dispatch, independent of page or filters.
|
||||||
|
public async Task<decimal> GetPendingExposureTotalAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _context.DispatchUpliftRequests
|
||||||
|
.Where(u => (u.IsDeleted == null || u.IsDeleted == false)
|
||||||
|
&& u.Status == "Pending"
|
||||||
|
&& u.Dispatch != null
|
||||||
|
&& (u.Dispatch.IsDeleted == null || u.Dispatch.IsDeleted == false))
|
||||||
|
.SumAsync(u => (decimal?)u.RequestedNTE, cancellationToken) ?? 0m;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<List<DispatchUpliftRequest>> GetPendingForWorkOrderAsync(
|
public Task<List<DispatchUpliftRequest>> GetPendingForWorkOrderAsync(
|
||||||
int workOrderId,
|
int workOrderId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
// from the result have no approved exposure.
|
// from the result have no approved exposure.
|
||||||
Task<IReadOnlyList<WorkOrderUpliftExposureData>> GetApprovedExposureForWorkOrdersAsync(
|
Task<IReadOnlyList<WorkOrderUpliftExposureData>> GetApprovedExposureForWorkOrdersAsync(
|
||||||
IReadOnlyCollection<int> workOrderIds, CancellationToken cancellationToken);
|
IReadOnlyCollection<int> workOrderIds, CancellationToken cancellationToken);
|
||||||
|
// SH-207: queue-wide pending exposure for the approvals header (sum of
|
||||||
|
// RequestedNTE over non-deleted Pending requests on non-deleted dispatches).
|
||||||
|
Task<decimal> GetPendingExposureTotalAsync(CancellationToken cancellationToken);
|
||||||
Task<List<DispatchUpliftRequest>> GetPendingForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken);
|
Task<List<DispatchUpliftRequest>> GetPendingForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken);
|
||||||
// SH-101: active = Pending or ChangesRequested (the only states that block a new request).
|
// SH-101: active = Pending or ChangesRequested (the only states that block a new request).
|
||||||
Task<bool> HasActiveAsync(int dispatchId, CancellationToken cancellationToken);
|
Task<bool> HasActiveAsync(int dispatchId, CancellationToken cancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ namespace SeaHaven.Services.DTOs
|
||||||
public string? WorkOrderNumber { get; set; }
|
public string? WorkOrderNumber { get; set; }
|
||||||
public string? WorkOrderSite { get; set; }
|
public string? WorkOrderSite { get; set; }
|
||||||
public string? WorkOrderService { get; set; }
|
public string? WorkOrderService { get; set; }
|
||||||
|
public bool WorkOrderClosed { get; set; }
|
||||||
|
public int AttachmentCount { get; set; }
|
||||||
public string? RequestedByName { get; set; }
|
public string? RequestedByName { get; set; }
|
||||||
|
public string? DecidedByName { get; set; }
|
||||||
public decimal? CurrentNTE { get; set; }
|
public decimal? CurrentNTE { get; set; }
|
||||||
public decimal RequestedNTE { get; set; }
|
public decimal RequestedNTE { get; set; }
|
||||||
public decimal Delta { get; set; }
|
public decimal Delta { get; set; }
|
||||||
|
|
@ -44,6 +47,8 @@ namespace SeaHaven.Services.DTOs
|
||||||
public int Total { get; set; }
|
public int Total { get; set; }
|
||||||
public int Page { get; set; }
|
public int Page { get; set; }
|
||||||
public int PageSize { get; set; }
|
public int PageSize { get; set; }
|
||||||
|
// SH-207: queue-wide pending exposure for the approvals header.
|
||||||
|
public decimal PendingExposureTotal { get; set; }
|
||||||
public IEnumerable<UpliftListItemDTO> Items { get; set; } = Enumerable.Empty<UpliftListItemDTO>();
|
public IEnumerable<UpliftListItemDTO> Items { get; set; } = Enumerable.Empty<UpliftListItemDTO>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,10 @@ namespace SeaHaven.Services.Implementation
|
||||||
WorkOrderNumber = r.WorkOrderNumber,
|
WorkOrderNumber = r.WorkOrderNumber,
|
||||||
WorkOrderSite = r.WorkOrderSiteCode,
|
WorkOrderSite = r.WorkOrderSiteCode,
|
||||||
WorkOrderService = r.WorkOrderService,
|
WorkOrderService = r.WorkOrderService,
|
||||||
|
WorkOrderClosed = r.WorkOrderClosed,
|
||||||
|
AttachmentCount = r.AttachmentCount,
|
||||||
RequestedByName = ResolveRequestedByName(r.RequestedByVendorName, r.RequestedByFirstName, r.RequestedByLastName),
|
RequestedByName = ResolveRequestedByName(r.RequestedByVendorName, r.RequestedByFirstName, r.RequestedByLastName),
|
||||||
|
DecidedByName = ComposeDisplayName(r.DecidedByFirstName, r.DecidedByLastName),
|
||||||
CurrentNTE = r.CurrentNTE,
|
CurrentNTE = r.CurrentNTE,
|
||||||
RequestedNTE = r.RequestedNTE,
|
RequestedNTE = r.RequestedNTE,
|
||||||
Delta = r.RequestedNTE - (r.CurrentNTE ?? 0m),
|
Delta = r.RequestedNTE - (r.CurrentNTE ?? 0m),
|
||||||
|
|
@ -93,6 +96,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
Total = total,
|
Total = total,
|
||||||
Page = page,
|
Page = page,
|
||||||
PageSize = pageSize,
|
PageSize = pageSize,
|
||||||
|
PendingExposureTotal = await _upliftData.GetPendingExposureTotalAsync(cancellationToken),
|
||||||
Items = mapped
|
Items = mapped
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -345,6 +349,11 @@ namespace SeaHaven.Services.Implementation
|
||||||
if (!string.IsNullOrWhiteSpace(vendorName))
|
if (!string.IsNullOrWhiteSpace(vendorName))
|
||||||
return vendorName;
|
return vendorName;
|
||||||
|
|
||||||
|
return ComposeDisplayName(firstName, lastName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ComposeDisplayName(string? firstName, string? lastName)
|
||||||
|
{
|
||||||
var composed = string.Join(" ", new[] { firstName, lastName }
|
var composed = string.Join(" ", new[] { firstName, lastName }
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||||
return composed.Length > 0 ? composed : null;
|
return composed.Length > 0 ? composed : null;
|
||||||
|
|
|
||||||
|
|
@ -443,6 +443,7 @@ public class WorkOrderBoardCancelServiceTests
|
||||||
public Task<bool> HasPendingForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<bool> HasPendingForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<decimal> SumAutoApprovedAmountForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<decimal> SumAutoApprovedAmountForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<IReadOnlyList<WorkOrderUpliftExposureData>> GetApprovedExposureForWorkOrdersAsync(IReadOnlyCollection<int> workOrderIds, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<IReadOnlyList<WorkOrderUpliftExposureData>> GetApprovedExposureForWorkOrdersAsync(IReadOnlyCollection<int> workOrderIds, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
|
public Task<decimal> GetPendingExposureTotalAsync(CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<List<DispatchUpliftRequest>> GetPendingForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<List<DispatchUpliftRequest>> GetPendingForWorkOrderAsync(int workOrderId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<bool> HasActiveAsync(int dispatchId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<bool> HasActiveAsync(int dispatchId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
public Task<DispatchUpliftRequest?> GetActiveRequestAsync(int dispatchId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
public Task<DispatchUpliftRequest?> GetActiveRequestAsync(int dispatchId, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue