shoc-backend/SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs

133 lines
5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
namespace SeaHaven.Services.Helpers
{
public static class WorkOrderDerivedFields
{
/// <summary>
/// Past due overlay from Schedule On — never a WorkOrderType. Matches SHOC board design (SH-185).
/// Changing Due Date alone must never trigger or clear it.
/// </summary>
public static bool IsPastDue(WorkOrder workOrder, DateOnly? asOf = null)
{
var today = asOf ?? DateOnly.FromDateTime(DateTime.UtcNow);
if (workOrder.ScheduledDate == null)
return false;
if (LifecycleStatusMapper.IsTerminal(workOrder.LifecycleStatus))
return false;
return DateOnly.FromDateTime(workOrder.ScheduledDate.Value.Date) < today;
}
/// <summary>
/// Board read-model past-due check against ScheduledDate. Compares UTC calendar dates;
/// business/location timezone support is planned for a later phase.
/// </summary>
public static bool IsPastDue(
DateTime? scheduledDate,
LifecycleStatus? lifecycleStatus,
DateTime? utcNow = null)
{
if (!scheduledDate.HasValue)
return false;
if (LifecycleStatusMapper.IsTerminal(lifecycleStatus))
return false;
var today = (utcNow ?? DateTime.UtcNow).Date;
return scheduledDate.Value.Date < today;
}
/// <summary>
/// Sets initial lifecycle on create. New WOs start Incomplete unless auto-scheduled.
/// </summary>
public static void SetInitialLifecycleStatus(WorkOrder workOrder)
{
workOrder.LifecycleStatus ??= LifecycleStatus.Incomplete;
ApplyAutoScheduleIfEligible(workOrder);
}
/// <summary>
/// Promote Incomplete/Pending to Scheduled, or demote Scheduled to Pending, from Schedule On.
/// 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>
public static bool ApplyAutoScheduleIfEligible(WorkOrder workOrder)
{
var before = workOrder.LifecycleStatus;
ApplyLifecycleFromSchedule(workOrder);
return before != LifecycleStatus.Scheduled
&& workOrder.LifecycleStatus == LifecycleStatus.Scheduled;
}
/// <summary>
/// Maps scheduled date to Mon–Fri board column. Phase 1 uses the stored
/// date's day-of-week (UTC); timezone-aware grouping is planned for a later phase.
/// </summary>
public static string? GetDayGroup(DateTime? scheduledDate)
{
if (!scheduledDate.HasValue)
return null;
return scheduledDate.Value.DayOfWeek switch
{
DayOfWeek.Monday => "monday",
DayOfWeek.Tuesday => "tuesday",
DayOfWeek.Wednesday => "wednesday",
DayOfWeek.Thursday => "thursday",
DayOfWeek.Friday => "friday",
_ => null
};
}
public static string? FormatApptTime(DateTime? start, DateTime? end)
{
if (!start.HasValue)
return null;
if (end.HasValue)
return $"{start.Value:HH:mm} – {end.Value:HH:mm}";
return start.Value.ToString("HH:mm");
}
public static string GetLifecycleStatusLabel(LifecycleStatus? status)
=> status.HasValue ? LifecycleStatusMapper.ToFeLabel(status.Value) : "";
}
}