mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 04:53:11 +00:00
fix(work-orders): omit inactive primary vendor from board rows (SH-183)
Do not project VendorId/VendorName for Cancelled, Canceled, or Refused primary dispatches, and expose PrimaryDispatchStatus on the board DTO.
This commit is contained in:
parent
b02f0e4afd
commit
75d337df0f
7 changed files with 186 additions and 4 deletions
|
|
@ -0,0 +1,28 @@
|
|||
namespace SeaHaven.DataServices.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// SH-183: a cancelled or refused primary dispatch is not a live vendor assignment.
|
||||
/// Verified/Completed remain live — the company that did the work.
|
||||
/// </summary>
|
||||
public static class WorkOrderBoardDispatchAssignment
|
||||
{
|
||||
public static bool IsInactivePrimaryDispatch(string? status)
|
||||
{
|
||||
var normalized = (status ?? string.Empty).Trim().ToLowerInvariant();
|
||||
return normalized is "cancelled" or "canceled" or "refused";
|
||||
}
|
||||
|
||||
public static (int? VendorId, string? VendorName) LiveVendor(
|
||||
int? vendorId,
|
||||
string? vendorName,
|
||||
string? primaryDispatchStatus)
|
||||
{
|
||||
if (IsInactivePrimaryDispatch(primaryDispatchStatus))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
return (vendorId, vendorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ namespace SeaHaven.DataServices.Helpers
|
|||
w.ScheduledEnd,
|
||||
w.TargetWeek,
|
||||
w.ScheduleWeekOnly,
|
||||
PrimaryDispatchStatus = w.PrimaryDispatch != null ? w.PrimaryDispatch.Status : null,
|
||||
VendorId = w.PrimaryDispatch != null ? (int?)w.PrimaryDispatch.VendorId : null,
|
||||
VendorName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
||||
? w.PrimaryDispatch.Vendor.CompanyName
|
||||
|
|
@ -124,6 +125,10 @@ namespace SeaHaven.DataServices.Helpers
|
|||
var techPhone = !string.IsNullOrWhiteSpace(w.DispatchTechPhone)
|
||||
? w.DispatchTechPhone
|
||||
: (!string.IsNullOrWhiteSpace(w.WoTechPhone) ? w.WoTechPhone : w.VendorPhone);
|
||||
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(
|
||||
w.VendorId,
|
||||
w.VendorName,
|
||||
w.PrimaryDispatchStatus);
|
||||
|
||||
return new WorkOrderBoardRawRow(
|
||||
w.Id,
|
||||
|
|
@ -150,8 +155,8 @@ namespace SeaHaven.DataServices.Helpers
|
|||
w.ScheduledEnd,
|
||||
w.TargetWeek,
|
||||
w.ScheduleWeekOnly,
|
||||
w.VendorId,
|
||||
w.VendorName,
|
||||
vendorId,
|
||||
vendorName,
|
||||
w.TechName,
|
||||
techPhone,
|
||||
w.DispatchApptDate,
|
||||
|
|
@ -170,6 +175,7 @@ namespace SeaHaven.DataServices.Helpers
|
|||
w.HasUplift,
|
||||
w.PrimaryUpliftStatus,
|
||||
w.PrimaryUpliftAmount,
|
||||
w.PrimaryDispatchStatus,
|
||||
isUnscheduled);
|
||||
}).ToList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
bool HasUplift,
|
||||
string? PrimaryUpliftStatus,
|
||||
decimal? PrimaryUpliftAmount,
|
||||
string? PrimaryDispatchStatus,
|
||||
bool IsUnscheduled);
|
||||
|
||||
public record WorkOrderBoardQueryResult(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ namespace SeaHaven.Services.DTOs
|
|||
public string? DayGroup { get; set; }
|
||||
public int? VendorId { get; set; }
|
||||
public string? VendorName { get; set; }
|
||||
/// <summary>
|
||||
/// Primary dispatch status (SH-183). Inactive statuses are not a live company assignment.
|
||||
/// </summary>
|
||||
public string? PrimaryDispatchStatus { get; set; }
|
||||
public string? TechName { get; set; }
|
||||
public string? TechPhone { get; set; }
|
||||
public DateTime? ApptDate { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Security.Claims;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using SeaHaven.DataServices.Helpers;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Helpers;
|
||||
|
|
@ -86,6 +87,10 @@ namespace SeaHaven.Services.Implementation
|
|||
// Time prefers WO ScheduledStart/End (dispatcher window), then dispatch datetime.
|
||||
var apptStart = row.ScheduledStart ?? row.DispatchApptDate;
|
||||
var apptEnd = row.ScheduledEnd;
|
||||
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(
|
||||
row.VendorId,
|
||||
row.VendorName,
|
||||
row.PrimaryDispatchStatus);
|
||||
|
||||
return new WorkOrderBoardRowDto
|
||||
{
|
||||
|
|
@ -113,8 +118,9 @@ namespace SeaHaven.Services.Implementation
|
|||
TargetWeek = row.TargetWeek,
|
||||
ScheduleWeekOnly = row.ScheduleWeekOnly,
|
||||
DayGroup = WorkOrderDerivedFields.GetDayGroup(row.ScheduledDate),
|
||||
VendorId = row.VendorId,
|
||||
VendorName = row.VendorName,
|
||||
VendorId = vendorId,
|
||||
VendorName = vendorName,
|
||||
PrimaryDispatchStatus = row.PrimaryDispatchStatus,
|
||||
TechName = row.TechName,
|
||||
TechPhone = row.TechPhone,
|
||||
ApptDate = row.DispatchApptDate ?? row.ScheduledDate,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using SeaHaven.DataServices.Helpers;
|
||||
|
||||
namespace SeaHavenIndustries.Tests;
|
||||
|
||||
public class WorkOrderBoardDispatchAssignmentTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Cancelled")]
|
||||
[InlineData("Canceled")]
|
||||
[InlineData("Refused")]
|
||||
[InlineData(" cancelled ")]
|
||||
[InlineData("CANCELED")]
|
||||
[InlineData("refused")]
|
||||
public void LiveVendor_InactiveStatus_ClearsAssignment(string status)
|
||||
{
|
||||
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(45, "Acme HVAC", status);
|
||||
Assert.Null(vendorId);
|
||||
Assert.Null(vendorName);
|
||||
Assert.True(WorkOrderBoardDispatchAssignment.IsInactivePrimaryDispatch(status));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData("Sent")]
|
||||
[InlineData("Verified")]
|
||||
[InlineData("Completed")]
|
||||
public void LiveVendor_LiveOrMissingStatus_KeepsVendorIdWhenNameEmpty(string? status)
|
||||
{
|
||||
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(45, null, status);
|
||||
Assert.Equal(45, vendorId);
|
||||
Assert.Null(vendorName);
|
||||
Assert.False(WorkOrderBoardDispatchAssignment.IsInactivePrimaryDispatch(status));
|
||||
}
|
||||
}
|
||||
102
SeaHavenIndustries.Tests/WorkOrderBoardInactiveVendorTests.cs
Normal file
102
SeaHavenIndustries.Tests/WorkOrderBoardInactiveVendorTests.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Implementation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.Implementation;
|
||||
|
||||
namespace SeaHavenIndustries.Tests;
|
||||
|
||||
public class WorkOrderBoardInactiveVendorTests
|
||||
{
|
||||
private static ApplicationDbContext CreateContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
private static async Task SeedPrimaryDispatchAsync(
|
||||
ApplicationDbContext context,
|
||||
string? dispatchStatus,
|
||||
string? companyName)
|
||||
{
|
||||
context.Vendors.Add(new Vendor { Id = 1, CompanyName = companyName });
|
||||
context.Dispatches.Add(new Dispatch
|
||||
{
|
||||
Id = 10,
|
||||
VendorId = 1,
|
||||
WorkOrderId = 1,
|
||||
Status = dispatchStatus,
|
||||
ScheduledDate = new DateTime(2026, 6, 24, 8, 0, 0)
|
||||
});
|
||||
context.workOrders.Add(new WorkOrder
|
||||
{
|
||||
Id = 1,
|
||||
InternalWONumber = "10000000001",
|
||||
ScheduledDate = new DateTime(2026, 6, 23),
|
||||
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||
PrimaryDispatchId = 10,
|
||||
SiteCode = "BK5"
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Cancelled")]
|
||||
[InlineData("Canceled")]
|
||||
[InlineData("Refused")]
|
||||
public async Task GetBoardRows_InactivePrimaryDispatch_OmitsVendorAssignment(string status)
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
await SeedPrimaryDispatchAsync(context, status, "Acme HVAC");
|
||||
var weekStart = new DateOnly(2026, 6, 22);
|
||||
|
||||
var result = await new WorkOrderBoardDataService(context).GetBoardRowsAsync(new WorkOrderBoardQuery(
|
||||
weekStart,
|
||||
weekStart.AddDays(4),
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null));
|
||||
|
||||
var raw = Assert.Single(result.ScheduledRows);
|
||||
Assert.Null(raw.VendorId);
|
||||
Assert.Null(raw.VendorName);
|
||||
Assert.Equal(status, raw.PrimaryDispatchStatus);
|
||||
|
||||
var dto = WorkOrderBoardService.MapRawRow(raw, DateTime.UtcNow);
|
||||
Assert.Null(dto.VendorId);
|
||||
Assert.Null(dto.VendorName);
|
||||
Assert.Equal(status, dto.PrimaryDispatchStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetBoardRows_LiveDispatchWithEmptyCompanyName_KeepsVendorId()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
await SeedPrimaryDispatchAsync(context, "Sent", "");
|
||||
var weekStart = new DateOnly(2026, 6, 22);
|
||||
|
||||
var result = await new WorkOrderBoardDataService(context).GetBoardRowsAsync(new WorkOrderBoardQuery(
|
||||
weekStart,
|
||||
weekStart.AddDays(4),
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null));
|
||||
|
||||
var raw = Assert.Single(result.ScheduledRows);
|
||||
Assert.Equal(1, raw.VendorId);
|
||||
Assert.Equal("Sent", raw.PrimaryDispatchStatus);
|
||||
|
||||
var dto = WorkOrderBoardService.MapRawRow(raw, DateTime.UtcNow);
|
||||
Assert.Equal(1, dto.VendorId);
|
||||
Assert.Equal("Sent", dto.PrimaryDispatchStatus);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue