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