mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
Merge pull request #199 from Sea-Haven-Industries/fix/ab/sh411-technician-assignment-backend-thr_9fpik7tdmw
Persist explicit work-order technician assignment
This commit is contained in:
commit
5a5472ed43
19 changed files with 575 additions and 9 deletions
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.SeaHavenIndustries.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20260929210000_SH411WorkOrderTechnicianAssigned")]
|
||||||
|
public partial class SH411WorkOrderTechnicianAssigned : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "TechnicianAssigned",
|
||||||
|
table: "workOrders",
|
||||||
|
type: "bit",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TechnicianAssigned",
|
||||||
|
table: "workOrders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2826,6 +2826,9 @@ namespace Data.SeaHavenIndustries.Migrations
|
||||||
b.Property<string>("FrozenCompany")
|
b.Property<string>("FrozenCompany")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<bool?>("TechnicianAssigned")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("FrozenPoc")
|
b.Property<string>("FrozenPoc")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ namespace Data.SeaHavenIndustries
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public string? ContactName { get; set; }
|
public string? ContactName { get; set; }
|
||||||
|
public bool? TechnicianAssigned { get; set; }
|
||||||
public string? Address { get; set; }
|
public string? Address { get; set; }
|
||||||
public string? City { get; set; }
|
public string? City { get; set; }
|
||||||
public string? State { get; set; }
|
public string? State { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ namespace Data.SeaHavenIndustries
|
||||||
public int? PrimaryDispatchId { get; set; }
|
public int? PrimaryDispatchId { get; set; }
|
||||||
[ForeignKey(nameof(PrimaryDispatchId))]
|
[ForeignKey(nameof(PrimaryDispatchId))]
|
||||||
public virtual Dispatch? PrimaryDispatch { get; set; }
|
public virtual Dispatch? PrimaryDispatch { get; set; }
|
||||||
|
/// <summary>Explicit technician selection for this work order; null preserves legacy vendor-contact behavior.</summary>
|
||||||
|
public bool? TechnicianAssigned { get; set; }
|
||||||
|
|
||||||
// --- Service & Notes / POC (structured create) ---
|
// --- Service & Notes / POC (structured create) ---
|
||||||
[MaxLength(200)]
|
[MaxLength(200)]
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
TechName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
TechName = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
||||||
? w.PrimaryDispatch.Vendor.ContactName
|
? w.PrimaryDispatch.Vendor.ContactName
|
||||||
: null,
|
: null,
|
||||||
|
w.TechnicianAssigned,
|
||||||
DispatchTechPhone = w.PrimaryDispatch != null ? w.PrimaryDispatch.TechPhone : null,
|
DispatchTechPhone = w.PrimaryDispatch != null ? w.PrimaryDispatch.TechPhone : null,
|
||||||
WoTechPhone = w.TechPhone,
|
WoTechPhone = w.TechPhone,
|
||||||
VendorPhone = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
VendorPhone = w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null
|
||||||
|
|
@ -129,6 +130,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
var frozenPoc = w.LifecycleStatus == LifecycleStatus.Completed
|
var frozenPoc = w.LifecycleStatus == LifecycleStatus.Completed
|
||||||
? Deserialize<WorkOrderFrozenPoc>(w.FrozenPoc)
|
? Deserialize<WorkOrderFrozenPoc>(w.FrozenPoc)
|
||||||
: null;
|
: null;
|
||||||
|
var technicianAssigned = frozenCompany?.TechnicianAssigned ?? w.TechnicianAssigned;
|
||||||
var primaryFrozenPoc = frozenPoc?.Contacts.FirstOrDefault();
|
var primaryFrozenPoc = frozenPoc?.Contacts.FirstOrDefault();
|
||||||
var pocName = primaryFrozenPoc?.Name
|
var pocName = primaryFrozenPoc?.Name
|
||||||
?? FirstNotBlank(w.WoPocName, w.ContactPoc?.Name, w.SitePoc?.Name);
|
?? FirstNotBlank(w.WoPocName, w.ContactPoc?.Name, w.SitePoc?.Name);
|
||||||
|
|
@ -175,8 +177,8 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.ScheduleWeekOnly,
|
w.ScheduleWeekOnly,
|
||||||
vendorId,
|
vendorId,
|
||||||
vendorName,
|
vendorName,
|
||||||
frozenCompany?.ContactName ?? w.TechName,
|
technicianAssigned == false ? null : frozenCompany?.ContactName ?? w.TechName,
|
||||||
frozenCompany?.Phone ?? techPhone,
|
technicianAssigned == false ? null : frozenCompany?.Phone ?? techPhone,
|
||||||
w.DispatchApptDate,
|
w.DispatchApptDate,
|
||||||
w.Trade ?? w.Problem,
|
w.Trade ?? w.Problem,
|
||||||
w.Problem,
|
w.Problem,
|
||||||
|
|
@ -205,7 +207,8 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
frozenCompany,
|
frozenCompany,
|
||||||
frozenPoc,
|
frozenPoc,
|
||||||
w.WoPocName,
|
w.WoPocName,
|
||||||
w.WoPocPhone);
|
w.WoPocPhone,
|
||||||
|
technicianAssigned);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
|| (w.Trade ?? "").ToLower().Contains(s)
|
|| (w.Trade ?? "").ToLower().Contains(s)
|
||||||
|| (w.Problem ?? "").ToLower().Contains(s)
|
|| (w.Problem ?? "").ToLower().Contains(s)
|
||||||
|| (w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null && (w.PrimaryDispatch.Vendor.CompanyName ?? "").ToLower().Contains(s))
|
|| (w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null && (w.PrimaryDispatch.Vendor.CompanyName ?? "").ToLower().Contains(s))
|
||||||
|| (w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null && (w.PrimaryDispatch.Vendor.ContactName ?? "").ToLower().Contains(s))
|
|| (w.TechnicianAssigned != false && w.PrimaryDispatch != null && w.PrimaryDispatch.Vendor != null && (w.PrimaryDispatch.Vendor.ContactName ?? "").ToLower().Contains(s))
|
||||||
|| (w.Status ?? "").ToLower().Contains(s)
|
|| (w.Status ?? "").ToLower().Contains(s)
|
||||||
|| (w.WorkOrderType == WorkOrderType.PO && "po".Contains(s))
|
|| (w.WorkOrderType == WorkOrderType.PO && "po".Contains(s))
|
||||||
|| (w.WorkOrderType == WorkOrderType.PM && "pm".Contains(s))
|
|| (w.WorkOrderType == WorkOrderType.PM && "pm".Contains(s))
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,8 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
WorkOrderFrozenCompany? FrozenCompany = null,
|
WorkOrderFrozenCompany? FrozenCompany = null,
|
||||||
WorkOrderFrozenPoc? FrozenPoc = null,
|
WorkOrderFrozenPoc? FrozenPoc = null,
|
||||||
string? WoPocName = null,
|
string? WoPocName = null,
|
||||||
string? WoPocPhone = null);
|
string? WoPocPhone = null,
|
||||||
|
bool? TechnicianAssigned = null);
|
||||||
|
|
||||||
public record WorkOrderBoardQueryResult(
|
public record WorkOrderBoardQueryResult(
|
||||||
IReadOnlyList<WorkOrderBoardRawRow> ScheduledRows,
|
IReadOnlyList<WorkOrderBoardRawRow> ScheduledRows,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ namespace SeaHaven.Services.DTOs
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public string? ContactName { get; set; }
|
public string? ContactName { get; set; }
|
||||||
|
public bool? TechnicianAssigned { get; set; }
|
||||||
public string? Address { get; set; }
|
public string? Address { get; set; }
|
||||||
public string? City { get; set; }
|
public string? City { get; set; }
|
||||||
public string? State { get; set; }
|
public string? State { get; set; }
|
||||||
|
|
@ -112,6 +113,7 @@ 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; }
|
||||||
|
public bool? TechnicianAssigned { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Primary dispatch status. Inactive statuses are not a live company assignment.
|
/// Primary dispatch status. Inactive statuses are not a live company assignment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -147,6 +149,8 @@ namespace SeaHaven.Services.DTOs
|
||||||
{
|
{
|
||||||
public string Field { get; set; } = "";
|
public string Field { get; set; } = "";
|
||||||
public string? Value { get; set; }
|
public string? Value { get; set; }
|
||||||
|
/// <summary>Explicit technician-selection state, accepted only with a vendorId patch.</summary>
|
||||||
|
public bool? TechnicianAssigned { get; set; }
|
||||||
public string? WorkOrderVersion { get; set; }
|
public string? WorkOrderVersion { get; set; }
|
||||||
public string? DispatchVersion { get; set; }
|
public string? DispatchVersion { get; set; }
|
||||||
public int? PrimaryDispatchId { get; set; }
|
public int? PrimaryDispatchId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ namespace SeaHaven.Services.DTOs
|
||||||
/// <summary>Optional create-time lifecycle. When omitted, server defaults to Incomplete.</summary>
|
/// <summary>Optional create-time lifecycle. When omitted, server defaults to Incomplete.</summary>
|
||||||
public LifecycleStatus? LifecycleStatus { get; set; }
|
public LifecycleStatus? LifecycleStatus { get; set; }
|
||||||
public int? VendorId { get; set; }
|
public int? VendorId { get; set; }
|
||||||
|
/// <summary>Optional explicit technician-selection state for the selected vendor.</summary>
|
||||||
|
public bool? TechnicianAssigned { get; set; }
|
||||||
public DateTime? ApptDate { get; set; }
|
public DateTime? ApptDate { get; set; }
|
||||||
public string? ApptTime { get; set; }
|
public string? ApptTime { get; set; }
|
||||||
public int? PocContactId { get; set; }
|
public int? PocContactId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ namespace SeaHaven.Services.Helpers
|
||||||
new WorkOrderFrozenCompany
|
new WorkOrderFrozenCompany
|
||||||
{
|
{
|
||||||
Name = liveVendor.CompanyName,
|
Name = liveVendor.CompanyName,
|
||||||
ContactName = liveVendor.ContactName,
|
ContactName = workOrder.TechnicianAssigned == false ? null : liveVendor.ContactName,
|
||||||
|
TechnicianAssigned = workOrder.TechnicianAssigned,
|
||||||
Address = liveVendor.Address,
|
Address = liveVendor.Address,
|
||||||
City = liveVendor.City,
|
City = liveVendor.City,
|
||||||
State = liveVendor.State,
|
State = liveVendor.State,
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,12 @@ namespace SeaHaven.Services.Implementation
|
||||||
|| request.ApptDate.HasValue
|
|| request.ApptDate.HasValue
|
||||||
|| !string.IsNullOrWhiteSpace(request.ApptTime);
|
|| !string.IsNullOrWhiteSpace(request.ApptTime);
|
||||||
|
|
||||||
|
if (request.TechnicianAssigned.HasValue
|
||||||
|
&& (!request.VendorId.HasValue || request.VendorId.Value <= 0))
|
||||||
|
throw new WorkOrderBoardValidationException(
|
||||||
|
"DispatchRequired",
|
||||||
|
"technicianAssigned requires a positive vendorId.");
|
||||||
|
|
||||||
if (request.VendorId.HasValue && request.VendorId.Value > 0)
|
if (request.VendorId.HasValue && request.VendorId.Value > 0)
|
||||||
{
|
{
|
||||||
if (!await _mutationData.VendorExistsAsync(request.VendorId.Value, cancellationToken))
|
if (!await _mutationData.VendorExistsAsync(request.VendorId.Value, cancellationToken))
|
||||||
|
|
@ -128,6 +134,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
TargetWeek = request.TargetWeek,
|
TargetWeek = request.TargetWeek,
|
||||||
ScheduleWeekOnly = request.ScheduleWeekOnly,
|
ScheduleWeekOnly = request.ScheduleWeekOnly,
|
||||||
DocStatus = request.DocStatus,
|
DocStatus = request.DocStatus,
|
||||||
|
TechnicianAssigned = request.TechnicianAssigned,
|
||||||
createdby = actorId,
|
createdby = actorId,
|
||||||
CreatedDate = DateTime.UtcNow,
|
CreatedDate = DateTime.UtcNow,
|
||||||
istemplate = false
|
istemplate = false
|
||||||
|
|
@ -159,6 +166,12 @@ namespace SeaHaven.Services.Implementation
|
||||||
WorkOrderBoardFieldMutations.BoardFieldChange.Changed("LifecycleStatus", "", workOrder.LifecycleStatus?.ToString() ?? LifecycleStatus.Incomplete.ToString())
|
WorkOrderBoardFieldMutations.BoardFieldChange.Changed("LifecycleStatus", "", workOrder.LifecycleStatus?.ToString() ?? LifecycleStatus.Incomplete.ToString())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (request.TechnicianAssigned.HasValue)
|
||||||
|
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed(
|
||||||
|
"TechnicianAssigned",
|
||||||
|
"",
|
||||||
|
request.TechnicianAssigned.Value.ToString()));
|
||||||
|
|
||||||
if (workOrder.Severity != null)
|
if (workOrder.Severity != null)
|
||||||
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("Severity", "", workOrder.Severity));
|
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("Severity", "", workOrder.Severity));
|
||||||
|
|
||||||
|
|
@ -417,7 +430,9 @@ namespace SeaHaven.Services.Implementation
|
||||||
{
|
{
|
||||||
foreach (var change in changes.Where(c => c.HasChanged))
|
foreach (var change in changes.Where(c => c.HasChanged))
|
||||||
{
|
{
|
||||||
var dispatchId = change.DispatchId ?? (dispatch?.Id > 0 ? dispatch.Id : null);
|
var dispatchId = change.FieldName == "TechnicianAssigned"
|
||||||
|
? null
|
||||||
|
: change.DispatchId ?? (dispatch?.Id > 0 ? dispatch.Id : null);
|
||||||
switch (change.Action)
|
switch (change.Action)
|
||||||
{
|
{
|
||||||
case AuditActionType.StatusChanged:
|
case AuditActionType.StatusChanged:
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
DayGroup = WorkOrderDerivedFields.GetDayGroup(row.ScheduledDate),
|
DayGroup = WorkOrderDerivedFields.GetDayGroup(row.ScheduledDate),
|
||||||
VendorId = vendorId,
|
VendorId = vendorId,
|
||||||
VendorName = vendorName,
|
VendorName = vendorName,
|
||||||
|
TechnicianAssigned = row.TechnicianAssigned,
|
||||||
PrimaryDispatchStatus = row.PrimaryDispatchStatus,
|
PrimaryDispatchStatus = row.PrimaryDispatchStatus,
|
||||||
TechName = row.TechName,
|
TechName = row.TechName,
|
||||||
TechPhone = row.TechPhone,
|
TechPhone = row.TechPhone,
|
||||||
|
|
@ -202,6 +203,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
{
|
{
|
||||||
Name = company.Name,
|
Name = company.Name,
|
||||||
ContactName = company.ContactName,
|
ContactName = company.ContactName,
|
||||||
|
TechnicianAssigned = company.TechnicianAssigned,
|
||||||
Address = company.Address,
|
Address = company.Address,
|
||||||
City = company.City,
|
City = company.City,
|
||||||
State = company.State,
|
State = company.State,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,18 @@ namespace SeaHaven.Services.Implementation
|
||||||
if (canonicalField == null)
|
if (canonicalField == null)
|
||||||
throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.");
|
throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.");
|
||||||
|
|
||||||
|
if (request.TechnicianAssigned.HasValue
|
||||||
|
&& !canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
throw new WorkOrderBoardValidationException(
|
||||||
|
"InvalidField",
|
||||||
|
"technicianAssigned can only be supplied with a vendorId patch.");
|
||||||
|
|
||||||
|
if (request.TechnicianAssigned.HasValue
|
||||||
|
&& !ParseVendorIdHint(request.Value).HasValue)
|
||||||
|
throw new WorkOrderBoardValidationException(
|
||||||
|
"InvalidValue",
|
||||||
|
"technicianAssigned requires a positive vendorId.");
|
||||||
|
|
||||||
var workOrderVersion = ParseRowVersion(request.WorkOrderVersion, "workOrderVersion");
|
var workOrderVersion = ParseRowVersion(request.WorkOrderVersion, "workOrderVersion");
|
||||||
if (workOrderVersion == null)
|
if (workOrderVersion == null)
|
||||||
throw new WorkOrderBoardValidationException("WorkOrderVersionRequired", "workOrderVersion is required.");
|
throw new WorkOrderBoardValidationException("WorkOrderVersionRequired", "workOrderVersion is required.");
|
||||||
|
|
@ -132,10 +144,30 @@ namespace SeaHaven.Services.Implementation
|
||||||
_mutationData.DetachWorkOrderFromDispatch(previousDispatch, workOrder.Id);
|
_mutationData.DetachWorkOrderFromDispatch(previousDispatch, workOrder.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var priorVendorId = resolved?.Previous?.VendorId
|
||||||
|
?? (resolved is { Created: true } ? null : resolved?.Dispatch.VendorId);
|
||||||
var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField);
|
var auditField = WorkOrderBoardFieldNames.ToAuditFieldName(canonicalField);
|
||||||
var lifecycleBefore = workOrder.LifecycleStatus;
|
var lifecycleBefore = workOrder.LifecycleStatus;
|
||||||
var changes = await ApplyFieldMutationAsync(canonicalField, workOrder, dispatch, request.Value, auditField, ct);
|
var changes = await ApplyFieldMutationAsync(canonicalField, workOrder, dispatch, request.Value, auditField, ct);
|
||||||
|
|
||||||
|
if (canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& resolved != null)
|
||||||
|
{
|
||||||
|
var requestedVendorId = ParseVendorIdHint(request.Value);
|
||||||
|
var vendorChanged = requestedVendorId.HasValue && priorVendorId != requestedVendorId.Value;
|
||||||
|
var nextTechnicianAssigned = request.TechnicianAssigned
|
||||||
|
?? (vendorChanged ? null : workOrder.TechnicianAssigned);
|
||||||
|
if (workOrder.TechnicianAssigned != nextTechnicianAssigned)
|
||||||
|
{
|
||||||
|
var oldValue = workOrder.TechnicianAssigned?.ToString();
|
||||||
|
workOrder.TechnicianAssigned = nextTechnicianAssigned;
|
||||||
|
changes.Add(FieldChange.ForField(
|
||||||
|
"TechnicianAssigned",
|
||||||
|
oldValue,
|
||||||
|
nextTechnicianAssigned?.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cancelling the work order cancels its pending uplift in the same action: the
|
// Cancelling the work order cancels its pending uplift in the same action: the
|
||||||
// withdrawal and its own audit entry are staged here and committed by the same
|
// withdrawal and its own audit entry are staged here and committed by the same
|
||||||
// save as the status change.
|
// save as the status change.
|
||||||
|
|
@ -147,6 +179,9 @@ namespace SeaHaven.Services.Implementation
|
||||||
&& canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase))
|
&& canonicalField.Equals(WorkOrderBoardFieldNames.VendorId, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var previousVendor = resolved.Previous?.VendorId.ToString() ?? "";
|
var previousVendor = resolved.Previous?.VendorId.ToString() ?? "";
|
||||||
|
var technicianAssignmentChanges = changes
|
||||||
|
.Where(change => change.FieldName == "TechnicianAssigned")
|
||||||
|
.ToList();
|
||||||
changes = new List<FieldChange>
|
changes = new List<FieldChange>
|
||||||
{
|
{
|
||||||
FieldChange.ForField(
|
FieldChange.ForField(
|
||||||
|
|
@ -155,6 +190,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
createdDispatch.VendorId.ToString(),
|
createdDispatch.VendorId.ToString(),
|
||||||
createdDispatch.Id)
|
createdDispatch.Id)
|
||||||
};
|
};
|
||||||
|
changes.AddRange(technicianAssignmentChanges);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -172,7 +208,9 @@ namespace SeaHaven.Services.Implementation
|
||||||
|
|
||||||
foreach (var change in changes)
|
foreach (var change in changes)
|
||||||
{
|
{
|
||||||
var dispatchId = change.DispatchId ?? (dispatch?.Id > 0 ? dispatch.Id : null);
|
var dispatchId = change.FieldName == "TechnicianAssigned"
|
||||||
|
? null
|
||||||
|
: change.DispatchId ?? (dispatch?.Id > 0 ? dispatch.Id : null);
|
||||||
switch (change.Action)
|
switch (change.Action)
|
||||||
{
|
{
|
||||||
case AuditActionType.StatusChanged:
|
case AuditActionType.StatusChanged:
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
DayGroup = row.DayGroup,
|
DayGroup = row.DayGroup,
|
||||||
VendorId = row.VendorId,
|
VendorId = row.VendorId,
|
||||||
VendorName = row.VendorName,
|
VendorName = row.VendorName,
|
||||||
|
TechnicianAssigned = row.TechnicianAssigned,
|
||||||
PrimaryDispatchStatus = row.PrimaryDispatchStatus,
|
PrimaryDispatchStatus = row.PrimaryDispatchStatus,
|
||||||
TechName = row.TechName,
|
TechName = row.TechName,
|
||||||
TechPhone = row.TechPhone,
|
TechPhone = row.TechPhone,
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,8 @@ public class WorkOrderBoardCreateRelationalTests
|
||||||
WorkOrderType = WorkOrderType.PM,
|
WorkOrderType = WorkOrderType.PM,
|
||||||
SiteCode = "BK5",
|
SiteCode = "BK5",
|
||||||
LocationId = 1,
|
LocationId = 1,
|
||||||
VendorId = 5
|
VendorId = 5,
|
||||||
|
TechnicianAssigned = false
|
||||||
}, WorkOrderAccountTestHelpers.AccountUser(), "actor-1");
|
}, WorkOrderAccountTestHelpers.AccountUser(), "actor-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +193,10 @@ public class WorkOrderBoardCreateRelationalTests
|
||||||
var dispatch = await verify.Dispatches.AsNoTracking().SingleAsync();
|
var dispatch = await verify.Dispatches.AsNoTracking().SingleAsync();
|
||||||
Assert.Equal(result.PrimaryDispatchId, dispatch.Id);
|
Assert.Equal(result.PrimaryDispatchId, dispatch.Id);
|
||||||
Assert.Equal(result.Id, dispatch.WorkOrderId);
|
Assert.Equal(result.Id, dispatch.WorkOrderId);
|
||||||
|
Assert.False(await verify.workOrders
|
||||||
|
.Where(workOrder => workOrder.Id == result.Id)
|
||||||
|
.Select(workOrder => workOrder.TechnicianAssigned)
|
||||||
|
.SingleAsync());
|
||||||
|
|
||||||
var upliftDispatch = await new UpliftDataService(verify).GetUpliftDispatchForWorkOrderAsync(
|
var upliftDispatch = await new UpliftDataService(verify).GetUpliftDispatchForWorkOrderAsync(
|
||||||
result.Id,
|
result.Id,
|
||||||
|
|
|
||||||
|
|
@ -655,6 +655,185 @@ public class WorkOrderBoardUpdateServiceTests
|
||||||
Assert.Equal(1, await context.Dispatches.CountAsync());
|
Assert.Equal(1, await context.Dispatches.CountAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_TechnicianAssignedFalseWithSameInactiveVendor_ReplacesPrimaryDispatch()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
context.Vendors.Add(new Vendor { Id = 5, CompanyName = "Vendor Co", ContactName = "Tech One" });
|
||||||
|
var dispatch = new Dispatch
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
WorkOrderId = 1,
|
||||||
|
VendorId = 5,
|
||||||
|
Status = "Refused",
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||||
|
};
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
PrimaryDispatchId = dispatch.Id,
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
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",
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
WorkOrderVersion = ToVersion(wo),
|
||||||
|
DispatchVersion = ToVersion(dispatch),
|
||||||
|
PrimaryDispatchId = dispatch.Id
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.NotEqual(dispatch.Id, result.PrimaryDispatchId);
|
||||||
|
Assert.Equal("Pending", result.PrimaryDispatchStatus);
|
||||||
|
Assert.False(result.TechnicianAssigned);
|
||||||
|
Assert.Equal("Refused", (await context.Dispatches.SingleAsync(item => item.Id == dispatch.Id)).Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_OldClientVendorChange_ResetsExplicitTechnicianStateToLegacy()
|
||||||
|
{
|
||||||
|
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 = dispatch.Id,
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
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 = dispatch.Id
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.Null(result.TechnicianAssigned);
|
||||||
|
Assert.Equal("New Co", result.VendorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(null)]
|
||||||
|
public async Task PatchField_OldClientSameVendor_PreservesExplicitOrLegacyState(bool? technicianAssigned)
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
context.Vendors.Add(new Vendor { Id = 5, CompanyName = "Vendor Co", ContactName = "Tech One" });
|
||||||
|
var dispatch = new Dispatch
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
WorkOrderId = 1,
|
||||||
|
VendorId = 5,
|
||||||
|
Status = "Sent",
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 }
|
||||||
|
};
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
PrimaryDispatchId = dispatch.Id,
|
||||||
|
TechnicianAssigned = technicianAssigned,
|
||||||
|
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 = dispatch.Id
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.Equal(technicianAssigned, result.TechnicianAssigned);
|
||||||
|
Assert.Equal("Tech One", result.TechName);
|
||||||
|
Assert.Equal(dispatch.Id, result.PrimaryDispatchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_NewVendorWithExplicitNoTechnician_AuditsWorkOrderState()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
context.Vendors.Add(new Vendor { Id = 5, CompanyName = "Vendor Co" });
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
};
|
||||||
|
context.workOrders.Add(wo);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||||
|
{
|
||||||
|
Field = WorkOrderBoardFieldNames.VendorId,
|
||||||
|
Value = "5",
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
WorkOrderVersion = ToVersion(wo)
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.False(result.TechnicianAssigned);
|
||||||
|
var audit = await context.WorkOrderAuditLogs.SingleAsync(item => item.FieldName == "TechnicianAssigned");
|
||||||
|
Assert.Equal(AuditActionType.FieldChanged.ToString(), audit.Action);
|
||||||
|
Assert.Equal("False", audit.NewValue);
|
||||||
|
Assert.Null(audit.DispatchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_TechnicianAssignedOnAnotherField_IsRejectedWithoutMutation()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
||||||
|
TechnicianAssigned = true,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
};
|
||||||
|
context.workOrders.Add(wo);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
||||||
|
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||||
|
{
|
||||||
|
Field = WorkOrderBoardFieldNames.ScheduledDate,
|
||||||
|
Value = "2026-09-30",
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
WorkOrderVersion = ToVersion(wo)
|
||||||
|
}, "actor-1"));
|
||||||
|
|
||||||
|
Assert.Equal("InvalidField", exception.Code);
|
||||||
|
Assert.True((await context.workOrders.SingleAsync()).TechnicianAssigned);
|
||||||
|
Assert.Empty(await context.WorkOrderAuditLogs.ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("Cancelled")]
|
[InlineData("Cancelled")]
|
||||||
[InlineData("Canceled")]
|
[InlineData("Canceled")]
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,57 @@ public class WorkOrderCompletionFreezeTests
|
||||||
Assert.Null(persisted.FrozenCompany);
|
Assert.Null(persisted.FrozenCompany);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CompletionWithExplicitNoTechnicianFreezesCompanyWithoutTechnicianContact()
|
||||||
|
{
|
||||||
|
await using var context = CreateContext();
|
||||||
|
var service = CreateService(context);
|
||||||
|
var vendor = new Vendor
|
||||||
|
{
|
||||||
|
Id = 20,
|
||||||
|
CompanyName = "Frozen Vendor",
|
||||||
|
ContactName = "Do Not Freeze Tech",
|
||||||
|
CompanyPhone = "312-555-0200",
|
||||||
|
Email = "company@example.com"
|
||||||
|
};
|
||||||
|
var dispatch = new Dispatch
|
||||||
|
{
|
||||||
|
Id = 40,
|
||||||
|
WorkOrderId = 1,
|
||||||
|
VendorId = vendor.Id,
|
||||||
|
Vendor = vendor,
|
||||||
|
Status = "Sent",
|
||||||
|
TechPhone = "312-555-0299"
|
||||||
|
};
|
||||||
|
var workOrder = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
ScheduledDate = DateTime.UtcNow.Date.AddDays(1),
|
||||||
|
PrimaryDispatchId = dispatch.Id,
|
||||||
|
PrimaryDispatch = dispatch,
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
RowVersion = Version()
|
||||||
|
};
|
||||||
|
context.AddRange(vendor, dispatch, workOrder);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var completed = await service.PatchFieldAsync(
|
||||||
|
workOrder.Id,
|
||||||
|
Patch(WorkOrderBoardFieldNames.LifecycleStatus, "Complete", workOrder),
|
||||||
|
"actor-1");
|
||||||
|
var persisted = await context.workOrders.SingleAsync();
|
||||||
|
var frozen = WorkOrderCompletionSnapshotMapper.ParseCompany(persisted.FrozenCompany);
|
||||||
|
|
||||||
|
Assert.False(completed.TechnicianAssigned);
|
||||||
|
Assert.Null(completed.TechName);
|
||||||
|
Assert.Null(completed.TechPhone);
|
||||||
|
Assert.False(frozen?.TechnicianAssigned);
|
||||||
|
Assert.Null(frozen?.ContactName);
|
||||||
|
Assert.Equal("312-555-0200", frozen?.Phone);
|
||||||
|
Assert.Equal("company@example.com", frozen?.Email);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task OpenWorkOrderProjectionKeepsUsingLiveLocation()
|
public async Task OpenWorkOrderProjectionKeepsUsingLiveLocation()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -45,4 +45,17 @@ public class WorkOrderMigrationDiscoveryTests
|
||||||
|
|
||||||
Assert.Contains("20260826120000_SH221_LocationAccountScope", migrations);
|
Assert.Contains("20260826120000_SH221_LocationAccountScope", migrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SH411_TechnicianAssigned_is_discoverable_by_ef_runtime()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseSqlite("DataSource=:memory:")
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
using var context = new ApplicationDbContext(options);
|
||||||
|
var migrations = context.Database.GetMigrations().ToList();
|
||||||
|
|
||||||
|
Assert.Contains("20260929210000_SH411WorkOrderTechnicianAssigned", migrations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SeaHaven.DataServices.Helpers;
|
||||||
|
using SeaHaven.DataServices.Implementation;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Helpers;
|
||||||
|
using SeaHaven.Services.Implementation;
|
||||||
|
|
||||||
|
namespace SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class WorkOrderTechnicianAssignmentRelationalTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ClearingTechnicianOnOneWorkOrder_PreservesSharedDispatchAndSiblingSelectionAfterReload()
|
||||||
|
{
|
||||||
|
await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseSqlite(connection)
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
byte[] workOrderVersion = { 1, 0, 0, 0, 0, 0, 0, 1 };
|
||||||
|
byte[] dispatchVersion = { 1, 0, 0, 0, 0, 0, 0, 2 };
|
||||||
|
await using (var seed = new SqliteBoardTestDbContext(options))
|
||||||
|
{
|
||||||
|
await seed.Database.EnsureCreatedAsync();
|
||||||
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(seed);
|
||||||
|
seed.Users.Add(new ApplicationUser
|
||||||
|
{
|
||||||
|
Id = "actor-1",
|
||||||
|
UserName = "actor-1",
|
||||||
|
NormalizedUserName = "ACTOR-1",
|
||||||
|
Email = "actor@test.local",
|
||||||
|
NormalizedEmail = "ACTOR@TEST.LOCAL"
|
||||||
|
});
|
||||||
|
seed.Vendors.Add(new Vendor
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
CompanyName = "Acme HVAC",
|
||||||
|
ContactName = "Technician One",
|
||||||
|
CompanyPhone = "312-555-0100"
|
||||||
|
});
|
||||||
|
await seed.SaveChangesAsync();
|
||||||
|
|
||||||
|
var dispatch = new Dispatch
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
VendorId = 5,
|
||||||
|
Status = "Sent",
|
||||||
|
TechPhone = "312-555-0199",
|
||||||
|
RowVersion = dispatchVersion
|
||||||
|
};
|
||||||
|
seed.Dispatches.Add(dispatch);
|
||||||
|
seed.workOrders.AddRange(
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
AccountId = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
ScheduledDate = new DateTime(2026, 9, 30),
|
||||||
|
PrimaryDispatchId = 10,
|
||||||
|
TechnicianAssigned = true,
|
||||||
|
RowVersion = workOrderVersion
|
||||||
|
},
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
AccountId = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
ScheduledDate = new DateTime(2026, 9, 30),
|
||||||
|
PrimaryDispatchId = 10,
|
||||||
|
RowVersion = new byte[] { 2, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
});
|
||||||
|
await seed.SaveChangesAsync();
|
||||||
|
|
||||||
|
dispatch.WorkOrderId = 1;
|
||||||
|
seed.DispatchWorkOrders.Add(new DispatchWorkOrder { Id = 20, DispatchId = 10, WorkOrderId = 2 });
|
||||||
|
await seed.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
await using (var context = new SqliteBoardTestDbContext(options))
|
||||||
|
{
|
||||||
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
||||||
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
||||||
|
var service = WorkOrderAccountTestHelpers.BoardUpdateService(context, audit);
|
||||||
|
var updated = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
|
||||||
|
{
|
||||||
|
Field = WorkOrderBoardFieldNames.VendorId,
|
||||||
|
Value = "5",
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
WorkOrderVersion = Convert.ToBase64String(workOrderVersion),
|
||||||
|
DispatchVersion = Convert.ToBase64String(dispatchVersion),
|
||||||
|
PrimaryDispatchId = 10
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.False(updated.TechnicianAssigned);
|
||||||
|
Assert.Equal(10, updated.PrimaryDispatchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var verify = new SqliteBoardTestDbContext(options);
|
||||||
|
var data = new WorkOrderBoardDataService(verify);
|
||||||
|
var workOrderA = Assert.IsType<SeaHaven.DataServices.Interfaces.WorkOrderBoardRawRow>(
|
||||||
|
await data.GetBoardRowByIdAsync(1));
|
||||||
|
var workOrderB = Assert.IsType<SeaHaven.DataServices.Interfaces.WorkOrderBoardRawRow>(
|
||||||
|
await data.GetBoardRowByIdAsync(2));
|
||||||
|
|
||||||
|
Assert.False(workOrderA.TechnicianAssigned);
|
||||||
|
Assert.Null(workOrderA.TechName);
|
||||||
|
Assert.Null(workOrderA.TechPhone);
|
||||||
|
Assert.Equal("Technician One", workOrderB.TechName);
|
||||||
|
Assert.Equal("312-555-0199", workOrderB.TechPhone);
|
||||||
|
Assert.Equal(10, workOrderA.PrimaryDispatchId);
|
||||||
|
Assert.Equal(10, workOrderB.PrimaryDispatchId);
|
||||||
|
Assert.Equal(5, workOrderA.VendorId);
|
||||||
|
Assert.Equal(5, workOrderB.VendorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TechnicianNameSearch_MatchesLegacyAndAssignedRows_ButNotExplicitlyClearedRow()
|
||||||
|
{
|
||||||
|
await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseSqlite(connection)
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
await using (var seed = new SqliteBoardTestDbContext(options))
|
||||||
|
{
|
||||||
|
await seed.Database.EnsureCreatedAsync();
|
||||||
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(seed);
|
||||||
|
seed.Vendors.Add(new Vendor
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
CompanyName = "Acme HVAC",
|
||||||
|
ContactName = "Technician One",
|
||||||
|
CompanyPhone = "312-555-0100"
|
||||||
|
});
|
||||||
|
seed.Dispatches.AddRange(
|
||||||
|
new Dispatch { Id = 10, VendorId = 5, Status = "Sent", RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 2 } },
|
||||||
|
new Dispatch { Id = 11, VendorId = 5, Status = "Sent", RowVersion = new byte[] { 2, 0, 0, 0, 0, 0, 0, 2 } },
|
||||||
|
new Dispatch { Id = 12, VendorId = 5, Status = "Sent", RowVersion = new byte[] { 3, 0, 0, 0, 0, 0, 0, 2 } });
|
||||||
|
seed.workOrders.AddRange(
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
AccountId = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
PrimaryDispatchId = 10,
|
||||||
|
TechnicianAssigned = null,
|
||||||
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
},
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
AccountId = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
PrimaryDispatchId = 11,
|
||||||
|
TechnicianAssigned = true,
|
||||||
|
RowVersion = new byte[] { 2, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
},
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
AccountId = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
PrimaryDispatchId = 12,
|
||||||
|
TechnicianAssigned = false,
|
||||||
|
RowVersion = new byte[] { 3, 0, 0, 0, 0, 0, 0, 1 }
|
||||||
|
});
|
||||||
|
await seed.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var context = new SqliteBoardTestDbContext(options);
|
||||||
|
|
||||||
|
var technicianMatches = await WorkOrderBoardSearchFilter
|
||||||
|
.Apply(context.workOrders.AsQueryable(), "Technician One")
|
||||||
|
.Select(w => w.Id)
|
||||||
|
.OrderBy(id => id)
|
||||||
|
.ToListAsync();
|
||||||
|
Assert.Equal(new[] { 1, 2 }, technicianMatches);
|
||||||
|
|
||||||
|
var companyMatches = await WorkOrderBoardSearchFilter
|
||||||
|
.Apply(context.workOrders.AsQueryable(), "Acme HVAC")
|
||||||
|
.Select(w => w.Id)
|
||||||
|
.OrderBy(id => id)
|
||||||
|
.ToListAsync();
|
||||||
|
Assert.Equal(new[] { 1, 2, 3 }, companyMatches);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SqliteBoardTestDbContext : ApplicationDbContext
|
||||||
|
{
|
||||||
|
public SqliteBoardTestDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
foreach (var index in builder.Model.GetEntityTypes().SelectMany(entity => entity.GetIndexes()))
|
||||||
|
{
|
||||||
|
if (index.GetFilter() != null)
|
||||||
|
index.SetFilter(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entityType in new[] { typeof(WorkOrder), typeof(Dispatch) })
|
||||||
|
{
|
||||||
|
var property = builder.Entity(entityType).Property("RowVersion").Metadata;
|
||||||
|
property.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never;
|
||||||
|
property.IsConcurrencyToken = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue