using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; namespace SeaHaven.Services.Helpers { public static class WorkOrderDerivedFields { /// /// 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. /// 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; } /// /// Board read-model past-due check against ScheduledDate. Compares UTC calendar dates; /// business/location timezone support is planned for a later phase. /// 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; } /// /// Sets initial lifecycle on create. New WOs start Incomplete unless auto-scheduled. /// public static void SetInitialLifecycleStatus(WorkOrder workOrder) { workOrder.LifecycleStatus ??= LifecycleStatus.Incomplete; ApplyAutoScheduleIfEligible(workOrder); } /// /// SHOC rule: specific scheduled date + assignee + Incomplete → Scheduled. /// Week-only targets do not auto-schedule. /// 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; } /// /// 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. /// 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) : ""; } }