UDT-011: Localización Temporal Argentina (infra transversal) #25

Merged
dmolinari merged 24 commits from feature/UDT-011 into main 2026-04-18 13:57:49 +00:00
27 changed files with 164 additions and 59 deletions
Showing only changes of commit d69da5ff4c - Show all commits

View File

@@ -22,6 +22,7 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
private readonly IRolPermisoRepository _rolPermisoRepository;
private readonly ISecurityEventLogger _security;
private readonly ILogger<LoginCommandHandler> _logger;
private readonly TimeProvider _timeProvider;
public LoginCommandHandler(
IUsuarioRepository repository,
@@ -33,7 +34,8 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
AuthOptions authOptions,
IRolPermisoRepository rolPermisoRepository,
ISecurityEventLogger security,
ILogger<LoginCommandHandler> logger)
ILogger<LoginCommandHandler> logger,
TimeProvider timeProvider)
{
_repository = repository;
_hasher = hasher;
@@ -45,6 +47,7 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
_rolPermisoRepository = rolPermisoRepository;
_security = security;
_logger = logger;
_timeProvider = timeProvider;
}
public async Task<LoginResponseDto> Handle(LoginCommand command)
@@ -81,7 +84,7 @@ public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginRes
// Generate and persist refresh token — only the hash hits the DB
var rawRefresh = _refreshGenerator.Generate();
var hash = TokenHasher.Sha256Base64Url(rawRefresh);
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().UtcDateTime;
var ttl = TimeSpan.FromDays(_authOptions.RefreshTokenDays);
var entity = RefreshToken.IssueForNewFamily(
usuario.Id, hash, now, ttl,

View File

@@ -8,18 +8,24 @@ public sealed class LogoutCommandHandler : ICommandHandler<LogoutCommand, Logout
{
private readonly IRefreshTokenRepository _refreshRepo;
private readonly ISecurityEventLogger _security;
private readonly TimeProvider _timeProvider;
public LogoutCommandHandler(IRefreshTokenRepository refreshRepo, ISecurityEventLogger security)
public LogoutCommandHandler(
IRefreshTokenRepository refreshRepo,
ISecurityEventLogger security,
TimeProvider timeProvider)
{
_refreshRepo = refreshRepo;
_security = security;
_timeProvider = timeProvider;
}
public async Task<LogoutResponseDto> Handle(LogoutCommand command)
{
// Revoke all active tokens for the user across all families.
// Idempotent: 0 rows affected is not an error.
await _refreshRepo.RevokeAllActiveForUserAsync(command.UsuarioId, DateTime.UtcNow);
var now = _timeProvider.GetUtcNow().UtcDateTime;
await _refreshRepo.RevokeAllActiveForUserAsync(command.UsuarioId, now);
await _security.LogAsync("logout", "success", actorUserId: command.UsuarioId);
return new LogoutResponseDto(true, "Sesión cerrada correctamente");
}

View File

@@ -17,6 +17,7 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
private readonly IClientContext _clientCtx;
private readonly AuthOptions _authOptions;
private readonly ISecurityEventLogger _security;
private readonly TimeProvider _timeProvider;
public RefreshCommandHandler(
IRefreshTokenRepository refreshRepo,
@@ -25,7 +26,8 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
IRefreshTokenGenerator refreshGenerator,
IClientContext clientCtx,
AuthOptions authOptions,
ISecurityEventLogger security)
ISecurityEventLogger security,
TimeProvider timeProvider)
{
_refreshRepo = refreshRepo;
_usuarioRepo = usuarioRepo;
@@ -34,6 +36,7 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
_clientCtx = clientCtx;
_authOptions = authOptions;
_security = security;
_timeProvider = timeProvider;
}
public async Task<RefreshResponseDto> Handle(RefreshCommand command)
@@ -60,7 +63,7 @@ public sealed class RefreshCommandHandler : ICommandHandler<RefreshCommand, Refr
if (stored is null)
throw new InvalidRefreshTokenException();
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().UtcDateTime;
// 4. Reuse detection: already revoked → chain revocation and throw
if (stored.IsRevoked)

View File

@@ -11,11 +11,13 @@ public sealed class CreateIngresosBrutosCommandHandler
{
private readonly IIngresosBrutosRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public CreateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit)
public CreateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<IngresosBrutosDto> Handle(CreateIngresosBrutosCommand command)
@@ -51,7 +53,7 @@ public sealed class CreateIngresosBrutosCommandHandler
vigenciaDesde: entity.VigenciaDesde,
vigenciaHasta: entity.VigenciaHasta,
predecesorId: entity.PredecesorId,
fechaCreacion: DateTime.UtcNow,
fechaCreacion: _timeProvider.GetUtcNow().UtcDateTime,
fechaModificacion: null));
}
}

View File

@@ -12,11 +12,13 @@ public sealed class DeactivateIngresosBrutosCommandHandler
{
private readonly IIngresosBrutosRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public DeactivateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit)
public DeactivateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<IngresosBrutosDto> Handle(DeactivateIngresosBrutosCommand command)
@@ -41,6 +43,7 @@ public sealed class DeactivateIngresosBrutosCommandHandler
tx.Complete();
return IngresosBrutosMapper.ToDto(entity.Deactivate());
var now = _timeProvider.GetUtcNow().UtcDateTime;
return IngresosBrutosMapper.ToDto(entity.Deactivate(now));
}
}

View File

@@ -12,11 +12,13 @@ public sealed class NuevaVersionIngresosBrutosCommandHandler
{
private readonly IIngresosBrutosRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public NuevaVersionIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit)
public NuevaVersionIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<NuevaVersionIibbResultDto> Handle(NuevaVersionIngresosBrutosCommand command)
@@ -29,10 +31,13 @@ public sealed class NuevaVersionIngresosBrutosCommandHandler
if (!predecesora.Activo || predecesora.VigenciaHasta is not null)
throw new PredecesorYaCerradoException(command.PredecesoraId);
var now = _timeProvider.GetUtcNow().UtcDateTime;
// Steps 34: domain validation + tuple creation (throws ArgumentException if vigencia invalid)
var (predecesoraCerrada, nuevaVersion) = predecesora.NuevaVersion(
command.NuevaAlicuota,
command.VigenciaDesde);
command.VigenciaDesde,
now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,11 +12,13 @@ public sealed class ReactivateIngresosBrutosCommandHandler
{
private readonly IIngresosBrutosRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ReactivateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit)
public ReactivateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<IngresosBrutosDto> Handle(ReactivateIngresosBrutosCommand command)
@@ -41,6 +43,7 @@ public sealed class ReactivateIngresosBrutosCommandHandler
tx.Complete();
return IngresosBrutosMapper.ToDto(entity.Reactivate());
var now = _timeProvider.GetUtcNow().UtcDateTime;
return IngresosBrutosMapper.ToDto(entity.Reactivate(now));
}
}

View File

@@ -12,11 +12,13 @@ public sealed class UpdateIngresosBrutosCommandHandler
{
private readonly IIngresosBrutosRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit)
public UpdateIngresosBrutosCommandHandler(IIngresosBrutosRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<IngresosBrutosDto> Handle(UpdateIngresosBrutosCommand command)
@@ -24,8 +26,9 @@ public sealed class UpdateIngresosBrutosCommandHandler
var entity = await _repo.GetByIdAsync(command.Id)
?? throw new IngresosBrutosNotFoundException(command.Id);
var updated = entity.WithDescripcion(command.Descripcion);
updated = command.Activo ? updated.Reactivate() : updated.Deactivate();
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = entity.WithDescripcion(command.Descripcion, now);
updated = command.Activo ? updated.Reactivate(now) : updated.Deactivate(now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -11,11 +11,13 @@ public sealed class DeactivateMedioCommandHandler : ICommandHandler<DeactivateMe
{
private readonly IMedioRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public DeactivateMedioCommandHandler(IMedioRepository repo, IAuditLogger audit)
public DeactivateMedioCommandHandler(IMedioRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<MedioStatusDto> Handle(DeactivateMedioCommand command)
@@ -27,7 +29,8 @@ public sealed class DeactivateMedioCommandHandler : ICommandHandler<DeactivateMe
if (!target.Activo)
return new MedioStatusDto(target.Id, target.Codigo, target.Activo);
var updated = target.WithActivo(false);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithActivo(false, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,11 +12,13 @@ public sealed class ReactivateMedioCommandHandler : ICommandHandler<ReactivateMe
{
private readonly IMedioRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ReactivateMedioCommandHandler(IMedioRepository repo, IAuditLogger audit)
public ReactivateMedioCommandHandler(IMedioRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<MedioStatusDto> Handle(ReactivateMedioCommand command)
@@ -28,7 +30,8 @@ public sealed class ReactivateMedioCommandHandler : ICommandHandler<ReactivateMe
if (target.Activo)
return new MedioStatusDto(target.Id, target.Codigo, target.Activo);
var updated = target.WithActivo(true);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithActivo(true, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -11,11 +11,13 @@ public sealed class UpdateMedioCommandHandler : ICommandHandler<UpdateMedioComma
{
private readonly IMedioRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdateMedioCommandHandler(IMedioRepository repo, IAuditLogger audit)
public UpdateMedioCommandHandler(IMedioRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<MedioUpdatedDto> Handle(UpdateMedioCommand command)
@@ -23,7 +25,8 @@ public sealed class UpdateMedioCommandHandler : ICommandHandler<UpdateMedioComma
var target = await _repo.GetByIdAsync(command.Id)
?? throw new MedioNotFoundException(command.Id);
var updated = target.WithUpdatedProfile(command.Nombre, command.Tipo, command.PlataformaEmpresaId);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithUpdatedProfile(command.Nombre, command.Tipo, command.PlataformaEmpresaId, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,15 +12,18 @@ public sealed class DeactivatePuntoDeVentaCommandHandler : ICommandHandler<Deact
private readonly IPuntoDeVentaRepository _repo;
private readonly IMedioRepository _medioRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public DeactivatePuntoDeVentaCommandHandler(
IPuntoDeVentaRepository repo,
IMedioRepository medioRepo,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_repo = repo;
_medioRepo = medioRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<PuntoDeVentaStatusDto> Handle(DeactivatePuntoDeVentaCommand command)
@@ -32,7 +35,8 @@ public sealed class DeactivatePuntoDeVentaCommandHandler : ICommandHandler<Deact
if (!target.Activo)
return new PuntoDeVentaStatusDto(target.Id, target.NumeroAFIP, target.Activo);
var updated = target.WithActivo(false);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithActivo(false, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -13,15 +13,18 @@ public sealed class ReactivatePuntoDeVentaCommandHandler : ICommandHandler<React
private readonly IPuntoDeVentaRepository _repo;
private readonly IMedioRepository _medioRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ReactivatePuntoDeVentaCommandHandler(
IPuntoDeVentaRepository repo,
IMedioRepository medioRepo,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_repo = repo;
_medioRepo = medioRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<PuntoDeVentaStatusDto> Handle(ReactivatePuntoDeVentaCommand command)
@@ -39,7 +42,8 @@ public sealed class ReactivatePuntoDeVentaCommandHandler : ICommandHandler<React
if (target.Activo)
return new PuntoDeVentaStatusDto(target.Id, target.NumeroAFIP, target.Activo);
var updated = target.WithActivo(true);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithActivo(true, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,15 +12,18 @@ public sealed class UpdatePuntoDeVentaCommandHandler : ICommandHandler<UpdatePun
private readonly IPuntoDeVentaRepository _repo;
private readonly IMedioRepository _medioRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdatePuntoDeVentaCommandHandler(
IPuntoDeVentaRepository repo,
IMedioRepository medioRepo,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_repo = repo;
_medioRepo = medioRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<PuntoDeVentaUpdatedDto> Handle(UpdatePuntoDeVentaCommand command)
@@ -39,7 +42,8 @@ public sealed class UpdatePuntoDeVentaCommandHandler : ICommandHandler<UpdatePun
if (exists)
throw new NumeroAFIPDuplicadoException(target.MedioId, command.NumeroAFIP);
var updated = target.WithUpdatedProfile(command.Nombre, command.NumeroAFIP, command.Descripcion);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithUpdatedProfile(command.Nombre, command.NumeroAFIP, command.Descripcion, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,12 +12,18 @@ public sealed class DeactivateSeccionCommandHandler : ICommandHandler<Deactivate
private readonly ISeccionRepository _repo;
private readonly IMedioRepository _medioRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public DeactivateSeccionCommandHandler(ISeccionRepository repo, IMedioRepository medioRepo, IAuditLogger audit)
public DeactivateSeccionCommandHandler(
ISeccionRepository repo,
IMedioRepository medioRepo,
IAuditLogger audit,
TimeProvider timeProvider)
{
_repo = repo;
_medioRepo = medioRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<SeccionStatusDto> Handle(DeactivateSeccionCommand command)
@@ -35,7 +41,8 @@ public sealed class DeactivateSeccionCommandHandler : ICommandHandler<Deactivate
if (!target.Activo)
return new SeccionStatusDto(target.Id, target.Codigo, target.Activo);
var updated = target.WithActivo(false);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithActivo(false, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -13,12 +13,18 @@ public sealed class ReactivateSeccionCommandHandler : ICommandHandler<Reactivate
private readonly ISeccionRepository _repo;
private readonly IMedioRepository _medioRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ReactivateSeccionCommandHandler(ISeccionRepository repo, IMedioRepository medioRepo, IAuditLogger audit)
public ReactivateSeccionCommandHandler(
ISeccionRepository repo,
IMedioRepository medioRepo,
IAuditLogger audit,
TimeProvider timeProvider)
{
_repo = repo;
_medioRepo = medioRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<SeccionStatusDto> Handle(ReactivateSeccionCommand command)
@@ -36,7 +42,8 @@ public sealed class ReactivateSeccionCommandHandler : ICommandHandler<Reactivate
if (target.Activo)
return new SeccionStatusDto(target.Id, target.Codigo, target.Activo);
var updated = target.WithActivo(true);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithActivo(true, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,12 +12,18 @@ public sealed class UpdateSeccionCommandHandler : ICommandHandler<UpdateSeccionC
private readonly ISeccionRepository _repo;
private readonly IMedioRepository _medioRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdateSeccionCommandHandler(ISeccionRepository repo, IMedioRepository medioRepo, IAuditLogger audit)
public UpdateSeccionCommandHandler(
ISeccionRepository repo,
IMedioRepository medioRepo,
IAuditLogger audit,
TimeProvider timeProvider)
{
_repo = repo;
_medioRepo = medioRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<SeccionUpdatedDto> Handle(UpdateSeccionCommand command)
@@ -31,7 +37,8 @@ public sealed class UpdateSeccionCommandHandler : ICommandHandler<UpdateSeccionC
if (!medio.Activo)
throw new MedioInactivoException(medio.Id);
var updated = target.WithUpdatedProfile(command.Nombre, command.Tipo);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = target.WithUpdatedProfile(command.Nombre, command.Tipo, now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,11 +12,13 @@ public sealed class CreateTipoDeIvaCommandHandler : ICommandHandler<CreateTipoDe
{
private readonly ITipoDeIvaRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public CreateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit)
public CreateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<TipoDeIvaDto> Handle(CreateTipoDeIvaCommand command)
@@ -55,7 +57,7 @@ public sealed class CreateTipoDeIvaCommandHandler : ICommandHandler<CreateTipoDe
vigenciaDesde: entity.VigenciaDesde,
vigenciaHasta: entity.VigenciaHasta,
predecesorId: entity.PredecesorId,
fechaCreacion: DateTime.UtcNow,
fechaCreacion: _timeProvider.GetUtcNow().UtcDateTime,
fechaModificacion: null));
}
}

View File

@@ -11,11 +11,13 @@ public sealed class DeactivateTipoDeIvaCommandHandler : ICommandHandler<Deactiva
{
private readonly ITipoDeIvaRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public DeactivateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit)
public DeactivateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<TipoDeIvaDto> Handle(DeactivateTipoDeIvaCommand command)
@@ -41,6 +43,7 @@ public sealed class DeactivateTipoDeIvaCommandHandler : ICommandHandler<Deactiva
tx.Complete();
return TipoDeIvaMapper.ToDto(entity.Deactivate());
var now = _timeProvider.GetUtcNow().UtcDateTime;
return TipoDeIvaMapper.ToDto(entity.Deactivate(now));
}
}

View File

@@ -12,11 +12,13 @@ public sealed class NuevaVersionTipoDeIvaCommandHandler
{
private readonly ITipoDeIvaRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public NuevaVersionTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit)
public NuevaVersionTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<NuevaVersionResultDto> Handle(NuevaVersionTipoDeIvaCommand command)
@@ -29,10 +31,13 @@ public sealed class NuevaVersionTipoDeIvaCommandHandler
if (!predecesora.Activo || predecesora.VigenciaHasta is not null)
throw new PredecesorYaCerradoException(command.PredecesoraId);
var now = _timeProvider.GetUtcNow().UtcDateTime;
// Steps 34: delegate validation + tuple creation to domain (throws ArgumentException on invalid vigencia)
var (predecesoraCerrada, nuevaVersion) = predecesora.NuevaVersion(
command.NuevoPorcentaje,
command.VigenciaDesde);
command.VigenciaDesde,
now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -11,11 +11,13 @@ public sealed class ReactivateTipoDeIvaCommandHandler : ICommandHandler<Reactiva
{
private readonly ITipoDeIvaRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ReactivateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit)
public ReactivateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<TipoDeIvaDto> Handle(ReactivateTipoDeIvaCommand command)
@@ -41,6 +43,7 @@ public sealed class ReactivateTipoDeIvaCommandHandler : ICommandHandler<Reactiva
tx.Complete();
return TipoDeIvaMapper.ToDto(entity.Reactivate());
var now = _timeProvider.GetUtcNow().UtcDateTime;
return TipoDeIvaMapper.ToDto(entity.Reactivate(now));
}
}

View File

@@ -11,11 +11,13 @@ public sealed class UpdateTipoDeIvaCommandHandler : ICommandHandler<UpdateTipoDe
{
private readonly ITipoDeIvaRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit)
public UpdateTipoDeIvaCommandHandler(ITipoDeIvaRepository repo, IAuditLogger audit, TimeProvider timeProvider)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<TipoDeIvaDto> Handle(UpdateTipoDeIvaCommand command)
@@ -23,15 +25,16 @@ public sealed class UpdateTipoDeIvaCommandHandler : ICommandHandler<UpdateTipoDe
var entity = await _repo.GetByIdAsync(command.Id)
?? throw new TipoDeIvaNotFoundException(command.Id);
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = entity
.WithCodigo(command.Codigo)
.WithDescripcion(command.Descripcion)
.WithAplicaIVA(command.AplicaIVA);
.WithCodigo(command.Codigo, now)
.WithDescripcion(command.Descripcion, now)
.WithAplicaIVA(command.AplicaIVA, now);
// Apply Activo change if needed
updated = command.Activo
? updated.Reactivate()
: updated.Deactivate();
? updated.Reactivate(now)
: updated.Deactivate(now);
using var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -13,15 +13,18 @@ public sealed class DeactivateUsuarioCommandHandler : ICommandHandler<Deactivate
private readonly IUsuarioRepository _repository;
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public DeactivateUsuarioCommandHandler(
IUsuarioRepository repository,
IRefreshTokenRepository refreshTokenRepository,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_repository = repository;
_refreshTokenRepository = refreshTokenRepository;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<UsuarioDetailDto> Handle(DeactivateUsuarioCommand cmd)
@@ -43,7 +46,7 @@ public sealed class DeactivateUsuarioCommandHandler : ICommandHandler<Deactivate
throw new LastAdminLockoutException();
var fields = new UpdateUsuarioFields(target.Nombre, target.Apellido, target.Email, target.Rol, false);
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().UtcDateTime;
using (var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -18,17 +18,20 @@ public sealed class UpdateUsuarioPermisosOverridesCommandHandler
private readonly IRolPermisoRepository _rolPermisoRepo;
private readonly IPermisoRepository _permisoRepo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdateUsuarioPermisosOverridesCommandHandler(
IUsuarioRepository usuarioRepo,
IRolPermisoRepository rolPermisoRepo,
IPermisoRepository permisoRepo,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_usuarioRepo = usuarioRepo;
_rolPermisoRepo = rolPermisoRepo;
_permisoRepo = permisoRepo;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<UsuarioPermisosDto> Handle(UpdateUsuarioPermisosOverridesCommand command)
@@ -59,7 +62,8 @@ public sealed class UpdateUsuarioPermisosOverridesCommandHandler
// 4. Persist — use WithPermisosJson to get updated FechaModificacion
var newOverrides = new PermisosOverride(grant, deny);
var previousOverrides = PermisosOverride.FromJson(usuario.PermisosJson);
var updated = usuario.WithPermisosJson(newOverrides.ToJson());
var now = _timeProvider.GetUtcNow().UtcDateTime;
var updated = usuario.WithPermisosJson(newOverrides.ToJson(), now);
using (var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -12,11 +12,16 @@ public sealed class ReactivateUsuarioCommandHandler : ICommandHandler<Reactivate
{
private readonly IUsuarioRepository _repository;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ReactivateUsuarioCommandHandler(IUsuarioRepository repository, IAuditLogger audit)
public ReactivateUsuarioCommandHandler(
IUsuarioRepository repository,
IAuditLogger audit,
TimeProvider timeProvider)
{
_repository = repository;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<UsuarioDetailDto> Handle(ReactivateUsuarioCommand cmd)
@@ -34,7 +39,7 @@ public sealed class ReactivateUsuarioCommandHandler : ICommandHandler<Reactivate
}
var fields = new UpdateUsuarioFields(target.Nombre, target.Apellido, target.Email, target.Rol, true);
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().UtcDateTime;
using (var tx = new TransactionScope(
TransactionScopeOption.Required,

View File

@@ -14,17 +14,20 @@ public sealed class ResetUsuarioPasswordCommandHandler : ICommandHandler<ResetUs
private readonly IPasswordHasher _hasher;
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public ResetUsuarioPasswordCommandHandler(
IUsuarioRepository repository,
IPasswordHasher hasher,
IRefreshTokenRepository refreshTokenRepository,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_repository = repository;
_hasher = hasher;
_refreshTokenRepository = refreshTokenRepository;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<ResetUsuarioPasswordResponse> Handle(ResetUsuarioPasswordCommand cmd)
@@ -45,8 +48,9 @@ public sealed class ResetUsuarioPasswordCommandHandler : ICommandHandler<ResetUs
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled);
var now = _timeProvider.GetUtcNow().UtcDateTime;
await _repository.UpdatePasswordAsync(cmd.TargetId, hash, mustChangePassword: true);
await _refreshTokenRepository.RevokeAllActiveForUserAsync(cmd.TargetId, DateTime.UtcNow);
await _refreshTokenRepository.RevokeAllActiveForUserAsync(cmd.TargetId, now);
await _audit.LogAsync(
action: "usuario.password_reset",

View File

@@ -14,17 +14,20 @@ public sealed class UpdateUsuarioCommandHandler : ICommandHandler<UpdateUsuarioC
private readonly IRolRepository _rolRepository;
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
public UpdateUsuarioCommandHandler(
IUsuarioRepository repository,
IRolRepository rolRepository,
IRefreshTokenRepository refreshTokenRepository,
IAuditLogger audit)
IAuditLogger audit,
TimeProvider timeProvider)
{
_repository = repository;
_rolRepository = rolRepository;
_refreshTokenRepository = refreshTokenRepository;
_audit = audit;
_timeProvider = timeProvider;
}
public async Task<UsuarioDetailDto> Handle(UpdateUsuarioCommand cmd)
@@ -52,7 +55,7 @@ public sealed class UpdateUsuarioCommandHandler : ICommandHandler<UpdateUsuarioC
}
var fields = new UpdateUsuarioFields(cmd.Nombre, cmd.Apellido, cmd.Email, cmd.Rol, cmd.Activo);
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().UtcDateTime;
using (var tx = new TransactionScope(
TransactionScopeOption.Required,