mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
feat(work-orders): persist IsAddOn frozen at create (SH-126)
Add set-once IsAddOn with server cutoff at create, board DTO exposure, legacy type-7 backfill, and Types=AddOn search compat. Aligns with FE PR #61 frozen contract.
This commit is contained in:
parent
f7ce2ee25d
commit
97e042f552
21 changed files with 566 additions and 11 deletions
|
|
@ -0,0 +1,37 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.SeaHavenIndustries.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20260730150000_WoIsAddOn")]
|
||||||
|
public partial class WoIsAddOn : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsAddOn",
|
||||||
|
table: "workOrders",
|
||||||
|
type: "bit",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
// Legacy WorkOrderType.AddOn (7) → flag; do not remap type (real type unknown).
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"UPDATE workOrders SET IsAddOn = 1 WHERE WorkOrderType = 7;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsAddOn",
|
||||||
|
table: "workOrders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2458,6 +2458,9 @@ namespace Data.SeaHavenIndustries.Migrations
|
||||||
.HasMaxLength(11)
|
.HasMaxLength(11)
|
||||||
.HasColumnType("nvarchar(11)");
|
.HasColumnType("nvarchar(11)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAddOn")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<bool?>("IsDeleted")
|
b.Property<bool?>("IsDeleted")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ namespace Data.SeaHavenIndustries
|
||||||
// --- Analytics slice ---
|
// --- Analytics slice ---
|
||||||
public int RescheduleCount { get; set; }
|
public int RescheduleCount { get; set; }
|
||||||
public int CarriedOver { get; set; }
|
public int CarriedOver { get; set; }
|
||||||
|
/// <summary>Recalculated on schedule set/change/clear (SH-184); create hint when unscheduled.</summary>
|
||||||
|
public bool IsAddOn { get; set; }
|
||||||
public OperationalFlags OperationalFlags { get; set; }
|
public OperationalFlags OperationalFlags { get; set; }
|
||||||
|
|
||||||
// --- Completion slice ---
|
// --- Completion slice ---
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.InternalWONumber,
|
w.InternalWONumber,
|
||||||
w.RescheduleCount,
|
w.RescheduleCount,
|
||||||
w.CarriedOver,
|
w.CarriedOver,
|
||||||
|
w.IsAddOn,
|
||||||
w.WorkOrderType,
|
w.WorkOrderType,
|
||||||
w.SiteCode,
|
w.SiteCode,
|
||||||
LocationName = w.Locations != null ? w.Locations.Name : null,
|
LocationName = w.Locations != null ? w.Locations.Name : null,
|
||||||
|
|
@ -99,6 +100,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
w.InternalWONumber,
|
w.InternalWONumber,
|
||||||
w.RescheduleCount,
|
w.RescheduleCount,
|
||||||
w.CarriedOver,
|
w.CarriedOver,
|
||||||
|
w.IsAddOn,
|
||||||
w.WorkOrderType,
|
w.WorkOrderType,
|
||||||
w.SiteCode,
|
w.SiteCode,
|
||||||
w.LocationName,
|
w.LocationName,
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,10 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
|
|
||||||
var today = (utcNow ?? DateTime.UtcNow).Date;
|
var today = (utcNow ?? DateTime.UtcNow).Date;
|
||||||
var typeFilters = hasTypes ? types!.ToList() : new List<WorkOrderType>();
|
var typeFilters = hasTypes ? types!.ToList() : new List<WorkOrderType>();
|
||||||
|
var includeAddOnFlag = typeFilters.Contains(WorkOrderType.AddOn);
|
||||||
|
|
||||||
// Keep past-due predicate inline — EF cannot translate local method calls.
|
// Keep past-due predicate inline — EF cannot translate local method calls.
|
||||||
|
// Types=AddOn also matches IsAddOn (legacy type 7 OR flag on a real type).
|
||||||
if (overdue && !hasTypes)
|
if (overdue && !hasTypes)
|
||||||
{
|
{
|
||||||
return query.Where(w =>
|
return query.Where(w =>
|
||||||
|
|
@ -84,10 +86,13 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
&& w.DueDate.Value.Date < today
|
&& w.DueDate.Value.Date < today
|
||||||
&& w.LifecycleStatus != LifecycleStatus.Completed
|
&& w.LifecycleStatus != LifecycleStatus.Completed
|
||||||
&& w.LifecycleStatus != LifecycleStatus.Canceled)
|
&& w.LifecycleStatus != LifecycleStatus.Canceled)
|
||||||
|| (w.WorkOrderType != null && typeFilters.Contains(w.WorkOrderType.Value)));
|
|| (w.WorkOrderType != null && typeFilters.Contains(w.WorkOrderType.Value))
|
||||||
|
|| (includeAddOnFlag && w.IsAddOn));
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.Where(w => w.WorkOrderType != null && typeFilters.Contains(w.WorkOrderType.Value));
|
return query.Where(w =>
|
||||||
|
(w.WorkOrderType != null && typeFilters.Contains(w.WorkOrderType.Value))
|
||||||
|
|| (includeAddOnFlag && w.IsAddOn));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IQueryable<WorkOrder> ApplyTypeFilter(
|
public static IQueryable<WorkOrder> ApplyTypeFilter(
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ namespace SeaHaven.DataServices.Helpers
|
||||||
|| (w.WorkOrderType == WorkOrderType.Inspection && "inspection".Contains(s))
|
|| (w.WorkOrderType == WorkOrderType.Inspection && "inspection".Contains(s))
|
||||||
|| (w.WorkOrderType == WorkOrderType.Reactive && "reactive".Contains(s))
|
|| (w.WorkOrderType == WorkOrderType.Reactive && "reactive".Contains(s))
|
||||||
|| (w.WorkOrderType == WorkOrderType.AddOn && ("add-on".Contains(s) || "addon".Contains(s)))
|
|| (w.WorkOrderType == WorkOrderType.AddOn && ("add-on".Contains(s) || "addon".Contains(s)))
|
||||||
|
|| (w.IsAddOn && ("add-on".Contains(s) || "addon".Contains(s)))
|
||||||
|| (w.WorkOrderType == WorkOrderType.Other && "other".Contains(s))
|
|| (w.WorkOrderType == WorkOrderType.Other && "other".Contains(s))
|
||||||
|| ("overdue".Contains(s)
|
|| ("overdue".Contains(s)
|
||||||
&& w.DueDate != null
|
&& w.DueDate != null
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
string? InternalWONumber,
|
string? InternalWONumber,
|
||||||
int RescheduleCount,
|
int RescheduleCount,
|
||||||
int CarriedOver,
|
int CarriedOver,
|
||||||
|
bool IsAddOn,
|
||||||
WorkOrderType? WorkOrderType,
|
WorkOrderType? WorkOrderType,
|
||||||
string? SiteCode,
|
string? SiteCode,
|
||||||
string? LocationName,
|
string? LocationName,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ namespace SeaHaven.Services.DTOs
|
||||||
public string? WoNumber { get; set; }
|
public string? WoNumber { get; set; }
|
||||||
public int RescheduleCount { get; set; }
|
public int RescheduleCount { get; set; }
|
||||||
public int CarriedOver { get; set; }
|
public int CarriedOver { get; set; }
|
||||||
|
public bool IsAddOn { get; set; }
|
||||||
public WorkOrderType? WorkOrderType { get; set; }
|
public WorkOrderType? WorkOrderType { get; set; }
|
||||||
public bool IsPastDue { get; set; }
|
public bool IsPastDue { get; set; }
|
||||||
public string? SiteCode { get; set; }
|
public string? SiteCode { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@ namespace SeaHaven.Services.DTOs
|
||||||
{
|
{
|
||||||
public string? WoNumber { get; set; }
|
public string? WoNumber { get; set; }
|
||||||
public WorkOrderType WorkOrderType { get; set; }
|
public WorkOrderType WorkOrderType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Manual Add-On override when unscheduled. Ignored when ScheduledDate or TargetWeek is set
|
||||||
|
/// (server computes from cutoff).
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsAddOn { get; set; }
|
||||||
public string? SiteCode { get; set; }
|
public string? SiteCode { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optional CRM customer name. Required when the caller is org-wide (no account_id)
|
/// Optional CRM customer name. Required when the caller is org-wide (no account_id)
|
||||||
|
|
|
||||||
77
SeaHaven.Services/Helpers/WorkOrderAddonCutoff.cs
Normal file
77
SeaHaven.Services/Helpers/WorkOrderAddonCutoff.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
namespace SeaHaven.Services.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side Add-On cutoff: Saturday 23:59:59 UTC of the week prior to the schedule week.
|
||||||
|
/// SH-184: recalculated whenever scheduling is set, changed, or cleared; audited on change.
|
||||||
|
/// Create may accept a client hint when unscheduled; schedule present ignores override.
|
||||||
|
/// </summary>
|
||||||
|
public static class WorkOrderAddonCutoff
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Cutoff = Monday-of(anchor) − 2 days, end of day UTC (prior-week Saturday 23:59:59).
|
||||||
|
/// </summary>
|
||||||
|
public static DateTime GetAddonCutoffUtc(DateTime anchor)
|
||||||
|
{
|
||||||
|
var monday = WorkOrderBoardMutationRules.GetWeekStartMonday(anchor);
|
||||||
|
var saturday = monday.Date.AddDays(-2);
|
||||||
|
return DateTime.SpecifyKind(
|
||||||
|
saturday.AddHours(23).AddMinutes(59).AddSeconds(59),
|
||||||
|
DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTime GetAddonCutoffUtc(DateOnly anchor)
|
||||||
|
=> GetAddonCutoffUtc(anchor.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// With schedule (ScheduledDate or TargetWeek): IsAddOn when createdAtUtc is after cutoff.
|
||||||
|
/// Without schedule: client <paramref name="manualOverride"/> (default false).
|
||||||
|
/// </summary>
|
||||||
|
public static bool ComputeIsAddOnAtCreation(
|
||||||
|
DateTime createdAtUtc,
|
||||||
|
DateTime? scheduledDate,
|
||||||
|
DateOnly? targetWeek,
|
||||||
|
bool? manualOverride)
|
||||||
|
{
|
||||||
|
return ComputeFromScheduleAnchor(createdAtUtc, scheduledDate, targetWeek, manualOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SH-184 authoritative recalc after schedule set/change/clear. No manual override on patch.
|
||||||
|
/// Cleared schedule yields false.
|
||||||
|
/// </summary>
|
||||||
|
public static bool RecalculateIsAddOnFromSchedule(
|
||||||
|
DateTime createdAtUtc,
|
||||||
|
DateTime? scheduledDate,
|
||||||
|
DateOnly? targetWeek)
|
||||||
|
=> ComputeFromScheduleAnchor(createdAtUtc, scheduledDate, targetWeek, manualOverride: false);
|
||||||
|
|
||||||
|
private static bool ComputeFromScheduleAnchor(
|
||||||
|
DateTime createdAtUtc,
|
||||||
|
DateTime? scheduledDate,
|
||||||
|
DateOnly? targetWeek,
|
||||||
|
bool? manualOverride)
|
||||||
|
{
|
||||||
|
DateTime? anchor = null;
|
||||||
|
if (scheduledDate.HasValue)
|
||||||
|
anchor = scheduledDate.Value;
|
||||||
|
else if (targetWeek.HasValue)
|
||||||
|
anchor = targetWeek.Value.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
if (!anchor.HasValue)
|
||||||
|
return manualOverride ?? false;
|
||||||
|
|
||||||
|
var cutoff = GetAddonCutoffUtc(anchor.Value);
|
||||||
|
var created = NormalizeUtc(createdAtUtc);
|
||||||
|
return created > cutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime NormalizeUtc(DateTime value)
|
||||||
|
{
|
||||||
|
if (value.Kind == DateTimeKind.Utc)
|
||||||
|
return value;
|
||||||
|
if (value.Kind == DateTimeKind.Local)
|
||||||
|
return value.ToUniversalTime();
|
||||||
|
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ namespace SeaHaven.Services.Helpers
|
||||||
? DateOnly.FromDateTime(GetWeekStartMonday(scheduledDate.Value))
|
? DateOnly.FromDateTime(GetWeekStartMonday(scheduledDate.Value))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
private static DateTime GetWeekStartMonday(DateTime date)
|
public static DateTime GetWeekStartMonday(DateTime date)
|
||||||
{
|
{
|
||||||
var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
|
var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
|
||||||
return date.Date.AddDays(-diff);
|
return date.Date.AddDays(-diff);
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,12 @@ namespace SeaHaven.Services.Implementation
|
||||||
|
|
||||||
WorkOrderBoardFieldMutations.SetInitialLifecycleStatus(workOrder);
|
WorkOrderBoardFieldMutations.SetInitialLifecycleStatus(workOrder);
|
||||||
|
|
||||||
|
workOrder.IsAddOn = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
workOrder.CreatedDate!.Value,
|
||||||
|
request.ScheduledDate,
|
||||||
|
request.TargetWeek,
|
||||||
|
request.IsAddOn);
|
||||||
|
|
||||||
var changes = new List<WorkOrderBoardFieldMutations.BoardFieldChange>
|
var changes = new List<WorkOrderBoardFieldMutations.BoardFieldChange>
|
||||||
{
|
{
|
||||||
WorkOrderBoardFieldMutations.BoardFieldChange.Changed("InternalWONumber", "", woNumber),
|
WorkOrderBoardFieldMutations.BoardFieldChange.Changed("InternalWONumber", "", woNumber),
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
WoNumber = row.InternalWONumber,
|
WoNumber = row.InternalWONumber,
|
||||||
RescheduleCount = row.RescheduleCount,
|
RescheduleCount = row.RescheduleCount,
|
||||||
CarriedOver = row.CarriedOver,
|
CarriedOver = row.CarriedOver,
|
||||||
|
IsAddOn = row.IsAddOn,
|
||||||
WorkOrderType = row.WorkOrderType,
|
WorkOrderType = row.WorkOrderType,
|
||||||
IsPastDue = WorkOrderDerivedFields.IsPastDue(row.DueDate, row.LifecycleStatus, utcNow),
|
IsPastDue = WorkOrderDerivedFields.IsPastDue(row.DueDate, row.LifecycleStatus, utcNow),
|
||||||
SiteCode = row.SiteCode,
|
SiteCode = row.SiteCode,
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,8 @@ namespace SeaHaven.Services.Implementation
|
||||||
WorkOrderBoardFieldNames.AssignTo => ApplyAssignTo(workOrder, value, auditField),
|
WorkOrderBoardFieldNames.AssignTo => ApplyAssignTo(workOrder, value, auditField),
|
||||||
WorkOrderBoardFieldNames.DueDate => new List<FieldChange> { ApplyDateField(value, auditField, v => workOrder.DueDate = v, () => workOrder.DueDate) },
|
WorkOrderBoardFieldNames.DueDate => new List<FieldChange> { ApplyDateField(value, auditField, v => workOrder.DueDate = v, () => workOrder.DueDate) },
|
||||||
WorkOrderBoardFieldNames.ScheduledDate => ApplyScheduledDate(workOrder, value, auditField),
|
WorkOrderBoardFieldNames.ScheduledDate => ApplyScheduledDate(workOrder, value, auditField),
|
||||||
WorkOrderBoardFieldNames.TargetWeek => new List<FieldChange> { ApplyTargetWeek(workOrder, value, auditField) },
|
WorkOrderBoardFieldNames.TargetWeek => ApplyTargetWeek(workOrder, value, auditField),
|
||||||
WorkOrderBoardFieldNames.ScheduleWeekOnly => new List<FieldChange> { ApplyBoolField(value, auditField, v => workOrder.ScheduleWeekOnly = v, () => workOrder.ScheduleWeekOnly) },
|
WorkOrderBoardFieldNames.ScheduleWeekOnly => ApplyScheduleWeekOnly(workOrder, value, auditField),
|
||||||
WorkOrderBoardFieldNames.VendorId => new List<FieldChange> { await ApplyVendorIdAsync(dispatch!, value, auditField) },
|
WorkOrderBoardFieldNames.VendorId => new List<FieldChange> { await ApplyVendorIdAsync(dispatch!, value, auditField) },
|
||||||
WorkOrderBoardFieldNames.ApptDate => new List<FieldChange> { ApplyApptDate(dispatch!, value, auditField) },
|
WorkOrderBoardFieldNames.ApptDate => new List<FieldChange> { ApplyApptDate(dispatch!, value, auditField) },
|
||||||
WorkOrderBoardFieldNames.ApptTime => ApplyApptTime(workOrder, dispatch!, value),
|
WorkOrderBoardFieldNames.ApptTime => ApplyApptTime(workOrder, dispatch!, value),
|
||||||
|
|
@ -437,10 +437,11 @@ namespace SeaHaven.Services.Implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
changes.AddRange(ApplyAutoScheduleSideEffects(workOrder));
|
changes.AddRange(ApplyAutoScheduleSideEffects(workOrder));
|
||||||
|
AppendIsAddOnRecalculation(workOrder, changes);
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FieldChange ApplyTargetWeek(WorkOrder workOrder, string? value, string auditField)
|
private static List<FieldChange> ApplyTargetWeek(WorkOrder workOrder, string? value, string auditField)
|
||||||
{
|
{
|
||||||
DateOnly? parsed = null;
|
DateOnly? parsed = null;
|
||||||
if (!string.IsNullOrWhiteSpace(value))
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
|
@ -450,13 +451,42 @@ namespace SeaHaven.Services.Implementation
|
||||||
parsed = d;
|
parsed = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var changes = new List<FieldChange>();
|
||||||
var old = workOrder.TargetWeek?.ToString("yyyy-MM-dd");
|
var old = workOrder.TargetWeek?.ToString("yyyy-MM-dd");
|
||||||
var newVal = parsed?.ToString("yyyy-MM-dd");
|
var newVal = parsed?.ToString("yyyy-MM-dd");
|
||||||
if (old == newVal)
|
if (old != newVal)
|
||||||
return FieldChange.Unchanged(auditField);
|
{
|
||||||
|
|
||||||
workOrder.TargetWeek = parsed;
|
workOrder.TargetWeek = parsed;
|
||||||
return FieldChange.ForField(auditField, old, newVal);
|
changes.Add(FieldChange.ForField(auditField, old, newVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendIsAddOnRecalculation(workOrder, changes);
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<FieldChange> ApplyScheduleWeekOnly(WorkOrder workOrder, string? value, string auditField)
|
||||||
|
{
|
||||||
|
var change = ApplyBoolField(value, auditField, v => workOrder.ScheduleWeekOnly = v, () => workOrder.ScheduleWeekOnly);
|
||||||
|
var changes = new List<FieldChange> { change };
|
||||||
|
if (change.HasChanged)
|
||||||
|
AppendIsAddOnRecalculation(workOrder, changes);
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendIsAddOnRecalculation(WorkOrder workOrder, List<FieldChange> changes)
|
||||||
|
{
|
||||||
|
var createdAt = workOrder.CreatedDate ?? DateTime.UtcNow;
|
||||||
|
var oldValue = workOrder.IsAddOn.ToString();
|
||||||
|
var newIsAddOn = WorkOrderAddonCutoff.RecalculateIsAddOnFromSchedule(
|
||||||
|
createdAt,
|
||||||
|
workOrder.ScheduledDate,
|
||||||
|
workOrder.TargetWeek);
|
||||||
|
|
||||||
|
if (workOrder.IsAddOn == newIsAddOn)
|
||||||
|
return;
|
||||||
|
|
||||||
|
workOrder.IsAddOn = newIsAddOn;
|
||||||
|
changes.Add(FieldChange.ForField("IsAddOn", oldValue, newIsAddOn.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FieldChange ApplyBoolField(
|
private static FieldChange ApplyBoolField(
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ namespace SeaHaven.Services.Implementation
|
||||||
WoNumber = row.WoNumber,
|
WoNumber = row.WoNumber,
|
||||||
RescheduleCount = row.RescheduleCount,
|
RescheduleCount = row.RescheduleCount,
|
||||||
CarriedOver = row.CarriedOver,
|
CarriedOver = row.CarriedOver,
|
||||||
|
IsAddOn = row.IsAddOn,
|
||||||
WorkOrderType = row.WorkOrderType,
|
WorkOrderType = row.WorkOrderType,
|
||||||
IsPastDue = row.IsPastDue,
|
IsPastDue = row.IsPastDue,
|
||||||
SiteCode = row.SiteCode,
|
SiteCode = row.SiteCode,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
using SeaHaven.Services.DTOs;
|
using SeaHaven.Services.DTOs;
|
||||||
|
|
||||||
namespace SeaHaven.Services.Validation
|
namespace SeaHaven.Services.Validation
|
||||||
|
|
@ -12,7 +13,9 @@ namespace SeaHaven.Services.Validation
|
||||||
public WorkOrderBoardCreateValidation()
|
public WorkOrderBoardCreateValidation()
|
||||||
{
|
{
|
||||||
RuleFor(x => x.WorkOrderType)
|
RuleFor(x => x.WorkOrderType)
|
||||||
.IsInEnum().WithMessage("workOrderType is required and must be a valid value.");
|
.IsInEnum().WithMessage("workOrderType is required and must be a valid value.")
|
||||||
|
.Must(t => t != WorkOrderType.AddOn)
|
||||||
|
.WithMessage("workOrderType AddOn is not allowed on create; use isAddOn with a real type.");
|
||||||
|
|
||||||
RuleFor(x => x.SiteCode)
|
RuleFor(x => x.SiteCode)
|
||||||
.NotEmpty().WithMessage("siteCode is required.")
|
.NotEmpty().WithMessage("siteCode is required.")
|
||||||
|
|
|
||||||
135
SeaHavenIndustries.Tests/WorkOrderAddonCutoffTests.cs
Normal file
135
SeaHavenIndustries.Tests/WorkOrderAddonCutoffTests.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
using SeaHaven.Services.Helpers;
|
||||||
|
|
||||||
|
namespace SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class WorkOrderAddonCutoffTests
|
||||||
|
{
|
||||||
|
// Schedule week Mon 2026-06-22 → cutoff Sat 2026-06-20 23:59:59 UTC
|
||||||
|
private static readonly DateTime ScheduleMonday = new(2026, 6, 22, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
private static readonly DateTime ExpectedCutoff = new(2026, 6, 20, 23, 59, 59, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAddonCutoffUtc_FromScheduledDate_IsPriorSaturdayEndOfDay()
|
||||||
|
{
|
||||||
|
// Wednesday in schedule week
|
||||||
|
var cutoff = WorkOrderAddonCutoff.GetAddonCutoffUtc(new DateTime(2026, 6, 24, 15, 0, 0, DateTimeKind.Utc));
|
||||||
|
Assert.Equal(ExpectedCutoff, cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAddonCutoffUtc_FromTargetWeekMonday_IsPriorSaturdayEndOfDay()
|
||||||
|
{
|
||||||
|
var cutoff = WorkOrderAddonCutoff.GetAddonCutoffUtc(new DateOnly(2026, 6, 22));
|
||||||
|
Assert.Equal(ExpectedCutoff, cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAddonCutoffUtc_FromMondayAnchor_MatchesGetWeekStartMondayMinusTwo()
|
||||||
|
{
|
||||||
|
var monday = WorkOrderBoardMutationRules.GetWeekStartMonday(ScheduleMonday);
|
||||||
|
Assert.Equal(ScheduleMonday.Date, monday.Date);
|
||||||
|
Assert.Equal(ExpectedCutoff, WorkOrderAddonCutoff.GetAddonCutoffUtc(ScheduleMonday));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Compute_AfterCutoff_ReturnsTrue_IgnoresManualFalse()
|
||||||
|
{
|
||||||
|
var created = new DateTime(2026, 6, 21, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
var result = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
created,
|
||||||
|
scheduledDate: new DateTime(2026, 6, 24),
|
||||||
|
targetWeek: null,
|
||||||
|
manualOverride: false);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Compute_BeforeCutoff_ReturnsFalse_IgnoresManualTrue()
|
||||||
|
{
|
||||||
|
var created = new DateTime(2026, 6, 20, 23, 59, 59, DateTimeKind.Utc);
|
||||||
|
var result = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
created,
|
||||||
|
scheduledDate: new DateTime(2026, 6, 24),
|
||||||
|
targetWeek: null,
|
||||||
|
manualOverride: true);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Compute_ExactlyAtCutoff_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
ExpectedCutoff,
|
||||||
|
scheduledDate: ScheduleMonday,
|
||||||
|
targetWeek: null,
|
||||||
|
manualOverride: true);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Compute_TargetWeekOnly_UsesCutoff()
|
||||||
|
{
|
||||||
|
var after = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
new DateTime(2026, 6, 21, 12, 0, 0, DateTimeKind.Utc),
|
||||||
|
scheduledDate: null,
|
||||||
|
targetWeek: new DateOnly(2026, 6, 22),
|
||||||
|
manualOverride: false);
|
||||||
|
|
||||||
|
var before = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
new DateTime(2026, 6, 19, 12, 0, 0, DateTimeKind.Utc),
|
||||||
|
scheduledDate: null,
|
||||||
|
targetWeek: new DateOnly(2026, 6, 22),
|
||||||
|
manualOverride: true);
|
||||||
|
|
||||||
|
Assert.True(after);
|
||||||
|
Assert.False(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Compute_NoSchedule_UsesManualOverride()
|
||||||
|
{
|
||||||
|
Assert.True(WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
DateTime.UtcNow, null, null, manualOverride: true));
|
||||||
|
Assert.False(WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
DateTime.UtcNow, null, null, manualOverride: false));
|
||||||
|
Assert.False(WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
DateTime.UtcNow, null, null, manualOverride: null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Compute_ScheduledDatePreferredOverTargetWeek()
|
||||||
|
{
|
||||||
|
// Target week far future (cutoff after created) would be false;
|
||||||
|
// scheduled date near past (cutoff before created) → true.
|
||||||
|
var created = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
var result = WorkOrderAddonCutoff.ComputeIsAddOnAtCreation(
|
||||||
|
created,
|
||||||
|
scheduledDate: new DateTime(2026, 6, 24),
|
||||||
|
targetWeek: new DateOnly(2099, 1, 5),
|
||||||
|
manualOverride: false);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecalculateIsAddOnFromSchedule_NoSchedule_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var created = new DateTime(2026, 6, 21, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
Assert.False(WorkOrderAddonCutoff.RecalculateIsAddOnFromSchedule(created, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecalculateIsAddOnFromSchedule_AfterCutoff_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var created = new DateTime(2026, 6, 21, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
var result = WorkOrderAddonCutoff.RecalculateIsAddOnFromSchedule(
|
||||||
|
created,
|
||||||
|
scheduledDate: new DateTime(2026, 6, 24),
|
||||||
|
targetWeek: null);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -351,4 +351,77 @@ public class WorkOrderBoardCreateServiceTests
|
||||||
|
|
||||||
Assert.Equal("VendorNotFound", ex.Code);
|
Assert.Equal("VendorNotFound", ex.Code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_ScheduledAfterCutoff_SetsIsAddOnTrue_KeepsRealType()
|
||||||
|
{
|
||||||
|
// UtcNow (test run) is after cutoff for a 2026-06 schedule week.
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
|
||||||
|
var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
||||||
|
{
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
SiteCode = "BK5",
|
||||||
|
ScheduledDate = new DateTime(2026, 6, 24),
|
||||||
|
IsAddOn = false
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.True(result.IsAddOn);
|
||||||
|
Assert.Equal(WorkOrderType.PM, result.WorkOrderType);
|
||||||
|
var wo = await context.workOrders.SingleAsync(w => w.Id == result.Id);
|
||||||
|
Assert.True(wo.IsAddOn);
|
||||||
|
Assert.Equal(WorkOrderType.PM, wo.WorkOrderType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_ScheduledBeforeCutoff_SetsIsAddOnFalse_IgnoresClientTrue()
|
||||||
|
{
|
||||||
|
// Far-future schedule → cutoff after UtcNow → not an add-on.
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
|
||||||
|
var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
||||||
|
{
|
||||||
|
WorkOrderType = WorkOrderType.Reactive,
|
||||||
|
SiteCode = "BK5",
|
||||||
|
ScheduledDate = new DateTime(2099, 3, 10),
|
||||||
|
IsAddOn = true
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.False(result.IsAddOn);
|
||||||
|
Assert.Equal(WorkOrderType.Reactive, result.WorkOrderType);
|
||||||
|
var wo = await context.workOrders.SingleAsync(w => w.Id == result.Id);
|
||||||
|
Assert.False(wo.IsAddOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_NoSchedule_ManualIsAddOnTrue()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
|
||||||
|
var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
||||||
|
{
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
SiteCode = "BK5",
|
||||||
|
IsAddOn = true
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.True(result.IsAddOn);
|
||||||
|
var wo = await context.workOrders.SingleAsync(w => w.Id == result.Id);
|
||||||
|
Assert.True(wo.IsAddOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_WorkOrderTypeAddOn_ThrowsValidation()
|
||||||
|
{
|
||||||
|
var (_, service) = CreateSut();
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<FluentValidation.ValidationException>(() =>
|
||||||
|
service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
||||||
|
{
|
||||||
|
WorkOrderType = WorkOrderType.AddOn,
|
||||||
|
SiteCode = "BK5"
|
||||||
|
}, "actor-1"));
|
||||||
|
|
||||||
|
Assert.Contains(ex.Errors, e => e.PropertyName == "WorkOrderType");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -613,4 +613,58 @@ public class WorkOrderAdvancedSearchServiceTests
|
||||||
|
|
||||||
Assert.Contains("sortBy", ex.Message);
|
Assert.Contains("sortBy", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SearchAsync_TypesAddOn_MatchesLegacyTypeOrIsAddOnFlag()
|
||||||
|
{
|
||||||
|
await using var context = CreateContext();
|
||||||
|
var today = DateTime.UtcNow.Date;
|
||||||
|
|
||||||
|
context.workOrders.AddRange(
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
WorkOrderType = WorkOrderType.AddOn,
|
||||||
|
IsAddOn = false,
|
||||||
|
ScheduledDate = today.AddDays(1),
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
InternalWONumber = "10000000001"
|
||||||
|
},
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
IsAddOn = true,
|
||||||
|
ScheduledDate = today.AddDays(2),
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
InternalWONumber = "10000000002"
|
||||||
|
},
|
||||||
|
new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
WorkOrderType = WorkOrderType.Reactive,
|
||||||
|
IsAddOn = false,
|
||||||
|
ScheduledDate = today.AddDays(3),
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
InternalWONumber = "10000000003"
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var dataService = new WorkOrderAdvancedSearchDataService(context);
|
||||||
|
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(dataService);
|
||||||
|
|
||||||
|
var result = await service.SearchAsync(new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
|
||||||
|
{
|
||||||
|
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
|
||||||
|
DateFrom = DateOnly.FromDateTime(today.AddMonths(-1)),
|
||||||
|
DateTo = DateOnly.FromDateTime(today.AddMonths(1)),
|
||||||
|
Types = new List<WorkOrderType> { WorkOrderType.AddOn }
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
Assert.Equal(2, result.TotalCount);
|
||||||
|
Assert.Contains(result.Items, i => i.Id == 1 && i.WorkOrderType == WorkOrderType.AddOn);
|
||||||
|
Assert.Contains(result.Items, i => i.Id == 2 && i.IsAddOn && i.WorkOrderType == WorkOrderType.PM);
|
||||||
|
Assert.DoesNotContain(result.Items, i => i.Id == 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,35 @@ public class WorkOrderBoardServiceTests
|
||||||
Assert.Equal("wednesday", response.Scheduled[0].DayGroup);
|
Assert.Equal("wednesday", response.Scheduled[0].DayGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetBoardAsync_MapsIsAddOn()
|
||||||
|
{
|
||||||
|
await using var context = CreateContext();
|
||||||
|
var weekStart = new DateOnly(2026, 6, 22);
|
||||||
|
|
||||||
|
context.workOrders.Add(new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
InternalWONumber = "10000000001",
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
IsAddOn = true,
|
||||||
|
ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc),
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context));
|
||||||
|
var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto
|
||||||
|
{
|
||||||
|
WeekStart = weekStart
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
Assert.Single(response.Scheduled);
|
||||||
|
Assert.True(response.Scheduled[0].IsAddOn);
|
||||||
|
Assert.Equal(WorkOrderType.PM, response.Scheduled[0].WorkOrderType);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetBoardAsync_InvalidWeekWindow_Throws()
|
public async Task GetBoardAsync_InvalidWeekWindow_Throws()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -993,4 +993,93 @@ public class WorkOrderBoardUpdateServiceTests
|
||||||
Assert.Null(result.CompletedDate);
|
Assert.Null(result.CompletedDate);
|
||||||
Assert.Null(context.workOrders.Single().CompletedDate);
|
Assert.Null(context.workOrders.Single().CompletedDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_ClearsIsAddOn_WhenScheduleCleared()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
ScheduledDate = new DateTime(2026, 6, 20),
|
||||||
|
CreatedDate = new DateTime(2026, 6, 21, 0, 0, 0, DateTimeKind.Utc),
|
||||||
|
IsAddOn = true,
|
||||||
|
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.ScheduledDate,
|
||||||
|
Value = "",
|
||||||
|
WorkOrderVersion = ToVersion(wo)
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.False(result.IsAddOn);
|
||||||
|
var persisted = await context.workOrders.SingleAsync(w => w.Id == 1);
|
||||||
|
Assert.False(persisted.IsAddOn);
|
||||||
|
Assert.Single(await context.WorkOrderAuditLogs.Where(a => a.FieldName == "IsAddOn").ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_ScheduledDate_RecalculatesIsAddOn_AfterCrossWeekMove()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
ScheduledDate = new DateTime(2026, 6, 10),
|
||||||
|
CreatedDate = new DateTime(2026, 6, 21, 0, 0, 0, DateTimeKind.Utc),
|
||||||
|
IsAddOn = false,
|
||||||
|
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.ScheduledDate,
|
||||||
|
Value = "2026-06-25",
|
||||||
|
WorkOrderVersion = ToVersion(wo)
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.True(result.IsAddOn);
|
||||||
|
var persisted = await context.workOrders.SingleAsync(w => w.Id == 1);
|
||||||
|
Assert.True(persisted.IsAddOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PatchField_WorkOrderType_DoesNotRecalculateIsAddOn()
|
||||||
|
{
|
||||||
|
var (context, service) = CreateSut();
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
||||||
|
WorkOrderType = WorkOrderType.PM,
|
||||||
|
IsAddOn = true,
|
||||||
|
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.WorkOrderType,
|
||||||
|
Value = "Reactive",
|
||||||
|
WorkOrderVersion = ToVersion(wo)
|
||||||
|
}, "actor-1");
|
||||||
|
|
||||||
|
Assert.Equal(WorkOrderType.Reactive, result.WorkOrderType);
|
||||||
|
Assert.True(result.IsAddOn);
|
||||||
|
var persisted = await context.workOrders.SingleAsync(w => w.Id == 1);
|
||||||
|
Assert.Equal(WorkOrderType.Reactive, persisted.WorkOrderType);
|
||||||
|
Assert.True(persisted.IsAddOn);
|
||||||
|
Assert.Empty(await context.WorkOrderAuditLogs.Where(a => a.FieldName == "IsAddOn").ToListAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue