Files
SIG-CM2.0/src/api/SIGCM2.Application/Rubros/Move/MoveRubroCommandHandler.cs

93 lines
3.3 KiB
C#
Raw Normal View History

using System.Transactions;
using Microsoft.Extensions.Options;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Audit;
using SIGCM2.Domain.Exceptions;
namespace SIGCM2.Application.Rubros.Move;
public sealed class MoveRubroCommandHandler : ICommandHandler<MoveRubroCommand, RubroMovedDto>
{
private readonly IRubroRepository _repo;
private readonly IAuditLogger _audit;
private readonly TimeProvider _timeProvider;
private readonly RubrosOptions _options;
public MoveRubroCommandHandler(
IRubroRepository repo,
IAuditLogger audit,
TimeProvider timeProvider,
IOptions<RubrosOptions> options)
{
_repo = repo;
_audit = audit;
_timeProvider = timeProvider;
_options = options.Value;
}
public async Task<RubroMovedDto> Handle(MoveRubroCommand command)
{
var target = await _repo.GetByIdAsync(command.Id)
?? throw new RubroNotFoundException(command.Id);
var anteriorParentId = target.ParentId;
// Cycle check: nuevoParentId must not be in descendants of target
if (command.NuevoParentId.HasValue)
{
var descendants = await _repo.GetDescendantsAsync(command.Id);
if (descendants.Any(d => d.Id == command.NuevoParentId.Value))
throw new RubroCycleDetectedException(command.Id, command.NuevoParentId.Value);
// New parent must exist and be active
var newParent = await _repo.GetByIdAsync(command.NuevoParentId.Value)
?? throw new RubroNotFoundException(command.NuevoParentId.Value);
if (!newParent.Activo)
throw new RubroPadreInactivoException(command.NuevoParentId.Value);
// Depth check
var parentDepth = await _repo.GetDepthAsync(command.NuevoParentId);
var newDepth = parentDepth + 1;
if (newDepth > _options.MaxDepth)
throw new RubroMaxDepthExceededException(newDepth, _options.MaxDepth);
}
// Duplicate name check under new parent (excluding self)
var exists = await _repo.ExistsByNombreUnderParentAsync(command.NuevoParentId, target.Nombre, excludeId: command.Id);
if (exists)
throw new RubroNombreDuplicadoEnPadreException(target.Nombre, command.NuevoParentId);
var moved = target.WithMoved(command.NuevoParentId, command.NuevoOrden, _timeProvider);
using var tx = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled);
await _repo.UpdateAsync(moved);
await _audit.LogAsync(
action: "rubro.moved",
targetType: "Rubro",
targetId: command.Id.ToString(),
metadata: new
{
anteriorParentId,
nuevoParentId = command.NuevoParentId,
anteriorOrden = target.Orden,
nuevoOrden = command.NuevoOrden,
});
tx.Complete();
return new RubroMovedDto(
Id: moved.Id,
Nombre: moved.Nombre,
ParentId: moved.ParentId,
Orden: moved.Orden,
Activo: moved.Activo);
}
}