From efe4f5f0b98ab36d4450643573dda103adde33d6 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Thu, 1 Oct 2026 16:19:13 -0300 Subject: [PATCH] test: pin add-on across week-only toggles and date-less edits Keep the add-on recalculation on the week-only toggle so a future schedule side effect cannot leave a stale tag, and cover that a toggle and an edit without Schedule On leave Add-On unchanged. --- .../WorkOrderBoardUpdateService.cs | 7 ++-- .../WorkOrderBoardUpdateServiceTests.cs | 36 +++++++++++++++++++ .../WorkOrderEditAddonRescheduleTests.cs | 30 ++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs index 72b26ed..edbc3c8 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs @@ -823,12 +823,15 @@ namespace SeaHaven.Services.Implementation return changes; } - // Toggling week-only mode keeps the scheduled week, so Add-On is unchanged (SH-412). - private static List ApplyScheduleWeekOnly(WorkOrder workOrder, string? value, string auditField) + private List ApplyScheduleWeekOnly(WorkOrder workOrder, string? value, string auditField) { + var previousDate = workOrder.ScheduledDate; + var previousTargetWeek = workOrder.TargetWeek; var change = ApplyBoolField(value, auditField, v => workOrder.ScheduleWeekOnly = v, () => workOrder.ScheduleWeekOnly); var changes = new List { change }; changes.AddRange(ApplyAutoScheduleSideEffects(workOrder)); + if (change.HasChanged) + AppendIsAddOnRecalculation(workOrder, previousDate, previousTargetWeek, changes); return changes; } diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs index 0cf861b..821ab59 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs @@ -2267,6 +2267,42 @@ public class WorkOrderBoardUpdateServiceTests Assert.True(persisted.IsAddOn); } + [Theory] + [InlineData(true, "true")] + [InlineData(false, "true")] + [InlineData(true, "false")] + [InlineData(false, "false")] + public async Task PatchField_ScheduleWeekOnlyToggle_KeepsIsAddOn(bool isAddOn, string weekOnly) + { + var (context, service) = CreateSut(new FixedTimeProvider()); + var wo = new WorkOrder + { + Id = 1, + LifecycleStatus = weekOnly == "true" ? LifecycleStatus.Scheduled : LifecycleStatus.Pending, + WorkOrderType = WorkOrderType.PM, + ScheduledDate = new DateTime(2026, 6, 25), + ScheduleWeekOnly = weekOnly != "true", + CreatedDate = new DateTime(2026, 6, 1, 0, 0, 0, DateTimeKind.Utc), + IsAddOn = isAddOn, + 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.ScheduleWeekOnly, + Value = weekOnly, + WorkOrderVersion = ToVersion(wo) + }, "actor-1"); + + Assert.Equal(isAddOn, result.IsAddOn); + var persisted = await context.workOrders.SingleAsync(w => w.Id == 1); + Assert.Equal(new DateTime(2026, 6, 25), persisted.ScheduledDate); + Assert.Equal(isAddOn, persisted.IsAddOn); + Assert.Empty(await context.WorkOrderAuditLogs.Where(a => a.FieldName == "IsAddOn").ToListAsync()); + } + [Theory] [InlineData("2026-07-08", false, "2026-06-25", true)] // into the current week, past its cutoff [InlineData("2026-06-24", true, "2026-07-01", false)] // into next week, before its cutoff diff --git a/SeaHavenIndustries.Tests/WorkOrderEditAddonRescheduleTests.cs b/SeaHavenIndustries.Tests/WorkOrderEditAddonRescheduleTests.cs index 0748c9f..dbda101 100644 --- a/SeaHavenIndustries.Tests/WorkOrderEditAddonRescheduleTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderEditAddonRescheduleTests.cs @@ -129,6 +129,36 @@ public sealed class WorkOrderEditAddonRescheduleTests Assert.DoesNotContain("IsAddOn", audit.FieldName); } + [Fact] + public async Task EditWorkorder_WithoutScheduledDate_LeavesScheduleAndAddOnUntouched() + { + // The edit form sends only changed fields: a null Schedule On means "not sent", never + // "clear". Clearing happens through the board patch, which turns Add-On off. + await using var context = CreateContext(); + context.workOrders.Add(new WorkOrder + { + Id = 1, + CreatedDate = new DateTime(2026, 6, 23, 0, 0, 0, DateTimeKind.Utc), + ScheduledDate = new DateTime(2026, 6, 24), + IsAddOn = true + }); + await context.SaveChangesAsync(); + + var result = await CreateController(context, new FixedTimeProvider()).Editworkorder(new EditWorkorder_DTO + { + Id = 1, + DueDate = new DateTime(2026, 6, 30) + }); + + Assert.IsType(result); + var persisted = await context.workOrders.SingleAsync(x => x.Id == 1); + Assert.Equal(new DateTime(2026, 6, 24), persisted.ScheduledDate); + Assert.True(persisted.IsAddOn); + Assert.DoesNotContain( + await context.WorkOrderAuditLogs.Where(x => x.WorkOrderId == 1).ToListAsync(), + x => x.FieldName?.Contains("IsAddOn", StringComparison.Ordinal) == true); + } + [Fact] public async Task EditWorkorder_FirstSchedule_SetsOriginalWeekAndPromotesIncomplete() {