mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-01 07:33:14 +00:00
Past Due follows ScheduledDate so Due Date alone cannot set or clear it. Auto-schedule and reschedule tests now expect the third IsAddOn audit field.
108 lines
3.7 KiB
C#
108 lines
3.7 KiB
C#
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>
|
||
/// SHOC rule: specific scheduled date + assignee + Incomplete → Scheduled.
|
||
/// Week-only targets do not auto-schedule.
|
||
/// </summary>
|
||
public static bool ApplyAutoScheduleIfEligible(WorkOrder workOrder)
|
||
{
|
||
if (workOrder.LifecycleStatus != LifecycleStatus.Incomplete)
|
||
return false;
|
||
|
||
if (workOrder.ScheduleWeekOnly == true)
|
||
return false;
|
||
|
||
if (workOrder.ScheduledDate == null)
|
||
return false;
|
||
|
||
if (string.IsNullOrWhiteSpace(workOrder.AssignTo))
|
||
return false;
|
||
|
||
workOrder.LifecycleStatus = LifecycleStatus.Scheduled;
|
||
return true;
|
||
}
|
||
|
||
/// <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) : "";
|
||
}
|
||
}
|