shoc-backend/SeaHaven.Services/Helpers/WorkOrderAddonCutoff.cs
Alexandre Brandizzi b9f9725c73 fix: recalculate add-on at reschedule time, not creation time
A reschedule moves the work order into its new week at the moment of the
reschedule, so Add-On now compares that moment with the new week's cutoff,
matching the prototype. Moves within the same week keep the current value.
Previously an older work order moved into a week already past its cutoff
stayed off because it was created before that cutoff (SH-412).
2026-10-01 16:10:19 -03:00

99 lines
4.1 KiB
C#
Raw 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.

namespace SeaHaven.Services.Helpers
{
/// <summary>
/// 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.
/// At creation the WO arrives when it is created; on a reschedule it arrives when it is moved.
/// Create may accept a client hint when unscheduled; schedule present ignores override.
/// </summary>
public static class WorkOrderAddonCutoff
{
/// <summary>
/// Cutoff = Monday-of(anchor) − 2 days, end of day UTC (prior-week Saturday 23:59:59).
/// </summary>
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));
/// <summary>
/// With schedule (ScheduledDate or TargetWeek): IsAddOn when createdAtUtc is after cutoff.
/// Without schedule: client <paramref name="manualOverride"/> (default false).
/// </summary>
public static bool ComputeIsAddOnAtCreation(
DateTime createdAtUtc,
DateTime? scheduledDate,
DateOnly? targetWeek,
bool? manualOverride)
{
return ComputeFromScheduleAnchor(createdAtUtc, scheduledDate, targetWeek, manualOverride);
}
/// <summary>
/// SH-412: a reschedule into another week makes the WO arrive in that week at the moment
/// of the reschedule, so Add-On compares <paramref name="rescheduledAtUtc"/> (not the
/// creation time) with the new week's cutoff. A move within the same week keeps the
/// current value; a cleared schedule yields false. Matches the prototype's reschedule rule.
/// </summary>
public static bool RecalculateIsAddOnOnReschedule(
bool currentIsAddOn,
DateTime? previousScheduledDate,
DateOnly? previousTargetWeek,
DateTime? scheduledDate,
DateOnly? targetWeek,
DateTime rescheduledAtUtc)
{
var anchor = ScheduleAnchor(scheduledDate, targetWeek);
if (!anchor.HasValue)
return false;
var previousAnchor = ScheduleAnchor(previousScheduledDate, previousTargetWeek);
if (previousAnchor.HasValue
&& WorkOrderBoardMutationRules.GetWeekStartMonday(previousAnchor.Value)
== WorkOrderBoardMutationRules.GetWeekStartMonday(anchor.Value))
return currentIsAddOn;
return NormalizeUtc(rescheduledAtUtc) > GetAddonCutoffUtc(anchor.Value);
}
private static DateTime? ScheduleAnchor(DateTime? scheduledDate, DateOnly? targetWeek)
{
if (scheduledDate.HasValue)
return scheduledDate.Value;
if (targetWeek.HasValue)
return targetWeek.Value.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
return null;
}
private static bool ComputeFromScheduleAnchor(
DateTime createdAtUtc,
DateTime? scheduledDate,
DateOnly? targetWeek,
bool? manualOverride)
{
var anchor = ScheduleAnchor(scheduledDate, targetWeek);
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);
}
}
}