diff --git a/src/Application/Common/Models/Dtos/DepartmentDto.cs b/src/Application/Common/Models/Dtos/DepartmentDto.cs index 68d00db4..9ae2e4e6 100644 --- a/src/Application/Common/Models/Dtos/DepartmentDto.cs +++ b/src/Application/Common/Models/Dtos/DepartmentDto.cs @@ -1,5 +1,5 @@ using Application.Common.Mappings; -using Application.Common.Models.Dtos.Physical; +using AutoMapper; using Domain.Entities; namespace Application.Common.Models.Dtos; @@ -8,5 +8,12 @@ public class DepartmentDto : IMapFrom { public Guid Id { get; set; } public string Name { get; set; } = null!; - public RoomDto? Room { get; set; } + public Guid? RoomId { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.RoomId, + opt => opt.MapFrom(src => src.Room!.Id)); + } } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Physical/RoomDto.cs b/src/Application/Common/Models/Dtos/Physical/RoomDto.cs index ff0ab27a..44fe55e0 100644 --- a/src/Application/Common/Models/Dtos/Physical/RoomDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/RoomDto.cs @@ -10,9 +10,16 @@ public class RoomDto : IMapFrom public Guid Id { get; set; } public string Name { get; set; } = null!; public string? Description { get; set; } - public StaffDto? Staff { get; set; } - public DepartmentDto Department { get; set; } + public Guid? StaffId { get; set; } + public DepartmentDto? Department { get; set; } public int Capacity { get; set; } public int NumberOfLockers { get; set; } public bool IsAvailable { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.StaffId, + opt => opt.MapFrom(src => src.Staff!.Id)); + } } \ No newline at end of file diff --git a/src/Application/Lockers/Commands/DisableLocker.cs b/src/Application/Lockers/Commands/DisableLocker.cs index eaa4f40b..cbde208a 100644 --- a/src/Application/Lockers/Commands/DisableLocker.cs +++ b/src/Application/Lockers/Commands/DisableLocker.cs @@ -26,12 +26,12 @@ public record Command : IRequest public Guid LockerId { get; init; } } - public class RemoveLockerCommandHandler : IRequestHandler + public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - public RemoveLockerCommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper) { _context = context; _mapper = mapper; diff --git a/src/Application/Lockers/Commands/RemoveLocker.cs b/src/Application/Lockers/Commands/RemoveLocker.cs index 11c6472b..4a0ce1fc 100644 --- a/src/Application/Lockers/Commands/RemoveLocker.cs +++ b/src/Application/Lockers/Commands/RemoveLocker.cs @@ -1,12 +1,69 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using FluentValidation; using MediatR; +using Microsoft.EntityFrameworkCore; namespace Application.Lockers.Commands; public class RemoveLocker { + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.LockerId) + .NotEmpty().WithMessage("LockerId is required."); + } + } + public record Command : IRequest { public Guid LockerId { get; init; } } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public CommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var locker = await _context.Lockers + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id.Equals(request.LockerId), cancellationToken); + + if (locker is null) + { + throw new KeyNotFoundException("Locker does not exist."); + } + + var canNotRemove = await _context.Documents + .CountAsync(x => x.Folder!.Locker.Id.Equals(request.LockerId), cancellationToken) + > 0; + + if (canNotRemove) + { + throw new InvalidOperationException("Locker cannot be removed because it contains documents."); + } + + var room = locker.Room; + + var result = _context.Lockers.Remove(locker); + room.NumberOfLockers -= 1; + _context.Rooms.Update(room); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } } \ No newline at end of file diff --git a/src/Application/Rooms/Commands/AddRoom.cs b/src/Application/Rooms/Commands/AddRoom.cs index a317d7cf..13449b93 100644 --- a/src/Application/Rooms/Commands/AddRoom.cs +++ b/src/Application/Rooms/Commands/AddRoom.cs @@ -82,6 +82,8 @@ public async Task Handle(Command request, CancellationToken cancellatio NumberOfLockers = 0, Capacity = request.Capacity, Department = department, + DepartmentId = request.DepartmentId, + IsAvailable = true, }; var result = await _context.Rooms.AddAsync(entity, cancellationToken); await _context.SaveChangesAsync(cancellationToken); diff --git a/src/Application/Rooms/Commands/DisableRoom.cs b/src/Application/Rooms/Commands/DisableRoom.cs index 580b62c6..dd9c7afe 100644 --- a/src/Application/Rooms/Commands/DisableRoom.cs +++ b/src/Application/Rooms/Commands/DisableRoom.cs @@ -26,12 +26,12 @@ public record Command : IRequest public Guid RoomId { get; init; } } - public class DisableRoomCommandHandler : IRequestHandler + public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - public DisableRoomCommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper) { _context = context; _mapper = mapper; diff --git a/src/Application/Rooms/Commands/RemoveRoom.cs b/src/Application/Rooms/Commands/RemoveRoom.cs index ef079f47..9e35ddc1 100644 --- a/src/Application/Rooms/Commands/RemoveRoom.cs +++ b/src/Application/Rooms/Commands/RemoveRoom.cs @@ -25,12 +25,12 @@ public record Command : IRequest public Guid RoomId { get; init; } } - public class RemoveRoomCommandHandler : IRequestHandler + public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - public RemoveRoomCommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper) { _context = context; _mapper = mapper; diff --git a/tests/Application.Tests.Integration/Lockers/Commands/RemoveLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/RemoveLockerTests.cs new file mode 100644 index 00000000..a5e19fcf --- /dev/null +++ b/tests/Application.Tests.Integration/Lockers/Commands/RemoveLockerTests.cs @@ -0,0 +1,90 @@ +using Application.Lockers.Commands; +using Application.Rooms.Commands; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Lockers.Commands; + +public class RemoveLockerTests : BaseClassFixture +{ + public RemoveLockerTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldRemoveLocker_WhenLockerHasNoDocuments() + { + // Arrange + var department = CreateDepartment(); + var locker = CreateLocker(); + var room = CreateRoom(department, locker); + await Add(room); + + var command = new RemoveLocker.Command() + { + LockerId = locker.Id, + }; + + // Act + var result = await SendAsync(command); + + // Assert + result.Id.Should().Be(locker.Id); + var removedLocker = await FindAsync(locker.Id); + removedLocker.Should().BeNull(); + + // Cleanup + Remove(await FindAsync(room.Id)); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenRoomStillHasDocuments() + { + // Arrange + var department = CreateDepartment(); + var documents = CreateNDocuments(1); + var folder = CreateFolder(documents); + var locker = CreateLocker(folder); + var room = CreateRoom(department, locker); + await AddAsync(room); + + var command = new RemoveLocker.Command() + { + LockerId = locker.Id, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Locker cannot be removed because it contains documents."); + + // Cleanup + Remove(documents.First()); + Remove(folder); + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenLockerDoesNotExist() + { + // Arrange + var command = new RemoveLocker.Command() + { + LockerId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Locker does not exist."); + } +} \ No newline at end of file