shoc-backend/SeaHaven.DataServices/Helpers/PendingUpliftCancellation.cs
Alexandre Brandizzi 99499c3281 Cancel pending uplifts when the CRM cancels a work order
The webhook and reconciliation saves now stage the same pending-uplift
cancellation, with its own sync audit row, as the board cancel. The rule
and the write live in one data-layer helper so the paths cannot drift.
2026-09-25 18:48:44 -03:00

79 lines
3.4 KiB
C#

using Data.SeaHavenIndustries;
using Data.SeaHavenIndustries.Enums;
using Microsoft.EntityFrameworkCore;
namespace SeaHaven.DataServices.Helpers
{
/// <summary>
/// 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,
/// the cancel endpoint, CRM webhook and reconciliation) asks <see cref="Applies"/> 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.
/// </summary>
public static class PendingUpliftCancellation
{
public const string AuditAction = "uplift_cancel";
public const string CancelledStatus = "Withdrawn";
private static readonly string[] PendingStatuses = { "Pending", "ChangesRequested" };
/// <summary>True when a lifecycle change moves a work order into Canceled.</summary>
public static bool Applies(LifecycleStatus? before, LifecycleStatus? after)
=> before != LifecycleStatus.Canceled && after == LifecycleStatus.Canceled;
/// <summary>
/// Uplift requests that belong to a work order: on a live dispatch that the work
/// order owns or is linked to.
/// </summary>
internal static IQueryable<DispatchUpliftRequest> 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)));
/// <summary>
/// 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.
/// </summary>
internal static async Task<int> 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;
}
}
}