using SIGCM2.Application.Abstractions; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.PuntosDeVenta.Reservar; /// /// Reserva el próximo número correlativo para (PdvId × TipoComprobante) ejecutando /// usp_ReservarNumeroComprobante vía el repositorio. /// /// NOTAS DE DISEÑO (AD4, AD9): /// - NO se envuelve en TransactionScope: el SP ya es atómico bajo SERIALIZABLE. /// Un TransactionScope ambiente aquí escalaría a DTC → innecesario. /// - NO usa Polly: no está en el proyecto. Retry deadlock con bucle simple. /// - Infrastructure traduce SqlException 1205 → DeadlockTransientException. /// - Backoff en ms: [50, 150, 450] — 3 intentos máximo. /// - La auditoría de reservas corre solo vía Temporal Tables (AD8). /// public sealed class ReservarNumeroCommandHandler : ICommandHandler { private readonly IPuntoDeVentaRepository _repo; private readonly int[] _deadlockBackoffMs; private static readonly int[] DefaultBackoffMs = [50, 150, 450]; public ReservarNumeroCommandHandler(IPuntoDeVentaRepository repo) : this(repo, DefaultBackoffMs) { } /// Constructor with custom backoff for testing (e.g., [0,0,0] for fast tests). public ReservarNumeroCommandHandler(IPuntoDeVentaRepository repo, int[] deadlockBackoffMs) { _repo = repo; _deadlockBackoffMs = deadlockBackoffMs; } public async Task Handle(ReservarNumeroCommand command) { for (var i = 0; ; i++) { try { var numero = await _repo.ReservarNumeroAsync(command.PuntoDeVentaId, command.TipoComprobante); return new ReservaNumeroDto(command.TipoComprobante, numero); } catch (DeadlockTransientException) when (i < _deadlockBackoffMs.Length) { // Deadlock — retry with backoff await Task.Delay(_deadlockBackoffMs[i]); } // All other exceptions bubble up immediately } } }