using System.Transactions; using SIGCM2.Application.Abstractions; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Audit; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.PuntosDeVenta.Create; public sealed class CreatePuntoDeVentaCommandHandler : ICommandHandler { private readonly IPuntoDeVentaRepository _repo; private readonly IMedioRepository _medioRepo; private readonly IAuditLogger _audit; public CreatePuntoDeVentaCommandHandler( IPuntoDeVentaRepository repo, IMedioRepository medioRepo, IAuditLogger audit) { _repo = repo; _medioRepo = medioRepo; _audit = audit; } public async Task Handle(CreatePuntoDeVentaCommand command) { // Validate medio exists and is active (REQ-PDV-001, -002) var medio = await _medioRepo.GetByIdAsync(command.MedioId) ?? throw new MedioNotFoundException(command.MedioId); if (!medio.Activo) throw new MedioInactivoException(medio.Id); // Check uniqueness NumeroAFIP within Medio (REQ-PDV-003) var exists = await _repo.ExistsByNumeroAFIPInMedioAsync(command.MedioId, command.NumeroAFIP, excludeId: null); if (exists) throw new NumeroAFIPDuplicadoException(command.MedioId, command.NumeroAFIP); var pdv = PuntoDeVenta.ForCreation(command.MedioId, command.NumeroAFIP, command.Nombre, command.Descripcion); using var tx = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }, TransactionScopeAsyncFlowOption.Enabled); var newId = await _repo.AddAsync(pdv); // fail-closed: if LogAsync throws, tx rolls back (REQ-PDV-010) await _audit.LogAsync( action: "punto_de_venta.create", targetType: "PuntoDeVenta", targetId: newId.ToString(), metadata: new { after = new { pdv.MedioId, pdv.NumeroAFIP, pdv.Nombre, pdv.Descripcion, }, }); tx.Complete(); return new PuntoDeVentaCreatedDto( Id: newId, MedioId: pdv.MedioId, NumeroAFIP: pdv.NumeroAFIP, Nombre: pdv.Nombre, Descripcion: pdv.Descripcion, Activo: pdv.Activo); } }