93 lines
3.3 KiB
C#
93 lines
3.3 KiB
C#
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);
|
|
}
|
|
}
|