feat(work-orders): apply schedule lifecycle promote and demote

This commit is contained in:
Arthur Bassi 2026-09-08 16:21:14 -03:00
parent 335390f746
commit e12b3ea54b
2 changed files with 87 additions and 22 deletions

View file

@ -50,25 +50,50 @@ namespace SeaHaven.Services.Helpers
} }
/// <summary> /// <summary>
/// SHOC rule: specific scheduled date + assignee + Incomplete → Scheduled. /// Promote Incomplete/Pending to Scheduled, or demote Scheduled to Pending, from Schedule On.
/// Week-only targets do not auto-schedule. /// Assignee is not required. Week-only is not a concrete date.
/// </summary>
public static List<WorkOrderBoardFieldMutations.BoardFieldChange> ApplyLifecycleFromSchedule(WorkOrder workOrder)
{
var changes = new List<WorkOrderBoardFieldMutations.BoardFieldChange>();
LifecycleStatus? next = null;
if (WorkOrderBoardMutationRules.ShouldPromoteToScheduled(
workOrder.LifecycleStatus,
workOrder.ScheduledDate,
workOrder.ScheduleWeekOnly))
{
next = LifecycleStatus.Scheduled;
}
else if (WorkOrderBoardMutationRules.ShouldDemoteFromScheduled(
workOrder.LifecycleStatus,
workOrder.ScheduledDate,
workOrder.ScheduleWeekOnly))
{
next = LifecycleStatus.Pending;
}
if (next == null || workOrder.LifecycleStatus == next)
return changes;
var old = workOrder.LifecycleStatus?.ToString();
workOrder.LifecycleStatus = next;
workOrder.Status = GetLifecycleStatusLabel(next);
changes.Add(WorkOrderBoardFieldMutations.BoardFieldChange.StatusChanged(
"LifecycleStatus",
old,
next.Value.ToString()));
return changes;
}
/// <summary>
/// Applies schedule-driven lifecycle (promote or demote). True when the row becomes Scheduled.
/// </summary> /// </summary>
public static bool ApplyAutoScheduleIfEligible(WorkOrder workOrder) public static bool ApplyAutoScheduleIfEligible(WorkOrder workOrder)
{ {
if (workOrder.LifecycleStatus != LifecycleStatus.Incomplete) var before = workOrder.LifecycleStatus;
return false; ApplyLifecycleFromSchedule(workOrder);
return before != LifecycleStatus.Scheduled
if (workOrder.ScheduleWeekOnly == true) && workOrder.LifecycleStatus == LifecycleStatus.Scheduled;
return false;
if (workOrder.ScheduledDate == null)
return false;
if (string.IsNullOrWhiteSpace(workOrder.AssignTo))
return false;
workOrder.LifecycleStatus = LifecycleStatus.Scheduled;
return true;
} }
/// <summary> /// <summary>

View file

@ -83,19 +83,48 @@ public class WorkOrderDerivedFieldsTests
} }
[Fact] [Fact]
public void ApplyAutoSchedule_WhenDateAndAssignee_PromotesToScheduled() public void ApplyAutoSchedule_WhenDateWithoutAssignee_PromotesToScheduled()
{ {
var wo = new WorkOrder var wo = new WorkOrder
{ {
LifecycleStatus = LifecycleStatus.Incomplete, LifecycleStatus = LifecycleStatus.Incomplete,
ScheduledDate = new DateTime(2026, 6, 25), ScheduledDate = new DateTime(2026, 6, 25)
AssignTo = "dispatcher-1"
}; };
Assert.True(WorkOrderDerivedFields.ApplyAutoScheduleIfEligible(wo)); Assert.True(WorkOrderDerivedFields.ApplyAutoScheduleIfEligible(wo));
Assert.Equal(LifecycleStatus.Scheduled, wo.LifecycleStatus); Assert.Equal(LifecycleStatus.Scheduled, wo.LifecycleStatus);
} }
[Fact]
public void ApplyLifecycleFromSchedule_PendingWithDate_PromotesToScheduled()
{
var wo = new WorkOrder
{
LifecycleStatus = LifecycleStatus.Pending,
ScheduledDate = new DateTime(2026, 6, 25)
};
var changes = WorkOrderDerivedFields.ApplyLifecycleFromSchedule(wo);
Assert.Equal(LifecycleStatus.Scheduled, wo.LifecycleStatus);
Assert.Single(changes);
Assert.Equal(AuditActionType.StatusChanged, changes[0].Action);
}
[Fact]
public void ApplyLifecycleFromSchedule_ScheduledWithoutDate_DemotesToPending()
{
var wo = new WorkOrder
{
LifecycleStatus = LifecycleStatus.Scheduled,
ScheduledDate = null
};
var changes = WorkOrderDerivedFields.ApplyLifecycleFromSchedule(wo);
Assert.Equal(LifecycleStatus.Pending, wo.LifecycleStatus);
Assert.Single(changes);
Assert.Equal(LifecycleStatus.Pending.ToString(), changes[0].NewValue);
}
[Fact] [Fact]
public void ApplyAutoSchedule_WeekOnly_DoesNotPromote() public void ApplyAutoSchedule_WeekOnly_DoesNotPromote()
{ {
@ -103,7 +132,6 @@ public class WorkOrderDerivedFieldsTests
{ {
LifecycleStatus = LifecycleStatus.Incomplete, LifecycleStatus = LifecycleStatus.Incomplete,
ScheduledDate = new DateTime(2026, 6, 25), ScheduledDate = new DateTime(2026, 6, 25),
AssignTo = "dispatcher-1",
ScheduleWeekOnly = true, ScheduleWeekOnly = true,
TargetWeek = new DateOnly(2026, 6, 22) TargetWeek = new DateOnly(2026, 6, 22)
}; };
@ -112,13 +140,25 @@ public class WorkOrderDerivedFieldsTests
Assert.Equal(LifecycleStatus.Incomplete, wo.LifecycleStatus); Assert.Equal(LifecycleStatus.Incomplete, wo.LifecycleStatus);
} }
[Fact]
public void ApplyLifecycleFromSchedule_InProgress_UnchangedWhenDateCleared()
{
var wo = new WorkOrder
{
LifecycleStatus = LifecycleStatus.InProgress,
ScheduledDate = null
};
Assert.Empty(WorkOrderDerivedFields.ApplyLifecycleFromSchedule(wo));
Assert.Equal(LifecycleStatus.InProgress, wo.LifecycleStatus);
}
[Fact] [Fact]
public void SetInitialLifecycleStatus_NewWo_StartsIncompleteThenAutoSchedules() public void SetInitialLifecycleStatus_NewWo_StartsIncompleteThenAutoSchedules()
{ {
var wo = new WorkOrder var wo = new WorkOrder
{ {
ScheduledDate = new DateTime(2026, 6, 25), ScheduledDate = new DateTime(2026, 6, 25)
AssignTo = "dispatcher-1"
}; };
WorkOrderDerivedFields.SetInitialLifecycleStatus(wo); WorkOrderDerivedFields.SetInitialLifecycleStatus(wo);