shoc-backend/SeaHaven.Services/Helpers/WorkOrderBoardPatchLifecycleRules.cs
Alexandre Brandizzi caba84ea08
fix(work-orders): reject forged automatic lifecycle statuses on board patch (SH-357, SH-358) (#120)
Incomplete and Scheduled are derived by the server. A direct lifecycleStatus
PATCH may only restate the status derivation already produced; any other
request to move a work order into an automatic state returns the stable
AutomaticLifecycleStatus validation error and leaves status and audit untouched.
2026-09-16 14:41:23 -03:00

52 lines
2.2 KiB
C#

using Data.SeaHavenIndustries.Enums;
using SeaHaven.Services.Exceptions;
namespace SeaHaven.Services.Helpers
{
/// <summary>
/// SH-169 manual transition guard for the board lifecycleStatus PATCH.
/// Incomplete and Scheduled are derived by the server; a direct request may only
/// restate what derivation already produces, never forge a new automatic state.
/// Past Due is a read-model overlay (<see cref="WorkOrderDerivedFields.IsPastDue(System.DateTime?, LifecycleStatus?, System.DateTime?)"/>),
/// not a <see cref="LifecycleStatus"/> value, so it cannot be requested at all.
/// </summary>
public static class WorkOrderBoardPatchLifecycleRules
{
public const string AutomaticStatusCode = "AutomaticLifecycleStatus";
public static bool IsAutomaticStatus(LifecycleStatus status)
=> status == LifecycleStatus.Incomplete || status == LifecycleStatus.Scheduled;
public static void EnsureRequestAllowed(
LifecycleStatus? current,
LifecycleStatus requested,
DateTime? scheduledDate,
bool? scheduleWeekOnly)
{
if (!IsAutomaticStatus(requested))
return;
if (requested == LifecycleStatus.Scheduled
&& !WorkOrderBoardMutationRules.HasConcreteSchedule(scheduledDate, scheduleWeekOnly))
{
throw new WorkOrderBoardValidationException(
"ScheduledRequiresDate",
"Scheduled requires a concrete scheduledDate when scheduleWeekOnly is not true.");
}
// why: the board client restates the derived status after a schedule patch; that is a no-op, not a forge.
if (current == requested)
return;
if (requested == LifecycleStatus.Scheduled
&& WorkOrderBoardMutationRules.ShouldPromoteToScheduled(current, scheduledDate, scheduleWeekOnly))
{
return;
}
throw new WorkOrderBoardValidationException(
AutomaticStatusCode,
$"{LifecycleStatusMapper.ToFeLabel(requested)} is set automatically and cannot be selected manually.");
}
}
}