mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 16:19:07 +00:00
Compare commits
2 commits
49406fa32d
...
b14aac2acf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b14aac2acf | ||
|
|
c594d9f689 |
14 changed files with 251 additions and 39 deletions
|
|
@ -64,8 +64,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);
|
||||
}
|
||||
|
|
@ -73,8 +73,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)));
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,37 +6,38 @@ namespace SeaHaven.Services.Helpers
|
|||
public static class WorkOrderDerivedFields
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -231,7 +231,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}");
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -178,6 +178,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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<WorkOrderBoardValidationException>(() =>
|
||||
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()
|
||||
{
|
||||
|
|
@ -132,7 +272,7 @@ public class WorkOrderBoardUpdateServiceTests
|
|||
|
||||
Assert.Equal("InvalidValue", ex.Code);
|
||||
Assert.Contains("isPastDue", ex.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("scheduledDate", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("dueDate", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -107,7 +107,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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue