using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using Microsoft.EntityFrameworkCore;
namespace SeaHaven.DataServices.Helpers
{
///
/// The single rule and write for "a work order that becomes Canceled cancels its pending
/// uplifts in the same action". Every path that can cancel a work order (board update,
/// CRM webhook and reconciliation, legacy ingest) asks and
/// stages the cancellation into its own unit of work, so the uplift change and its own
/// audit row commit, or roll back, with the work order's status change.
///
public static class PendingUpliftCancellation
{
public const string AuditAction = "uplift_cancel";
public const string CancelledStatus = "Withdrawn";
private static readonly string[] PendingStatuses = { "Pending", "ChangesRequested" };
/// True when a lifecycle change moves a work order into Canceled.
public static bool Applies(LifecycleStatus? before, LifecycleStatus? after)
=> before != LifecycleStatus.Canceled && after == LifecycleStatus.Canceled;
///
/// True when a status-text change moves a work order into Cancelled. The legacy ingest
/// sync writes only the status text, never the lifecycle status.
///
public static bool AppliesToStatusText(string? before, string? after)
=> !IsCancelledText(before) && IsCancelledText(after);
///
/// True when a work order is cancelled by either signal: its lifecycle status, or the
/// status text a sync cancel writes. No new uplift may be requested on it.
///
public static bool IsCancelled(LifecycleStatus? lifecycle, string? statusText)
=> lifecycle == LifecycleStatus.Canceled || IsCancelledText(statusText);
private static bool IsCancelledText(string? status)
=> string.Equals(status, "Cancelled", StringComparison.OrdinalIgnoreCase)
|| string.Equals(status, "Canceled", StringComparison.OrdinalIgnoreCase);
///
/// Uplift requests that belong to a work order: on a live dispatch that the work
/// order owns or is linked to.
///
internal static IQueryable ForWorkOrder(ApplicationDbContext context, int workOrderId)
=> context.DispatchUpliftRequests.Where(u =>
(u.IsDeleted == null || u.IsDeleted == false)
&& u.Dispatch != null
&& (u.Dispatch.IsDeleted == null || u.Dispatch.IsDeleted == false)
&& (
u.Dispatch.WorkOrderId == workOrderId
|| u.Dispatch.DispatchWorkOrders!.Any(link => link.WorkOrderId == workOrderId)));
///
/// Stages (does not save) the cancellation of every pending uplift on the work order,
/// with one audit row per uplift. Returns how many uplifts were cancelled.
///
internal static async Task StageAsync(
ApplicationDbContext context,
int workOrderId,
string? actorId,
string actorType,
DateTime now,
CancellationToken cancellationToken)
{
var pending = await ForWorkOrder(context, workOrderId)
.Include(u => u.Dispatch)
.Where(u => PendingStatuses.Contains(u.Status))
.ToListAsync(cancellationToken);
foreach (var request in pending)
{
var previous = request.Status;
request.Status = CancelledStatus;
request.DecidedAt = now;
request.DecidedByUserId = actorId;
request.LastModificationTime = now;
context.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
{
WorkOrderId = workOrderId,
UserId = actorId,
FieldName = $"Dispatch {request.Dispatch!.DispatchNumber} Uplift",
OldValue = previous,
NewValue = CancelledStatus,
Action = AuditAction,
ActorType = actorType,
CreatedAt = now,
});
}
return pending.Count;
}
}
}