From 05dd262e807400fbe65fbe32c4194791196eb82b Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Tue, 28 Jul 2026 14:01:22 -0300 Subject: [PATCH] fix(work-orders): derive Past Due from DueDate instead of ScheduledDate Past Due must track the deadline (Due Date), not Schedule On. Keep dueDate and scheduledDate PATCH mutations independent so rescheduling alone does not clear Past Due. --- .../Helpers/WorkOrderBoardQueryFilters.cs | 8 +- .../Helpers/WorkOrderBoardSearchFilter.cs | 4 +- .../WorkOrderDomainJobDataService.cs | 4 +- .../Helpers/WorkOrderDerivedFields.cs | 15 +- .../Implementation/WorkOrderBoardService.cs | 2 +- .../WorkOrderBoardUpdateService.cs | 6 +- .../WorkOrderBoardMutationRulesTests.cs | 2 + .../WorkOrderBoardSearchTests.cs | 18 ++- .../WorkOrderBoardServiceTests.cs | 1 + .../WorkOrderBoardUpdateServiceTests.cs | 142 +++++++++++++++++- .../WorkOrderDerivedFieldsTests.cs | 65 +++++++- .../WorkOrderPhase6Tests.cs | 3 +- .../WorkOrderWeekRolledTests.cs | 11 +- docs/work-orders/phase-1/README.md | 7 +- 14 files changed, 250 insertions(+), 38 deletions(-) diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs index ba41941..f6d7bd6 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs @@ -71,8 +71,8 @@ namespace SeaHaven.DataServices.Helpers if (overdue && !hasTypes) { return query.Where(w => - w.ScheduledDate != null - && w.ScheduledDate.Value.Date < today + w.DueDate != null + && w.DueDate.Value.Date < today && w.LifecycleStatus != LifecycleStatus.Completed && w.LifecycleStatus != LifecycleStatus.Canceled); } @@ -80,8 +80,8 @@ namespace SeaHaven.DataServices.Helpers if (overdue) { return query.Where(w => - (w.ScheduledDate != null - && w.ScheduledDate.Value.Date < today + (w.DueDate != null + && w.DueDate.Value.Date < today && w.LifecycleStatus != LifecycleStatus.Completed && w.LifecycleStatus != LifecycleStatus.Canceled) || (w.WorkOrderType != null && typeFilters.Contains(w.WorkOrderType.Value))); diff --git a/SeaHaven.DataServices/Helpers/WorkOrderBoardSearchFilter.cs b/SeaHaven.DataServices/Helpers/WorkOrderBoardSearchFilter.cs index d6cf28d..3149f5c 100644 --- a/SeaHaven.DataServices/Helpers/WorkOrderBoardSearchFilter.cs +++ b/SeaHaven.DataServices/Helpers/WorkOrderBoardSearchFilter.cs @@ -60,8 +60,8 @@ namespace SeaHaven.DataServices.Helpers || (w.WorkOrderType == WorkOrderType.AddOn && ("add-on".Contains(s) || "addon".Contains(s))) || (w.WorkOrderType == WorkOrderType.Other && "other".Contains(s)) || ("overdue".Contains(s) - && w.ScheduledDate != null - && w.ScheduledDate.Value.Date < today + && w.DueDate != null + && w.DueDate.Value.Date < today && w.LifecycleStatus != LifecycleStatus.Completed && w.LifecycleStatus != LifecycleStatus.Canceled) || (w.LifecycleStatus == LifecycleStatus.Incomplete && "incomplete".Contains(s)) diff --git a/SeaHaven.DataServices/Implementation/WorkOrderDomainJobDataService.cs b/SeaHaven.DataServices/Implementation/WorkOrderDomainJobDataService.cs index 65d98bc..ca15639 100644 --- a/SeaHaven.DataServices/Implementation/WorkOrderDomainJobDataService.cs +++ b/SeaHaven.DataServices/Implementation/WorkOrderDomainJobDataService.cs @@ -120,7 +120,7 @@ namespace SeaHaven.DataServices.Implementation var batch = await _context.workOrders .Where(w => w.IsDeleted != true) .Where(w => w.istemplate != true) - .Where(w => w.ScheduledDate != null) + .Where(w => w.DueDate != null) .OrderBy(w => w.Id) .Skip(skip) .Take(batchSize) @@ -132,7 +132,7 @@ namespace SeaHaven.DataServices.Implementation foreach (var workOrder in batch) { examinedCount++; - var shouldBePastDue = workOrder.ScheduledDate!.Value.Date < today + var shouldBePastDue = workOrder.DueDate!.Value.Date < today && !TerminalStatuses.Contains(workOrder.LifecycleStatus); var hasFlag = workOrder.OperationalFlags.HasFlag(OperationalFlags.PastDue); diff --git a/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs b/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs index b64d16c..9cc77b9 100644 --- a/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs +++ b/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs @@ -6,37 +6,38 @@ namespace SeaHaven.Services.Helpers public static class WorkOrderDerivedFields { /// - /// Past due overlay — never a WorkOrderType. Matches SHOC board design. + /// Past due overlay from DueDate (deadline) — never a WorkOrderType. + /// Independent of ScheduledDate (planned work / Schedule On). /// public static bool IsPastDue(WorkOrder workOrder, DateOnly? asOf = null) { var today = asOf ?? DateOnly.FromDateTime(DateTime.UtcNow); - if (workOrder.ScheduledDate == null) + if (workOrder.DueDate == null) return false; if (LifecycleStatusMapper.IsTerminal(workOrder.LifecycleStatus)) return false; - return DateOnly.FromDateTime(workOrder.ScheduledDate.Value.Date) < today; + return DateOnly.FromDateTime(workOrder.DueDate.Value.Date) < today; } /// - /// Board read-model past-due check. Phase 1 compares UTC calendar dates; + /// Board read-model past-due check against DueDate. Compares UTC calendar dates; /// business/location timezone support is planned for a later phase. /// public static bool IsPastDue( - DateTime? scheduledDate, + DateTime? dueDate, LifecycleStatus? lifecycleStatus, DateTime? utcNow = null) { - if (!scheduledDate.HasValue) + if (!dueDate.HasValue) return false; if (LifecycleStatusMapper.IsTerminal(lifecycleStatus)) return false; var today = (utcNow ?? DateTime.UtcNow).Date; - return scheduledDate.Value.Date < today; + return dueDate.Value.Date < today; } /// diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardService.cs index 04bf63c..1e840a9 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardService.cs @@ -94,7 +94,7 @@ namespace SeaHaven.Services.Implementation RescheduleCount = row.RescheduleCount, CarriedOver = row.CarriedOver, WorkOrderType = row.WorkOrderType, - IsPastDue = WorkOrderDerivedFields.IsPastDue(row.ScheduledDate, row.LifecycleStatus, utcNow), + IsPastDue = WorkOrderDerivedFields.IsPastDue(row.DueDate, row.LifecycleStatus, utcNow), SiteCode = row.SiteCode, LocationName = row.LocationName, PocName = row.PocName, diff --git a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs index 3e8c108..e717efc 100644 --- a/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs +++ b/SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs @@ -61,9 +61,9 @@ namespace SeaHaven.Services.Implementation if (WorkOrderBoardMutationRules.IsReadOnly(workOrder.LifecycleStatus)) throw new WorkOrderBoardValidationException("CanceledReadOnly", "Work order is read-only in its current status."); - var isPastDue = WorkOrderDerivedFields.IsPastDue(workOrder.ScheduledDate, workOrder.LifecycleStatus); + var isPastDue = WorkOrderDerivedFields.IsPastDue(workOrder.DueDate, workOrder.LifecycleStatus); if (WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(canonicalField, isPastDue)) - throw new WorkOrderBoardValidationException("PastDueStatusBlocked", "Status cannot be changed while work order is past due. Reschedule first."); + throw new WorkOrderBoardValidationException("PastDueStatusBlocked", "Status cannot be changed while work order is past due. Update due date first."); _mutationData.SetExpectedWorkOrderVersion(workOrder, workOrderVersion); @@ -232,7 +232,7 @@ namespace SeaHaven.Services.Implementation if (string.Equals(value.Trim(), "Overdue", StringComparison.OrdinalIgnoreCase)) throw new WorkOrderBoardValidationException( "InvalidValue", - "Overdue is not a work order type; it is derived from scheduledDate (isPastDue)."); + "Overdue is not a work order type; it is derived from dueDate (isPastDue)."); if (!WorkOrderTypeMapper.TryParseFeLabel(value, out var parsed)) throw new WorkOrderBoardValidationException("InvalidValue", $"Invalid workOrderType: {value}"); diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardMutationRulesTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardMutationRulesTests.cs index 933aa80..4cda7f8 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardMutationRulesTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardMutationRulesTests.cs @@ -22,6 +22,8 @@ public class WorkOrderBoardMutationRulesTests WorkOrderBoardFieldNames.LifecycleStatus, isPastDue: true)); Assert.False(WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue( WorkOrderBoardFieldNames.ScheduledDate, isPastDue: true)); + Assert.False(WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue( + WorkOrderBoardFieldNames.DueDate, isPastDue: true)); } [Fact] diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardSearchTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardSearchTests.cs index 780c25c..510da77 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardSearchTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardSearchTests.cs @@ -342,7 +342,8 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 1, WorkOrderType = WorkOrderType.PM, - ScheduledDate = today.AddDays(-3), + DueDate = today.AddDays(-3), + ScheduledDate = today.AddDays(5), LifecycleStatus = LifecycleStatus.Scheduled, InternalWONumber = "10000000001" }, @@ -358,7 +359,8 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 3, WorkOrderType = WorkOrderType.Reactive, - ScheduledDate = today.AddDays(1), + DueDate = today.AddDays(1), + ScheduledDate = today.AddDays(-5), LifecycleStatus = LifecycleStatus.Scheduled, InternalWONumber = "10000000003" }); @@ -392,7 +394,8 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 1, WorkOrderType = WorkOrderType.PM, - ScheduledDate = today.AddDays(-3), + DueDate = today.AddDays(-3), + ScheduledDate = today.AddDays(5), LifecycleStatus = LifecycleStatus.Scheduled, InternalWONumber = "10000000001" }, @@ -408,6 +411,7 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 3, WorkOrderType = WorkOrderType.Reactive, + DueDate = today.AddDays(1), ScheduledDate = today.AddDays(1), LifecycleStatus = LifecycleStatus.Scheduled, InternalWONumber = "10000000003" @@ -444,7 +448,8 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 1, WorkOrderType = WorkOrderType.Other, - ScheduledDate = today.AddDays(5), + DueDate = today.AddDays(5), + ScheduledDate = today.AddDays(-5), LifecycleStatus = LifecycleStatus.Scheduled, InternalWONumber = "10000000001" }, @@ -452,7 +457,8 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 2, WorkOrderType = WorkOrderType.PM, - ScheduledDate = today.AddDays(-3), + DueDate = today.AddDays(-3), + ScheduledDate = today.AddDays(5), LifecycleStatus = LifecycleStatus.Scheduled, InternalWONumber = "10000000002" }, @@ -460,6 +466,7 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 3, WorkOrderType = WorkOrderType.Reactive, + DueDate = today.AddDays(-2), ScheduledDate = today.AddDays(-2), LifecycleStatus = LifecycleStatus.Completed, InternalWONumber = "10000000003" @@ -468,6 +475,7 @@ public class WorkOrderAdvancedSearchServiceTests { Id = 4, WorkOrderType = WorkOrderType.Reactive, + DueDate = today.AddDays(-1), ScheduledDate = today.AddDays(-1), LifecycleStatus = LifecycleStatus.Canceled, InternalWONumber = "10000000004" diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardServiceTests.cs index 92c4d36..3eca81d 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardServiceTests.cs @@ -391,6 +391,7 @@ public class WorkOrderBoardServiceTests { Id = 1, InternalWONumber = "10000000001", + DueDate = new DateTime(2026, 6, 20), ScheduledDate = new DateTime(2026, 6, 24, 12, 0, 0, DateTimeKind.Utc), LifecycleStatus = LifecycleStatus.Scheduled }); diff --git a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs index b775ee6..7c1b678 100644 --- a/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderBoardUpdateServiceTests.cs @@ -91,7 +91,8 @@ public class WorkOrderBoardUpdateServiceTests { Id = 1, LifecycleStatus = LifecycleStatus.Scheduled, - ScheduledDate = DateTime.UtcNow.Date.AddDays(-3), + DueDate = DateTime.UtcNow.Date.AddDays(-3), + ScheduledDate = DateTime.UtcNow.Date.AddDays(5), RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } }; context.workOrders.Add(wo); @@ -108,6 +109,145 @@ public class WorkOrderBoardUpdateServiceTests Assert.Equal("PastDueStatusBlocked", ex.Code); } + [Fact] + public async Task PatchField_RescheduleAlone_DoesNotClearPastDueBlock() + { + var (context, service) = CreateSut(); + var wo = new WorkOrder + { + Id = 1, + LifecycleStatus = LifecycleStatus.Scheduled, + DueDate = DateTime.UtcNow.Date.AddDays(-3), + ScheduledDate = DateTime.UtcNow.Date.AddDays(-1), + RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } + }; + context.workOrders.Add(wo); + await context.SaveChangesAsync(); + + var futureSchedule = DateTime.UtcNow.Date.AddDays(7).ToString("yyyy-MM-dd"); + var row = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto + { + Field = WorkOrderBoardFieldNames.ScheduledDate, + Value = futureSchedule, + WorkOrderVersion = ToVersion(wo) + }, "actor-1"); + + Assert.True(row.IsPastDue); + Assert.Equal(DateTime.UtcNow.Date.AddDays(7), row.ScheduledDate!.Value.Date); + + wo = await context.workOrders.SingleAsync(w => w.Id == 1); + var ex = await Assert.ThrowsAsync(() => + service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto + { + Field = WorkOrderBoardFieldNames.LifecycleStatus, + Value = "Complete", + WorkOrderVersion = ToVersion(wo) + }, "actor-1")); + + Assert.Equal("PastDueStatusBlocked", ex.Code); + } + + [Fact] + public async Task PatchField_FutureDueDate_ClearsPastDueAndAllowsStatusChange() + { + var (context, service) = CreateSut(); + var wo = new WorkOrder + { + Id = 1, + LifecycleStatus = LifecycleStatus.Scheduled, + DueDate = DateTime.UtcNow.Date.AddDays(-3), + ScheduledDate = DateTime.UtcNow.Date.AddDays(-1), + RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 } + }; + context.workOrders.Add(wo); + await context.SaveChangesAsync(); + + var futureDue = DateTime.UtcNow.Date.AddDays(10).ToString("yyyy-MM-dd"); + var afterDue = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto + { + Field = WorkOrderBoardFieldNames.DueDate, + Value = futureDue, + WorkOrderVersion = ToVersion(wo) + }, "actor-1"); + + Assert.False(afterDue.IsPastDue); + Assert.Equal(DateTime.UtcNow.Date.AddDays(-1), afterDue.ScheduledDate!.Value.Date); + + wo = await context.workOrders.SingleAsync(w => w.Id == 1); + var afterStatus = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto + { + Field = WorkOrderBoardFieldNames.LifecycleStatus, + Value = "Complete", + WorkOrderVersion = ToVersion(wo) + }, "actor-1"); + + Assert.Equal(LifecycleStatus.Completed, afterStatus.LifecycleStatus); + Assert.False(afterStatus.IsPastDue); + } + + [Fact] + public async Task PatchField_DueDate_DoesNotChangeScheduledDate() + { + var (context, service) = CreateSut(); + var scheduled = new DateTime(2026, 6, 20); + var wo = new WorkOrder + { + Id = 1, + LifecycleStatus = LifecycleStatus.Scheduled, + DueDate = new DateTime(2026, 6, 18), + ScheduledDate = scheduled, + RescheduleCount = 2, + 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.DueDate, + Value = "2026-07-01", + WorkOrderVersion = ToVersion(wo) + }, "actor-1"); + + Assert.Equal(new DateTime(2026, 7, 1), result.DueDate!.Value.Date); + Assert.Equal(scheduled, result.ScheduledDate); + Assert.Equal(2, result.RescheduleCount); + + var persisted = await context.workOrders.SingleAsync(w => w.Id == 1); + Assert.Equal(scheduled, persisted.ScheduledDate); + Assert.Equal(2, persisted.RescheduleCount); + } + + [Fact] + public async Task PatchField_ScheduledDate_DoesNotChangeDueDate() + { + var (context, service) = CreateSut(); + var due = new DateTime(2026, 6, 18); + var wo = new WorkOrder + { + Id = 1, + LifecycleStatus = LifecycleStatus.Scheduled, + DueDate = due, + ScheduledDate = new DateTime(2026, 6, 20), + 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.Equal(new DateTime(2026, 6, 25), result.ScheduledDate!.Value.Date); + Assert.Equal(due, result.DueDate); + + var persisted = await context.workOrders.SingleAsync(w => w.Id == 1); + Assert.Equal(due, persisted.DueDate); + } + [Fact] public async Task PatchField_RejectsOverdueAsWorkOrderType() { diff --git a/SeaHavenIndustries.Tests/WorkOrderDerivedFieldsTests.cs b/SeaHavenIndustries.Tests/WorkOrderDerivedFieldsTests.cs index 0d1f61c..49b5a61 100644 --- a/SeaHavenIndustries.Tests/WorkOrderDerivedFieldsTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderDerivedFieldsTests.cs @@ -6,28 +6,79 @@ namespace SeaHavenIndustries.Tests; public class WorkOrderDerivedFieldsTests { + private static readonly DateOnly Today = DateOnly.FromDateTime(DateTime.UtcNow); + private static readonly DateTime Past = DateTime.UtcNow.Date.AddDays(-2); + private static readonly DateTime Future = DateTime.UtcNow.Date.AddDays(3); + private static readonly DateTime TodayDate = DateTime.UtcNow.Date; + [Fact] - public void IsPastDue_WhenScheduledBeforeTodayAndNotTerminal_ReturnsTrue() + public void IsPastDue_WhenDuePastAndScheduleFuture_ReturnsTrue() { var wo = new WorkOrder { - ScheduledDate = DateTime.UtcNow.AddDays(-2), + DueDate = Past, + ScheduledDate = Future, LifecycleStatus = LifecycleStatus.Scheduled }; - Assert.True(WorkOrderDerivedFields.IsPastDue(wo, DateOnly.FromDateTime(DateTime.UtcNow))); + Assert.True(WorkOrderDerivedFields.IsPastDue(wo, Today)); + Assert.True(WorkOrderDerivedFields.IsPastDue(wo.DueDate, wo.LifecycleStatus)); } [Fact] - public void IsPastDue_WhenCompleted_ReturnsFalse() + public void IsPastDue_WhenDueFutureAndSchedulePast_ReturnsFalse() { var wo = new WorkOrder { - ScheduledDate = DateTime.UtcNow.AddDays(-2), - LifecycleStatus = LifecycleStatus.Completed + DueDate = Future, + ScheduledDate = Past, + LifecycleStatus = LifecycleStatus.Scheduled }; - Assert.False(WorkOrderDerivedFields.IsPastDue(wo, DateOnly.FromDateTime(DateTime.UtcNow))); + Assert.False(WorkOrderDerivedFields.IsPastDue(wo, Today)); + } + + [Fact] + public void IsPastDue_WhenDueDateNull_ReturnsFalse() + { + var wo = new WorkOrder + { + DueDate = null, + ScheduledDate = Past, + LifecycleStatus = LifecycleStatus.Scheduled + }; + + Assert.False(WorkOrderDerivedFields.IsPastDue(wo, Today)); + Assert.False(WorkOrderDerivedFields.IsPastDue(null, LifecycleStatus.Scheduled)); + } + + [Fact] + public void IsPastDue_WhenDueEqualsToday_ReturnsFalse() + { + var wo = new WorkOrder + { + DueDate = TodayDate, + ScheduledDate = Past, + LifecycleStatus = LifecycleStatus.Scheduled + }; + + Assert.False(WorkOrderDerivedFields.IsPastDue(wo, Today)); + } + + [Theory] + [InlineData(LifecycleStatus.Completed)] + [InlineData(LifecycleStatus.Canceled)] + public void IsPastDue_WhenTerminal_ReturnsFalse(LifecycleStatus status) + { + var wo = new WorkOrder + { + DueDate = Past, + ScheduledDate = Past, + LifecycleStatus = status + }; + + Assert.False(WorkOrderDerivedFields.IsPastDue(wo, Today)); + Assert.False(WorkOrderDerivedFields.IsPastDue(Past, status)); } [Fact] diff --git a/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs b/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs index d6bd47e..044567b 100644 --- a/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderPhase6Tests.cs @@ -111,7 +111,8 @@ public class WorkOrderDetailServiceTests Id = 1, InternalWONumber = "00000000001", LifecycleStatus = LifecycleStatus.Scheduled, - ScheduledDate = DateTime.UtcNow.AddDays(-2), + DueDate = DateTime.UtcNow.AddDays(-2), + ScheduledDate = DateTime.UtcNow.AddDays(3), Trade = "HVAC PM", WorkOrderType = WorkOrderType.PM, DocStatus = DocStatus.No, diff --git a/SeaHavenIndustries.Tests/WorkOrderWeekRolledTests.cs b/SeaHavenIndustries.Tests/WorkOrderWeekRolledTests.cs index bff60ca..539d4ab 100644 --- a/SeaHavenIndustries.Tests/WorkOrderWeekRolledTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderWeekRolledTests.cs @@ -240,12 +240,14 @@ public class WorkOrderWeekRolledTests new WorkOrder { Id = 1, + DueDate = pastDate, ScheduledDate = pastDate, LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 2, + DueDate = pastDate, ScheduledDate = pastDate, LifecycleStatus = LifecycleStatus.Completed, OperationalFlags = OperationalFlags.PastDue @@ -253,6 +255,7 @@ public class WorkOrderWeekRolledTests new WorkOrder { Id = 3, + DueDate = futureDate, ScheduledDate = futureDate, LifecycleStatus = LifecycleStatus.Scheduled, OperationalFlags = OperationalFlags.PastDue @@ -307,7 +310,8 @@ public class WorkOrderWeekRolledTests context.workOrders.Add(new WorkOrder { Id = 1, - ScheduledDate = pastDate, + DueDate = pastDate, + ScheduledDate = DateTime.UtcNow.Date.AddDays(5), LifecycleStatus = LifecycleStatus.Scheduled, OperationalFlags = OperationalFlags.None }); @@ -319,7 +323,7 @@ public class WorkOrderWeekRolledTests Assert.NotNull(row); Assert.True(row!.IsPastDue); - Assert.False(WorkOrderDerivedFields.IsPastDue(pastDate, LifecycleStatus.Scheduled) == false); + Assert.True(WorkOrderDerivedFields.IsPastDue(pastDate, LifecycleStatus.Scheduled)); } [Fact] @@ -331,7 +335,8 @@ public class WorkOrderWeekRolledTests context.workOrders.Add(new WorkOrder { Id = 1, - ScheduledDate = futureDate, + DueDate = futureDate, + ScheduledDate = DateTime.UtcNow.Date.AddDays(-3), LifecycleStatus = LifecycleStatus.Scheduled, OperationalFlags = OperationalFlags.PastDue }); diff --git a/docs/work-orders/phase-1/README.md b/docs/work-orders/phase-1/README.md index 660bfb3..449aac5 100644 --- a/docs/work-orders/phase-1/README.md +++ b/docs/work-orders/phase-1/README.md @@ -88,10 +88,13 @@ Lista opções para o filtro/dropdown de assignee do board. ### isPastDue ``` -ScheduledDate < UTC hoje -AND LifecycleStatus NOT IN (Complete, Canceled, Closed) +DueDate != null +AND DueDate < UTC hoje +AND LifecycleStatus NOT IN (Completed, Canceled) ``` +`DueDate` null → `isPastDue = false`. Independent of `ScheduledDate` (Schedule On). + Implementação: `SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs` ### Vendor / Appt