revert(backend): eliminar handlers/endpoints/excepciones de reserva de numero ADM-008

Eliminar SecuenciaComprobante entity, TipoComprobante enum, DeadlockTransientException,
PuntoDeVentaInactivoException, carpetas Reservar/ y ProximoNumero/ de Application,
métodos ReservarNumeroAsync/GetUltimoNumeroAsync del repositorio, endpoints
POST /secuencias/.../reservar y GET /secuencias/.../proximo del controller,
y mapping PuntoDeVentaInactivoException del ExceptionFilter.
This commit is contained in:
2026-04-17 14:16:09 -03:00
parent 40482caf7b
commit 7d432a949a
15 changed files with 0 additions and 281 deletions

View File

@@ -1,6 +1,5 @@
using SIGCM2.Application.Common;
using SIGCM2.Domain.Entities;
using SIGCM2.Domain.Enums;
namespace SIGCM2.Application.Abstractions.Persistence;
@@ -11,6 +10,4 @@ public interface IPuntoDeVentaRepository
Task<bool> ExistsByNumeroAFIPInMedioAsync(int medioId, short numeroAFIP, int? excludeId = null, CancellationToken ct = default);
Task UpdateAsync(PuntoDeVenta pdv, CancellationToken ct = default);
Task<PagedResult<PuntoDeVenta>> GetPagedAsync(PuntosDeVentaQuery q, CancellationToken ct = default);
Task<int> ReservarNumeroAsync(int puntoDeVentaId, TipoComprobante tipo, CancellationToken ct = default);
Task<int?> GetUltimoNumeroAsync(int puntoDeVentaId, TipoComprobante tipo, CancellationToken ct = default);
}

View File

@@ -25,9 +25,7 @@ using SIGCM2.Application.PuntosDeVenta.Create;
using SIGCM2.Application.PuntosDeVenta.Deactivate;
using SIGCM2.Application.PuntosDeVenta.GetById;
using SIGCM2.Application.PuntosDeVenta.List;
using SIGCM2.Application.PuntosDeVenta.ProximoNumero;
using SIGCM2.Application.PuntosDeVenta.Reactivate;
using SIGCM2.Application.PuntosDeVenta.Reservar;
using SIGCM2.Application.PuntosDeVenta.Update;
using SIGCM2.Application.Secciones.Create;
using SIGCM2.Application.Secciones.Deactivate;
@@ -105,8 +103,6 @@ public static class DependencyInjection
services.AddScoped<ICommandHandler<ReactivatePuntoDeVentaCommand, PuntoDeVentaStatusDto>, ReactivatePuntoDeVentaCommandHandler>();
services.AddScoped<ICommandHandler<ListPuntosDeVentaQuery, PagedResult<PuntoDeVentaListItemDto>>, ListPuntosDeVentaQueryHandler>();
services.AddScoped<ICommandHandler<GetPuntoDeVentaByIdQuery, PuntoDeVentaDetailDto>, GetPuntoDeVentaByIdQueryHandler>();
services.AddScoped<ICommandHandler<ReservarNumeroCommand, ReservaNumeroDto>, ReservarNumeroCommandHandler>();
services.AddScoped<ICommandHandler<GetProximoNumeroQuery, ProximoNumeroDto>, GetProximoNumeroQueryHandler>();
// FluentValidation validators (scans entire Application assembly)
services.AddValidatorsFromAssemblyContaining<LoginCommandValidator>();

View File

@@ -1,5 +0,0 @@
using SIGCM2.Domain.Enums;
namespace SIGCM2.Application.PuntosDeVenta.ProximoNumero;
public sealed record GetProximoNumeroQuery(int PuntoDeVentaId, TipoComprobante TipoComprobante);

View File

@@ -1,27 +0,0 @@
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
namespace SIGCM2.Application.PuntosDeVenta.ProximoNumero;
/// <summary>
/// Consulta el próximo número disponible sin reservarlo (read-only, REQ-SEC-CMB-005).
/// Retorna UltimoNumero+1; si no existe fila devuelve 1.
/// </summary>
public sealed class GetProximoNumeroQueryHandler : ICommandHandler<GetProximoNumeroQuery, ProximoNumeroDto>
{
private readonly IPuntoDeVentaRepository _repo;
public GetProximoNumeroQueryHandler(IPuntoDeVentaRepository repo)
{
_repo = repo;
}
public async Task<ProximoNumeroDto> Handle(GetProximoNumeroQuery query)
{
var ultimoNumero = await _repo.GetUltimoNumeroAsync(query.PuntoDeVentaId, query.TipoComprobante);
var proximo = ultimoNumero.HasValue ? ultimoNumero.Value + 1 : 1;
return new ProximoNumeroDto(query.TipoComprobante, proximo);
}
}

View File

@@ -1,5 +0,0 @@
using SIGCM2.Domain.Enums;
namespace SIGCM2.Application.PuntosDeVenta.ProximoNumero;
public sealed record ProximoNumeroDto(TipoComprobante TipoComprobante, int ProximoNumero);

View File

@@ -1,5 +0,0 @@
using SIGCM2.Domain.Enums;
namespace SIGCM2.Application.PuntosDeVenta.Reservar;
public sealed record ReservaNumeroDto(TipoComprobante TipoComprobante, int NumeroReservado);

View File

@@ -1,5 +0,0 @@
using SIGCM2.Domain.Enums;
namespace SIGCM2.Application.PuntosDeVenta.Reservar;
public sealed record ReservarNumeroCommand(int PuntoDeVentaId, TipoComprobante TipoComprobante);

View File

@@ -1,53 +0,0 @@
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Domain.Exceptions;
namespace SIGCM2.Application.PuntosDeVenta.Reservar;
/// <summary>
/// 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: [25, 75, 200, 500, 1200] — 5 retries máximo (6 intentos totales).
/// - La auditoría de reservas corre solo vía Temporal Tables (AD8).
/// </summary>
public sealed class ReservarNumeroCommandHandler : ICommandHandler<ReservarNumeroCommand, ReservaNumeroDto>
{
private readonly IPuntoDeVentaRepository _repo;
private readonly int[] _deadlockBackoffMs;
private static readonly int[] DefaultBackoffMs = [25, 75, 200, 500, 1200];
public ReservarNumeroCommandHandler(IPuntoDeVentaRepository repo)
: this(repo, DefaultBackoffMs) { }
/// <summary>Constructor with custom backoff for testing (e.g., [0,0,0] for fast tests).</summary>
public ReservarNumeroCommandHandler(IPuntoDeVentaRepository repo, int[] deadlockBackoffMs)
{
_repo = repo;
_deadlockBackoffMs = deadlockBackoffMs;
}
public async Task<ReservaNumeroDto> 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
}
}
}