feat(work-orders): persist avetaRequired on board create, patch, and search

Expose avetaRequired and originalDate on list rows so the frontend can round-trip the Aveta checkbox and Reschedule hover.
This commit is contained in:
Arthur Bassi 2026-08-24 15:14:31 -03:00
parent 424d0b88bc
commit 15315edf08
22 changed files with 301 additions and 5 deletions

View file

@ -0,0 +1,33 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260824150000_SH117_AvetaRequired")]
public partial class SH117_AvetaRequired : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "AvetaRequired",
table: "workOrders",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AvetaRequired",
table: "workOrders");
}
}
}

View file

@ -2410,6 +2410,9 @@ namespace Data.SeaHavenIndustries.Migrations
b.Property<string>("Attachments") b.Property<string>("Attachments")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<bool>("AvetaRequired")
.HasColumnType("bit");
b.Property<string>("AvettaTask") b.Property<string>("AvettaTask")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");

View file

@ -62,6 +62,10 @@ namespace Data.SeaHavenIndustries
public string? ExtraServices { get; set; } public string? ExtraServices { get; set; }
[MaxLength(4000)] [MaxLength(4000)]
public string? ServiceNotes { get; set; } public string? ServiceNotes { get; set; }
/// <summary>
/// Per-WO Aveta compliance flag (SH-117). Distinct from legacy AvettaTask string.
/// </summary>
public bool AvetaRequired { get; set; }
/// <summary>Tech/crew phone when no primary dispatch; otherwise prefer Dispatch.TechPhone.</summary> /// <summary>Tech/crew phone when no primary dispatch; otherwise prefer Dispatch.TechPhone.</summary>
[MaxLength(50)] [MaxLength(50)]
public string? TechPhone { get; set; } public string? TechPhone { get; set; }

View file

@ -19,6 +19,9 @@ namespace SeaHaven.DataServices.Helpers
w.RescheduleCount, w.RescheduleCount,
w.CarriedOver, w.CarriedOver,
w.IsAddOn, w.IsAddOn,
w.AvetaRequired,
w.OriginalDate,
w.OriginalWeek,
w.WorkOrderType, w.WorkOrderType,
w.SiteCode, w.SiteCode,
LocationName = w.Locations != null ? w.Locations.Name : null, LocationName = w.Locations != null ? w.Locations.Name : null,
@ -176,6 +179,9 @@ namespace SeaHaven.DataServices.Helpers
w.PrimaryUpliftStatus, w.PrimaryUpliftStatus,
w.PrimaryUpliftAmount, w.PrimaryUpliftAmount,
w.PrimaryDispatchStatus, w.PrimaryDispatchStatus,
w.AvetaRequired,
w.OriginalDate,
w.OriginalWeek,
isUnscheduled); isUnscheduled);
}).ToList(); }).ToList();
} }

View file

@ -74,6 +74,16 @@ namespace SeaHaven.DataServices.Helpers
return query.Where(w => w.IsAddOn); return query.Where(w => w.IsAddOn);
} }
public static IQueryable<WorkOrder> ApplyAvetaOnlyFilter(
IQueryable<WorkOrder> query,
bool avetaOnly)
{
if (!avetaOnly)
return query;
return query.Where(w => w.AvetaRequired);
}
public static IQueryable<WorkOrder> ApplyFlagColorFilter( public static IQueryable<WorkOrder> ApplyFlagColorFilter(
IQueryable<WorkOrder> query, IQueryable<WorkOrder> query,
IReadOnlyList<string>? flagColors) IReadOnlyList<string>? flagColors)

View file

@ -32,6 +32,7 @@ namespace SeaHaven.DataServices.Implementation
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyRescheduledFilter(baseQuery, query.Rescheduled); baseQuery = WorkOrderBoardSearchFacetFilters.ApplyRescheduledFilter(baseQuery, query.Rescheduled);
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyCarriedOverFilter(baseQuery, query.CarriedOver); baseQuery = WorkOrderBoardSearchFacetFilters.ApplyCarriedOverFilter(baseQuery, query.CarriedOver);
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyAddOnFilter(baseQuery, query.AddOn); baseQuery = WorkOrderBoardSearchFacetFilters.ApplyAddOnFilter(baseQuery, query.AddOn);
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyAvetaOnlyFilter(baseQuery, query.AvetaOnly);
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyFlagColorFilter(baseQuery, query.FlagColors); baseQuery = WorkOrderBoardSearchFacetFilters.ApplyFlagColorFilter(baseQuery, query.FlagColors);
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyInternalOnlyFilter(baseQuery, query.InternalOnly); baseQuery = WorkOrderBoardSearchFacetFilters.ApplyInternalOnlyFilter(baseQuery, query.InternalOnly);
baseQuery = WorkOrderBoardSearchFacetFilters.ApplyUpliftFilter( baseQuery = WorkOrderBoardSearchFacetFilters.ApplyUpliftFilter(

View file

@ -19,6 +19,7 @@ namespace SeaHaven.DataServices.Interfaces
bool Rescheduled, bool Rescheduled,
bool CarriedOver, bool CarriedOver,
bool AddOn, bool AddOn,
bool AvetaOnly,
IReadOnlyList<string>? FlagColors, IReadOnlyList<string>? FlagColors,
bool InternalOnly, bool InternalOnly,
bool HasUplift, bool HasUplift,

View file

@ -59,6 +59,9 @@ namespace SeaHaven.DataServices.Interfaces
string? PrimaryUpliftStatus, string? PrimaryUpliftStatus,
decimal? PrimaryUpliftAmount, decimal? PrimaryUpliftAmount,
string? PrimaryDispatchStatus, string? PrimaryDispatchStatus,
bool AvetaRequired,
DateOnly? OriginalDate,
DateOnly? OriginalWeek,
bool IsUnscheduled); bool IsUnscheduled);
public record WorkOrderBoardQueryResult( public record WorkOrderBoardQueryResult(

View file

@ -36,6 +36,9 @@ namespace SeaHaven.Services.DTOs
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 bool IsAddOn { get; set; }
public bool AvetaRequired { get; set; }
public DateOnly? OriginalDate { get; set; }
public DateOnly? OriginalWeek { 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; }

View file

@ -32,6 +32,8 @@ namespace SeaHaven.Services.DTOs
public string? PrimaryService { get; set; } public string? PrimaryService { get; set; }
public List<string>? ExtraServices { get; set; } public List<string>? ExtraServices { get; set; }
public string? ServiceNotes { get; set; } public string? ServiceNotes { get; set; }
/// <summary>Per-WO Aveta compliance. Omit or false = not required.</summary>
public bool? AvetaRequired { get; set; }
public int? LocationId { get; set; } public int? LocationId { get; set; }
public DateTime? DueDate { get; set; } public DateTime? DueDate { get; set; }
public string? AssignTo { get; set; } public string? AssignTo { get; set; }
@ -73,6 +75,8 @@ namespace SeaHaven.Services.DTOs
public bool Rescheduled { get; set; } public bool Rescheduled { get; set; }
public bool CarriedOver { get; set; } public bool CarriedOver { get; set; }
public bool AddOn { get; set; } public bool AddOn { get; set; }
/// <summary>When true, only rows with AvetaRequired.</summary>
public bool AvetaOnly { get; set; }
public List<string>? FlagColors { get; set; } public List<string>? FlagColors { get; set; }
public bool InternalOnly { get; set; } public bool InternalOnly { get; set; }
public bool HasUplift { get; set; } public bool HasUplift { get; set; }

View file

@ -17,8 +17,6 @@ namespace SeaHaven.Services.DTOs
public string? Trade { get; set; } public string? Trade { get; set; }
public string? Problem { get; set; } public string? Problem { get; set; }
public string? SubTrade { get; set; } public string? SubTrade { get; set; }
public DateOnly? OriginalWeek { get; set; }
public DateOnly? OriginalDate { get; set; }
public string? VendorNotes { get; set; } public string? VendorNotes { get; set; }
} }

View file

@ -21,6 +21,7 @@ namespace SeaHaven.Services.Helpers
public const string ExtraServices = "extraServices"; public const string ExtraServices = "extraServices";
public const string AdditionalContacts = "additionalContacts"; public const string AdditionalContacts = "additionalContacts";
public const string FlagColor = "flagColor"; public const string FlagColor = "flagColor";
public const string AvetaRequired = "avetaRequired";
public const string RescheduleCount = "RescheduleCount"; public const string RescheduleCount = "RescheduleCount";
private static readonly HashSet<string> DispatchFields = new(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> DispatchFields = new(StringComparer.OrdinalIgnoreCase)
@ -50,7 +51,8 @@ namespace SeaHaven.Services.Helpers
ServiceNotes, ServiceNotes,
ExtraServices, ExtraServices,
AdditionalContacts, AdditionalContacts,
FlagColor FlagColor,
AvetaRequired
}; };
public static bool IsEditable(string field) => Canonicalize(field) != null; public static bool IsEditable(string field) => Canonicalize(field) != null;
@ -93,6 +95,7 @@ namespace SeaHaven.Services.Helpers
ExtraServices => "ExtraServices", ExtraServices => "ExtraServices",
AdditionalContacts => "AdditionalContacts", AdditionalContacts => "AdditionalContacts",
FlagColor => "FlagColor", FlagColor => "FlagColor",
AvetaRequired => "AvetaRequired",
_ => canonical _ => canonical
}; };
} }

View file

@ -55,6 +55,7 @@ namespace SeaHaven.Services.Implementation
query.Rescheduled, query.Rescheduled,
query.CarriedOver, query.CarriedOver,
query.AddOn, query.AddOn,
query.AvetaOnly,
query.FlagColors, query.FlagColors,
query.InternalOnly, query.InternalOnly,
query.HasUplift, query.HasUplift,

View file

@ -85,6 +85,7 @@ namespace SeaHaven.Services.Implementation
Trade = primaryService, Trade = primaryService,
ExtraServices = extraServicesJson, ExtraServices = extraServicesJson,
ServiceNotes = serviceNotes, ServiceNotes = serviceNotes,
AvetaRequired = request.AvetaRequired == true,
PocName = pocName, PocName = pocName,
PocPhone = pocPhone, PocPhone = pocPhone,
PocNotes = pocNotes, PocNotes = pocNotes,
@ -130,6 +131,8 @@ namespace SeaHaven.Services.Implementation
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("ExtraServices", "", workOrder.ExtraServices)); changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("ExtraServices", "", workOrder.ExtraServices));
if (workOrder.ServiceNotes != null) if (workOrder.ServiceNotes != null)
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("ServiceNotes", "", workOrder.ServiceNotes)); changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("ServiceNotes", "", workOrder.ServiceNotes));
if (workOrder.AvetaRequired)
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("AvetaRequired", "", bool.TrueString));
if (workOrder.PocName != null) if (workOrder.PocName != null)
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("PocName", "", workOrder.PocName)); changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.Changed("PocName", "", workOrder.PocName));
if (workOrder.PocPhone != null) if (workOrder.PocPhone != null)

View file

@ -99,6 +99,9 @@ namespace SeaHaven.Services.Implementation
RescheduleCount = row.RescheduleCount, RescheduleCount = row.RescheduleCount,
CarriedOver = row.CarriedOver, CarriedOver = row.CarriedOver,
IsAddOn = row.IsAddOn, IsAddOn = row.IsAddOn,
AvetaRequired = row.AvetaRequired,
OriginalDate = row.OriginalDate,
OriginalWeek = row.OriginalWeek,
WorkOrderType = row.WorkOrderType, WorkOrderType = row.WorkOrderType,
IsPastDue = WorkOrderDerivedFields.IsPastDue(row.ScheduledDate, row.LifecycleStatus, utcNow), IsPastDue = WorkOrderDerivedFields.IsPastDue(row.ScheduledDate, row.LifecycleStatus, utcNow),
SiteCode = row.SiteCode, SiteCode = row.SiteCode,

View file

@ -193,6 +193,7 @@ namespace SeaHaven.Services.Implementation
WorkOrderBoardFieldNames.ExtraServices => new List<FieldChange> { ApplyExtraServices(workOrder, value, auditField) }, WorkOrderBoardFieldNames.ExtraServices => new List<FieldChange> { ApplyExtraServices(workOrder, value, auditField) },
WorkOrderBoardFieldNames.AdditionalContacts => new List<FieldChange> { ApplyAdditionalContacts(workOrder, value, auditField) }, WorkOrderBoardFieldNames.AdditionalContacts => new List<FieldChange> { ApplyAdditionalContacts(workOrder, value, auditField) },
WorkOrderBoardFieldNames.FlagColor => new List<FieldChange> { ApplyFlagColor(workOrder, value, auditField) }, WorkOrderBoardFieldNames.FlagColor => new List<FieldChange> { ApplyFlagColor(workOrder, value, auditField) },
WorkOrderBoardFieldNames.AvetaRequired => new List<FieldChange> { ApplyAvetaRequired(workOrder, value, auditField) },
_ => throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.") _ => throw new WorkOrderBoardValidationException("InvalidField", $"Field '{field}' is not editable.")
}; };
} }
@ -679,6 +680,15 @@ namespace SeaHaven.Services.Implementation
return FieldChange.ForField(auditField, old, parsed.ToString()); return FieldChange.ForField(auditField, old, parsed.ToString());
} }
private static FieldChange ApplyAvetaRequired(WorkOrder workOrder, string? value, string auditField)
{
return ApplyBoolField(
value,
auditField,
v => workOrder.AvetaRequired = v == true,
() => workOrder.AvetaRequired);
}
private static FieldChange ApplyFlagColor(WorkOrder workOrder, string? value, string auditField) private static FieldChange ApplyFlagColor(WorkOrder workOrder, string? value, string auditField)
{ {
if (!WorkOrderFlagColors.TryNormalize(value, out var canonical)) if (!WorkOrderFlagColors.TryNormalize(value, out var canonical))

View file

@ -96,6 +96,7 @@ namespace SeaHaven.Services.Implementation
RescheduleCount = row.RescheduleCount, RescheduleCount = row.RescheduleCount,
CarriedOver = row.CarriedOver, CarriedOver = row.CarriedOver,
IsAddOn = row.IsAddOn, IsAddOn = row.IsAddOn,
AvetaRequired = row.AvetaRequired,
WorkOrderType = row.WorkOrderType, WorkOrderType = row.WorkOrderType,
IsPastDue = row.IsPastDue, IsPastDue = row.IsPastDue,
SiteCode = row.SiteCode, SiteCode = row.SiteCode,
@ -137,8 +138,8 @@ namespace SeaHaven.Services.Implementation
Trade = extended?.Trade, Trade = extended?.Trade,
Problem = extended?.Problem, Problem = extended?.Problem,
SubTrade = extended?.SubTrade, SubTrade = extended?.SubTrade,
OriginalWeek = extended?.OriginalWeek, OriginalWeek = row.OriginalWeek ?? extended?.OriginalWeek,
OriginalDate = extended?.OriginalDate, OriginalDate = row.OriginalDate ?? extended?.OriginalDate,
CompletedDate = extended?.CompletedDate, CompletedDate = extended?.CompletedDate,
VendorNotes = extended?.VendorNotes VendorNotes = extended?.VendorNotes
}; };

View file

@ -432,6 +432,45 @@ public class WorkOrderBoardCreateServiceTests
Assert.True(wo.IsAddOn); Assert.True(wo.IsAddOn);
} }
[Fact]
public async Task Create_AvetaRequiredTrue_PersistsAndReturns()
{
var (context, service) = CreateSut();
var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
{
WorkOrderType = WorkOrderType.PM,
SiteCode = "BK5",
AvetaRequired = true
}, WorkOrderAccountTestHelpers.AccountUser(), "actor-1");
Assert.True(result.AvetaRequired);
var wo = await context.workOrders.SingleAsync(w => w.Id == result.Id);
Assert.True(wo.AvetaRequired);
Assert.Contains(
await context.WorkOrderAuditLogs.ToListAsync(),
l => l.FieldName == "AvetaRequired" && l.Action == "FieldChanged");
}
[Fact]
public async Task Create_AvetaRequiredOmitted_DefaultsFalse()
{
var (context, service) = CreateSut();
var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
{
WorkOrderType = WorkOrderType.PM,
SiteCode = "BK5"
}, WorkOrderAccountTestHelpers.AccountUser(), "actor-1");
Assert.False(result.AvetaRequired);
var wo = await context.workOrders.SingleAsync(w => w.Id == result.Id);
Assert.False(wo.AvetaRequired);
Assert.DoesNotContain(
await context.WorkOrderAuditLogs.ToListAsync(),
l => l.FieldName == "AvetaRequired");
}
[Fact] [Fact]
public async Task Create_WorkOrderTypeAddOn_ThrowsValidation() public async Task Create_WorkOrderTypeAddOn_ThrowsValidation()
{ {

View file

@ -829,4 +829,51 @@ public class WorkOrderAdvancedSearchServiceTests
Assert.Equal(1, result.TotalCount); Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.Items.Single().Id); Assert.Equal(1, result.Items.Single().Id);
} }
[Fact]
public async Task SearchAsync_AvetaOnly_ReturnsFlaggedRows()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.AddRange(
new WorkOrder
{
Id = 1,
AvetaRequired = true,
ScheduledDate = new DateTime(2026, 6, 23),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000001",
},
new WorkOrder
{
Id = 2,
AvetaRequired = false,
ScheduledDate = new DateTime(2026, 6, 24),
LifecycleStatus = LifecycleStatus.Scheduled,
InternalWONumber = "10000000002",
});
await context.SaveChangesAsync();
var dataService = new WorkOrderAdvancedSearchDataService(context);
var service = new SeaHaven.Services.Implementation.WorkOrderAdvancedSearchService(
dataService,
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.SearchAsync(
new SeaHaven.Services.DTOs.WorkOrderAdvancedSearchQueryDto
{
DatePreset = SeaHaven.Services.Helpers.WorkOrderAdvancedSearchDatePreset.Custom,
DateFrom = weekStart,
DateTo = weekStart.AddDays(6),
AvetaOnly = true,
},
WorkOrderAccountTestHelpers.OrgWideAdmin(),
null);
Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.Items.Single().Id);
Assert.True(result.Items.Single().AvetaRequired);
}
} }

View file

@ -448,6 +448,40 @@ public class WorkOrderBoardServiceTests
Assert.Equal(WorkOrderType.PM, response.Scheduled[0].WorkOrderType); Assert.Equal(WorkOrderType.PM, response.Scheduled[0].WorkOrderType);
} }
[Fact]
public async Task GetBoardAsync_MapsAvetaRequiredAndOriginalDate()
{
await using var context = CreateContext();
var weekStart = new DateOnly(2026, 6, 22);
context.workOrders.Add(new WorkOrder
{
Id = 1,
InternalWONumber = "10000000001",
WorkOrderType = WorkOrderType.PM,
AvetaRequired = true,
OriginalDate = new DateOnly(2026, 5, 8),
OriginalWeek = new DateOnly(2026, 5, 4),
ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc),
LifecycleStatus = LifecycleStatus.Scheduled
});
await context.SaveChangesAsync();
var boardService = new WorkOrderBoardService(
new WorkOrderBoardDataService(context),
WorkOrderAccountTestHelpers.Resolver(context));
var response = await boardService.GetBoardAsync(new WorkOrderBoardQueryDto
{
WeekStart = weekStart
}, WorkOrderAccountTestHelpers.OrgWideAdmin(), null);
Assert.Single(response.Scheduled);
Assert.True(response.Scheduled[0].AvetaRequired);
Assert.Equal(new DateOnly(2026, 5, 8), response.Scheduled[0].OriginalDate);
Assert.Equal(new DateOnly(2026, 5, 4), response.Scheduled[0].OriginalWeek);
}
[Fact] [Fact]
public async Task GetBoardAsync_InvalidWeekWindow_Throws() public async Task GetBoardAsync_InvalidWeekWindow_Throws()
{ {

View file

@ -1388,4 +1388,80 @@ public class WorkOrderBoardUpdateServiceTests
Assert.True(persisted.IsAddOn); Assert.True(persisted.IsAddOn);
Assert.Empty(await context.WorkOrderAuditLogs.Where(a => a.FieldName == "IsAddOn").ToListAsync()); Assert.Empty(await context.WorkOrderAuditLogs.Where(a => a.FieldName == "IsAddOn").ToListAsync());
} }
[Fact]
public async Task PatchField_AvetaRequiredTrue_PersistsAndAudits()
{
var (context, service) = CreateSut();
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.AvetaRequired,
Value = "true",
WorkOrderVersion = ToVersion(wo)
}, "actor-1");
Assert.True(result.AvetaRequired);
Assert.True((await context.workOrders.FindAsync(1))!.AvetaRequired);
Assert.Contains(
await context.WorkOrderAuditLogs.ToListAsync(),
a => a.FieldName == "AvetaRequired" && a.Action == AuditActionType.FieldChanged.ToString());
}
[Fact]
public async Task PatchField_AvetaRequiredFalse_ClearsFlag()
{
var (context, service) = CreateSut();
var wo = new WorkOrder
{
Id = 1,
LifecycleStatus = LifecycleStatus.Incomplete,
AvetaRequired = 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.AvetaRequired,
Value = "false",
WorkOrderVersion = ToVersion(wo)
}, "actor-1");
Assert.False(result.AvetaRequired);
Assert.False((await context.workOrders.FindAsync(1))!.AvetaRequired);
}
[Fact]
public async Task PatchField_AvetaRequiredInvalid_ThrowsInvalidValue()
{
var (context, service) = CreateSut();
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 ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
{
Field = WorkOrderBoardFieldNames.AvetaRequired,
Value = "yes",
WorkOrderVersion = ToVersion(wo)
}, "actor-1"));
Assert.Equal("InvalidValue", ex.Code);
}
} }

View file

@ -19,4 +19,17 @@ public class WorkOrderMigrationDiscoveryTests
Assert.Contains("20260713120000_Phase7_ExternalWorkOrderIdUnique", migrations); Assert.Contains("20260713120000_Phase7_ExternalWorkOrderIdUnique", migrations);
} }
[Fact]
public void SH117_AvetaRequired_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("20260824150000_SH117_AvetaRequired", migrations);
}
} }