mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-03 15:03:29 +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.ScheduledEnd,
|
||||||
w.TargetWeek,
|
w.TargetWeek,
|
||||||
w.ScheduleWeekOnly,
|
w.ScheduleWeekOnly,
|
||||||
|
PrimaryDispatchStatus = w.PrimaryDispatch != null ? w.PrimaryDispatch.Status : null,
|
||||||
VendorId = w.PrimaryDispatch != null ? (int?)w.PrimaryDispatch.VendorId : null,
|
VendorId = w.PrimaryDispatch != null ? (int?)w.PrimaryDispatch.VendorId : null,
|
||||||
VendorName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
VendorName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
||||||
? w.PrimaryDispatch.Vendor.CompanyName
|
? w.PrimaryDispatch.Vendor.CompanyName
|
||||||
|
|
@ -124,6 +125,10 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
var techPhone = !string.IsNullOrWhiteSpace(w.DispatchTechPhone)
|
var techPhone = !string.IsNullOrWhiteSpace(w.DispatchTechPhone)
|
||||||
? w.DispatchTechPhone
|
? w.DispatchTechPhone
|
||||||
: (!string.IsNullOrWhiteSpace(w.WoTechPhone) ? w.WoTechPhone : w.VendorPhone);
|
: (!string.IsNullOrWhiteSpace(w.WoTechPhone) ? w.WoTechPhone : w.VendorPhone);
|
||||||
|
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(
|
||||||
|
w.VendorId,
|
||||||
|
w.VendorName,
|
||||||
|
w.PrimaryDispatchStatus);
|
||||||
|
|
||||||
return new WorkOrderBoardRawRow(
|
return new WorkOrderBoardRawRow(
|
||||||
w.Id,
|
w.Id,
|
||||||
|
|
@ -150,8 +155,8 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.ScheduledEnd,
|
w.ScheduledEnd,
|
||||||
w.TargetWeek,
|
w.TargetWeek,
|
||||||
w.ScheduleWeekOnly,
|
w.ScheduleWeekOnly,
|
||||||
w.VendorId,
|
vendorId,
|
||||||
w.VendorName,
|
vendorName,
|
||||||
w.TechName,
|
w.TechName,
|
||||||
techPhone,
|
techPhone,
|
||||||
w.DispatchApptDate,
|
w.DispatchApptDate,
|
||||||
|
|
@ -170,6 +175,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.HasUplift,
|
w.HasUplift,
|
||||||
w.PrimaryUpliftStatus,
|
w.PrimaryUpliftStatus,
|
||||||
w.PrimaryUpliftAmount,
|
w.PrimaryUpliftAmount,
|
||||||
|
w.PrimaryDispatchStatus,
|
||||||
isUnscheduled);
|
isUnscheduled);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
bool HasUplift,
|
bool HasUplift,
|
||||||
string? PrimaryUpliftStatus,
|
string? PrimaryUpliftStatus,
|
||||||
decimal? PrimaryUpliftAmount,
|
decimal? PrimaryUpliftAmount,
|
||||||
|
string? PrimaryDispatchStatus,
|
||||||
bool IsUnscheduled);
|
bool IsUnscheduled);
|
||||||
|
|
||||||
public record WorkOrderBoardQueryResult(
|
public record WorkOrderBoardQueryResult(
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,10 @@ namespace SeaHaven.Services.DTOs
|
||||||
public string? DayGroup { get; set; }
|
public string? DayGroup { get; set; }
|
||||||
public int? VendorId { get; set; }
|
public int? VendorId { get; set; }
|
||||||
public string? VendorName { 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? TechName { get; set; }
|
||||||
public string? TechPhone { get; set; }
|
public string? TechPhone { get; set; }
|
||||||
public DateTime? ApptDate { get; set; }
|
public DateTime? ApptDate { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Data.SeaHavenIndustries.Enums;
|
using Data.SeaHavenIndustries.Enums;
|
||||||
|
using SeaHaven.DataServices.Helpers;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.DataServices.Interfaces;
|
||||||
using SeaHaven.Services.DTOs;
|
using SeaHaven.Services.DTOs;
|
||||||
using SeaHaven.Services.Helpers;
|
using SeaHaven.Services.Helpers;
|
||||||
|
|
@ -86,6 +87,10 @@ namespace SeaHaven.Services.Implementation
|
||||||
// Time prefers WO ScheduledStart/End (dispatcher window), then dispatch datetime.
|
// Time prefers WO ScheduledStart/End (dispatcher window), then dispatch datetime.
|
||||||
var apptStart = row.ScheduledStart ?? row.DispatchApptDate;
|
var apptStart = row.ScheduledStart ?? row.DispatchApptDate;
|
||||||
var apptEnd = row.ScheduledEnd;
|
var apptEnd = row.ScheduledEnd;
|
||||||
|
var (vendorId, vendorName) = WorkOrderBoardDispatchAssignment.LiveVendor(
|
||||||
|
row.VendorId,
|
||||||
|
row.VendorName,
|
||||||
|
row.PrimaryDispatchStatus);
|
||||||
|
|
||||||
return new WorkOrderBoardRowDto
|
return new WorkOrderBoardRowDto
|
||||||
{
|
{
|
||||||
|
|
@ -113,8 +118,9 @@ namespace SeaHaven.Services.Implementation
|
||||||
TargetWeek = row.TargetWeek,
|
TargetWeek = row.TargetWeek,
|
||||||
ScheduleWeekOnly = row.ScheduleWeekOnly,
|
ScheduleWeekOnly = row.ScheduleWeekOnly,
|
||||||
DayGroup = WorkOrderDerivedFields.GetDayGroup(row.ScheduledDate),
|
DayGroup = WorkOrderDerivedFields.GetDayGroup(row.ScheduledDate),
|
||||||
VendorId = row.VendorId,
|
VendorId = vendorId,
|
||||||
VendorName = row.VendorName,
|
VendorName = vendorName,
|
||||||
|
PrimaryDispatchStatus = row.PrimaryDispatchStatus,
|
||||||
TechName = row.TechName,
|
TechName = row.TechName,
|
||||||
TechPhone = row.TechPhone,
|
TechPhone = row.TechPhone,
|
||||||
ApptDate = row.DispatchApptDate ?? row.ScheduledDate,
|
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