namespace SeaHaven.Services.Helpers { /// /// Server-side Add-On cutoff: Saturday 23:59:59 UTC of the week prior to the schedule week. /// SH-184: recalculated whenever scheduling is set, changed, or cleared; audited on change. /// Create may accept a client hint when unscheduled; schedule present ignores override. /// public static class WorkOrderAddonCutoff { /// /// Cutoff = Monday-of(anchor) − 2 days, end of day UTC (prior-week Saturday 23:59:59). /// public static DateTime GetAddonCutoffUtc(DateTime anchor) { var monday = WorkOrderBoardMutationRules.GetWeekStartMonday(anchor); var saturday = monday.Date.AddDays(-2); return DateTime.SpecifyKind( saturday.AddHours(23).AddMinutes(59).AddSeconds(59), DateTimeKind.Utc); } public static DateTime GetAddonCutoffUtc(DateOnly anchor) => GetAddonCutoffUtc(anchor.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc)); /// /// With schedule (ScheduledDate or TargetWeek): IsAddOn when createdAtUtc is after cutoff. /// Without schedule: client (default false). /// public static bool ComputeIsAddOnAtCreation( DateTime createdAtUtc, DateTime? scheduledDate, DateOnly? targetWeek, bool? manualOverride) { return ComputeFromScheduleAnchor(createdAtUtc, scheduledDate, targetWeek, manualOverride); } /// /// SH-184 authoritative recalc after schedule set/change/clear. No manual override on patch. /// Cleared schedule yields false. /// public static bool RecalculateIsAddOnFromSchedule( DateTime createdAtUtc, DateTime? scheduledDate, DateOnly? targetWeek) => ComputeFromScheduleAnchor(createdAtUtc, scheduledDate, targetWeek, manualOverride: false); private static bool ComputeFromScheduleAnchor( DateTime createdAtUtc, DateTime? scheduledDate, DateOnly? targetWeek, bool? manualOverride) { DateTime? anchor = null; if (scheduledDate.HasValue) anchor = scheduledDate.Value; else if (targetWeek.HasValue) anchor = targetWeek.Value.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc); if (!anchor.HasValue) return manualOverride ?? false; var cutoff = GetAddonCutoffUtc(anchor.Value); var created = NormalizeUtc(createdAtUtc); return created > cutoff; } private static DateTime NormalizeUtc(DateTime value) { if (value.Kind == DateTimeKind.Utc) return value; if (value.Kind == DateTimeKind.Local) return value.ToUniversalTime(); return DateTime.SpecifyKind(value, DateTimeKind.Utc); } } }