using System.Security.Claims; using Data.SeaHavenIndustries.Enums; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; namespace SeaHaven.Services.Implementation { public class WorkOrderBoardCancelService : IWorkOrderBoardCancelService { private readonly IWorkOrderBoardMutationDataService _mutationData; private readonly IWorkOrderBoardService _boardService; private readonly IWorkOrderAuditService _auditService; private readonly IWorkOrderUpliftService _upliftService; private readonly IUpliftDataService _upliftData; public WorkOrderBoardCancelService( IWorkOrderBoardMutationDataService mutationData, IWorkOrderBoardService boardService, IWorkOrderAuditService auditService, IWorkOrderUpliftService upliftService, IUpliftDataService upliftData) { _mutationData = mutationData; _boardService = boardService; _auditService = auditService; _upliftService = upliftService; _upliftData = upliftData; } public async Task CancelAsync( int workOrderId, ClaimsPrincipal user, string? actorId) { // SH-196: run the whole cancel under the same per-work-order gate that uplift // create uses. Previously the pending-uplift read happened outside any gate, so // an in-flight create could commit after that read and leave a pending uplift on // a Canceled work order, breaking "cancelling a WO with a pending uplift cancels // that uplift in the same action". await _upliftData.ExecuteWorkOrderMutationAsync( workOrderId, async ct => { var workOrder = await _mutationData.GetTrackedWorkOrderAsync(workOrderId, ct); if (workOrder == null) throw new WorkOrderBoardValidationException("NotFound", "Work order not found."); // Already canceled: nothing to mutate, the row is returned below. if (workOrder.LifecycleStatus == LifecycleStatus.Canceled) return false; if (workOrder.LifecycleStatus == LifecycleStatus.Completed) throw new WorkOrderBoardValidationException("CancelNotAllowed", "Work order cannot be canceled in its current status."); var oldStatus = workOrder.LifecycleStatus?.ToString() ?? workOrder.Status ?? ""; workOrder.LifecycleStatus = LifecycleStatus.Canceled; workOrder.Status = WorkOrderDerivedFields.GetLifecycleStatusLabel(LifecycleStatus.Canceled); if (workOrder.LegacyStatus == null && workOrder.Status != null) workOrder.LegacyStatus = workOrder.Status; await _upliftService.WithdrawPendingForWorkOrderAsync(workOrderId, actorId, ct); await _auditService.StageStatusChangedAsync(workOrderId, oldStatus, LifecycleStatus.Canceled.ToString(), actorId); await _mutationData.SaveAsync(ct); return true; }, CancellationToken.None); var row = await _boardService.GetBoardRowAsync(workOrderId, user); return row ?? throw new WorkOrderBoardValidationException("NotFound", "Work order not found."); } } }