diff --git a/Dfe.Academies.Academisation.Data/Repositories/TransferProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/TransferProjectRepository.cs index c07acb1d6..0e1495af8 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/TransferProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/TransferProjectRepository.cs @@ -20,14 +20,11 @@ public TransferProjectRepository(AcademisationContext context) : base(context) return await DefaultIncludes().SingleOrDefaultAsync(x => x.Urn == urn); } - public async Task> GetTransferProjectsByIncomingTrustUkprn(string ukprn, CancellationToken cancellationToken) - => await dbSet.Include(x => x.TransferringAcademies) - .Include(x=> x.IntendedTransferBenefits) - .Where(x => x.TransferringAcademies.Any(a => a.IncomingTrustUkprn == ukprn) && x.Status == null && x.ProjectGroupId == null).ToListAsync(cancellationToken); - + public async Task> GetTransfersProjectsForGroup(string ukprn, CancellationToken cancellationToken) + => await DefaultIncludes().Where(x => x.TransferringAcademies.Any(a => a.IncomingTrustUkprn == ukprn) + && x.Status == null && x.ProjectGroupId == null).ToListAsync(cancellationToken); public async Task> GetTransferProjectsByIdsAsync(List ids, CancellationToken cancellationToken) - => await dbSet.Include(x => x.TransferringAcademies) - .Include(x => x.IntendedTransferBenefits) + => await DefaultIncludes() .Where(x => ids.Contains(x.Id) && x.ProjectGroupId == null).ToListAsync(cancellationToken); public async Task<(IEnumerable, int totalcount)> SearchProjects(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, int page, int count) diff --git a/Dfe.Academies.Academisation.Domain/TransferProjectAggregate/ITransferProjectRepository.cs b/Dfe.Academies.Academisation.Domain/TransferProjectAggregate/ITransferProjectRepository.cs index c6d60da83..cd36e1360 100644 --- a/Dfe.Academies.Academisation.Domain/TransferProjectAggregate/ITransferProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/TransferProjectAggregate/ITransferProjectRepository.cs @@ -8,7 +8,7 @@ public interface ITransferProjectRepository : IRepository, IGen public Task GetByUrn(int urn); public Task> GetAllTransferProjects(); public Task> GetIncompleteProjects(); - Task> GetTransferProjectsByIncomingTrustUkprn(string ukprn, CancellationToken cancellationToken); + Task> GetTransfersProjectsForGroup(string ukprn, CancellationToken cancellationToken); Task> GetTransferProjectsByIdsAsync(List ids, CancellationToken cancellationToken); Task<(IEnumerable, int totalcount)> SearchProjects(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, int page, int count); Task> GetProjectsByProjectGroupIdAsync(int? projectGroupId, CancellationToken cancellationToken); diff --git a/Dfe.Academies.Academisation.IService/Query/ITransferProjectQueryService.cs b/Dfe.Academies.Academisation.IService/Query/ITransferProjectQueryService.cs index c4c3658a3..3dfa4d050 100644 --- a/Dfe.Academies.Academisation.IService/Query/ITransferProjectQueryService.cs +++ b/Dfe.Academies.Academisation.IService/Query/ITransferProjectQueryService.cs @@ -7,7 +7,7 @@ public interface ITransferProjectQueryService { Task GetByUrn(int Urn); Task GetById(int id); - Task?> GetTransferProjectsByIncomingTrustUkprn(string ukprn, CancellationToken cancellationToken); + Task?> GetTransfersProjectsForGroup(string ukprn, CancellationToken cancellationToken); Task> GetTransferProjects(int page, int count, int? urn, string title); Task?> GetProjects(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, int page, int count); diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/DeleteProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/DeleteProjectGroupCommandHandlerTests.cs new file mode 100644 index 000000000..d51e22c30 --- /dev/null +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/DeleteProjectGroupCommandHandlerTests.cs @@ -0,0 +1,78 @@ +using AutoFixture; +using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using Dfe.Academies.Academisation.Domain.SeedWork; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Dfe.Academies.Academisation.Service.UnitTest.Commands.ProjectGroup +{ + public class DeleteProjectGroupCommandHandlerTests + { + private readonly MockRepository _mockRepository; + private readonly Mock _mockProjectGroupRepository; + private readonly Mock> _mocklogger; + private readonly Fixture _fixture = new(); + private readonly CancellationToken _cancellationToken; + private readonly DeleteProjectGroupCommandHandler _deleteProjectGroupCommandHandler; + + public DeleteProjectGroupCommandHandlerTests() + { + _mockRepository = new MockRepository(MockBehavior.Strict); + _cancellationToken = CancellationToken.None; + _mockProjectGroupRepository = _mockRepository.Create(); + _mocklogger = new Mock>(); + + var mockContext = new Mock(); + _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); + + _deleteProjectGroupCommandHandler = new DeleteProjectGroupCommandHandler( + _mockProjectGroupRepository.Object, + _mocklogger.Object); + } + + [Fact] + public async Task Handle_ProjectGroupDoesNotExists_ReturnsNotFoundCommandResult() + { + // Arrange + var request = new DeleteProjectGroupCommand("Reference"); + _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync((Domain.ProjectGroupsAggregate.ProjectGroup?)null); + + // Act + var result = await _deleteProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var notFoundCommandResult = Assert.IsType(result); + _mockProjectGroupRepository.Verify(x => x.Update(It.IsAny()), Times.Never()); + _mockProjectGroupRepository.Verify(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken), Times.Once()); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Never()); + } + + [Fact] + public async Task Handle_ValidRequestWithNoRemovedConversions_ReturnsSuccess() + { + // Arrange + var expectedProjectGroup = _fixture.Create(); + expectedProjectGroup.SetProjectReference(1); + var request = new DeleteProjectGroupCommand(expectedProjectGroup.ReferenceNumber!); + _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); + _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken)).ReturnsAsync(expectedProjectGroup); + _mockProjectGroupRepository.Setup(x => x.Delete(expectedProjectGroup)); + + // Act + var result = await _deleteProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var commandSuccessResult = Assert.IsType(result); + _mockProjectGroupRepository.Verify(x => x.GetByReferenceNumberAsync(request.GroupReferenceNumber, _cancellationToken), Times.Once); + _mockProjectGroupRepository.Verify(x => x.Delete(expectedProjectGroup), Times.Once); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Once); + } + } +} diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Queries/TransferProjectQueryServiceTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Queries/TransferProjectQueryServiceTests.cs index e76d90ce4..a2364a19c 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Queries/TransferProjectQueryServiceTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Queries/TransferProjectQueryServiceTests.cs @@ -287,7 +287,7 @@ public async Task GetProjects_ReturnsFilteredProjects() } [Fact] - public async Task GetTransferProjectsByIncomingTrustUkprn_ShouldReturnTranferProjects() + public async Task GetTransfersProjectsForGroup_ShouldReturnTranferProjects() { // Arrange var trustUrn = "23456789"; @@ -302,28 +302,28 @@ public async Task GetTransferProjectsByIncomingTrustUkprn_ShouldReturnTranferPro DateTime.Now ); var cancelationToken = CancellationToken.None; - _mockTransferProjectRepository.Setup(repo => repo.GetTransferProjectsByIncomingTrustUkprn(trustUrn, cancelationToken)) + _mockTransferProjectRepository.Setup(repo => repo.GetTransfersProjectsForGroup(trustUrn, cancelationToken)) .ReturnsAsync([(dummyTransferProject!)]); var expectedResponse = _service.AcademyTransferProjectSummaryResponse([dummyTransferProject]); // Action - var result = await _service.GetTransferProjectsByIncomingTrustUkprn(trustUrn, cancelationToken); + var result = await _service.GetTransfersProjectsForGroup(trustUrn, cancelationToken); // Assert result.Should().BeEquivalentTo(expectedResponse); } [Fact] - public async Task GetTransferProjectsByIncomingTrustUkprn_ShouldReturnNoTranferProject() + public async Task GetTransfersProjectsForGroup_ShouldReturnNoTranferProject() { // Arrange var trustUrn = "123213"; var cancelationToken = CancellationToken.None; - _mockTransferProjectRepository.Setup(repo => repo.GetTransferProjectsByIncomingTrustUkprn(trustUrn, cancelationToken)) + _mockTransferProjectRepository.Setup(repo => repo.GetTransfersProjectsForGroup(trustUrn, cancelationToken)) .ReturnsAsync([]); var service = new TransferProjectQueryService(_mockTransferProjectRepository.Object, _establishmentRepo, _advisoryBoardDecisionRepository); // Action - var result = await service.GetTransferProjectsByIncomingTrustUkprn(trustUrn, cancelationToken); + var result = await service.GetTransfersProjectsForGroup(trustUrn, cancelationToken); // Assert result.Should().BeEmpty(); diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/DeleteProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/DeleteProjectGroupCommand.cs new file mode 100644 index 000000000..4b43328f8 --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/DeleteProjectGroupCommand.cs @@ -0,0 +1,10 @@ +using Dfe.Academies.Academisation.Core; +using MediatR; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup +{ + public class DeleteProjectGroupCommand(string referenceNumber) : IRequest + { + public string GroupReferenceNumber { get; set; } = referenceNumber; + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/DeleteProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/DeleteProjectGroupCommandHandler.cs new file mode 100644 index 000000000..2bf16d98d --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/DeleteProjectGroupCommandHandler.cs @@ -0,0 +1,27 @@ +using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup +{ + public class DeleteProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, ILogger logger) : IRequestHandler + { + public async Task Handle(DeleteProjectGroupCommand message, CancellationToken cancellationToken) + { + logger.LogInformation("Setting project group with reference number:{value}", message.GroupReferenceNumber); + + var projectGroup = await projectGroupRepository.GetByReferenceNumberAsync(message.GroupReferenceNumber, cancellationToken); + if (projectGroup == null) + { + logger.LogError("Project group is not found with reference number:{value}", message.GroupReferenceNumber); + return new NotFoundCommandResult(); + } + + projectGroupRepository.Delete(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + return new CommandSuccessResult(); + } + } +} diff --git a/Dfe.Academies.Academisation.Service/Queries/TransferProjectQueryService.cs b/Dfe.Academies.Academisation.Service/Queries/TransferProjectQueryService.cs index c5ad80c73..d6198f983 100644 --- a/Dfe.Academies.Academisation.Service/Queries/TransferProjectQueryService.cs +++ b/Dfe.Academies.Academisation.Service/Queries/TransferProjectQueryService.cs @@ -238,9 +238,9 @@ public IEnumerable AcademyTransferProject }); } - public async Task?> GetTransferProjectsByIncomingTrustUkprn(string ukprn, CancellationToken cancellationToken) + public async Task?> GetTransfersProjectsForGroup(string ukprn, CancellationToken cancellationToken) { - var transferProjects = await _transferProjectRepository.GetTransferProjectsByIncomingTrustUkprn(ukprn, cancellationToken); + var transferProjects = await _transferProjectRepository.GetTransfersProjectsForGroup(ukprn, cancellationToken); return AcademyTransferProjectSummaryResponse(transferProjects); } diff --git a/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs b/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs index 3ea749574..fc962acfe 100644 --- a/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs +++ b/Dfe.Academies.Academisation.SubcutaneousTest/ProjectGroup/ProjectGroupTests.cs @@ -1,4 +1,6 @@ -using AutoFixture; +using System.Web.Http.Results; +using AutoFixture; +using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Data; using Dfe.Academies.Academisation.Domain.ProjectAggregate; using Dfe.Academies.Academisation.Domain.TransferProjectAggregate; @@ -6,6 +8,7 @@ using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.SubcutaneousTest.Utils; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Dfe.Academies.Academisation.SubcutaneousTest.ProjectGroup @@ -211,5 +214,33 @@ private async Task VerifyProjectGroupAsync(HttpResponseMessage httpResponseMessa Assert.NotNull(dbTransferProject); Assert.Equal(dbTransferProject.ProjectGroupId, projectGroup.Id); } + + [Fact] + public async Task DeleteProjectGroup_ShouldDeleteSuccessfully() + { + // Arrange + var projectGroup = await CreateProjectGroup(); + + // Action + var httpResponseMessage = await _client.DeleteAsync($"project-group/{projectGroup.ReferenceNumber}", CancellationToken); + + Assert.True(httpResponseMessage.IsSuccessStatusCode); + var dbProjectGroup = await _context.ProjectGroups.AsNoTracking().FirstOrDefaultAsync(x => x.ReferenceNumber == projectGroup.ReferenceNumber); + Assert.Null(dbProjectGroup); + } + + [Fact] + public async Task DeleteProjectGroup_ShouldReturnNotFoundOnNotFindingGroup() + { + // Arrange + var referenceNumber = Fixture.Create()[..10]; + + // Action + var httpResponseMessage = await _client.DeleteAsync($"project-group/{referenceNumber}", CancellationToken); + + // Assert + Assert.False(httpResponseMessage.IsSuccessStatusCode); + Assert.Equal(httpResponseMessage!.StatusCode.GetHashCode(), System.Net.HttpStatusCode.NotFound.GetHashCode()); + } } } diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs index 492e7073f..5e86b75d3 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs @@ -99,5 +99,20 @@ public async Task> GetProjectGrouptById( return Ok(project); } + + [HttpDelete("{referenceNumber}", Name = "DeleteProjectGroupByReference")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> DeleteProjectGroupByReference(string referenceNumber, CancellationToken cancellationToken) + { + var result = await mediator.Send(new DeleteProjectGroupCommand(referenceNumber), cancellationToken); + + return result switch + { + CommandSuccessResult => Ok(), + NotFoundCommandResult => NotFound(), + _ => new InternalServerErrorObjectResult("Error serving request") + }; + } } } diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/TransferProjectController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/TransferProjectController.cs index 00a5ac446..ff0c83f44 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/TransferProjectController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/TransferProjectController.cs @@ -354,12 +354,12 @@ public async Task>> GetOpeningDa return Ok(result); } - [HttpGet("{ukprn}/get-transfers-by-incoming-trust", Name = "GetTransfersByIncomingTrust")] - public async Task>> GetTransfersByincomingTrust(string ukprn, CancellationToken cancellationToken) + [HttpGet("{ukprn}/get-transfers-for-group", Name = "GetTransfersProjectsForGroup")] + public async Task>> GetTransfersProjectsForGroup(string ukprn, CancellationToken cancellationToken) { _logger.LogInformation("Getting transfer projects by incoming trust ukprn: {value}", ukprn); - var result = await _transferProjectQueryService.GetTransferProjectsByIncomingTrustUkprn(ukprn, cancellationToken); + var result = await _transferProjectQueryService.GetTransfersProjectsForGroup(ukprn, cancellationToken); return result is null ? NotFound() : Ok(result); }