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