From f0e301a9a0ac260e68ad445e41b6e5ab6dafc104 Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Mon, 22 Jul 2024 14:39:25 +0100 Subject: [PATCH 01/15] Added set and get project group --- .../ConversionProjectRepository.cs | 41 +++++++++----- .../Repositories/ProjectGroupRepository.cs | 14 +++++ .../IConversionProjectRepository.cs | 4 ++ .../ProjectAggregate/Project.cs | 6 +++ .../IProjectGroupRepository.cs | 1 + .../ProjectGroupsAggregate/ProjectGroup.cs | 34 +++++++----- .../ProjectGroupAggregate/IProjectGroup.cs | 7 ++- .../CreateProjectGroupCommandHandlerTests.cs | 53 +++++++++++++++---- .../CreateProjectGroupCommandValidator.cs | 5 -- .../SetProjectGroupCommandValidator.cs | 26 +++++++++ .../ProjectGroup/CreateProjectGroupCommand.cs | 5 +- .../CreateProjectGroupCommandHandler.cs | 19 +++++-- .../GetProjectGroupQueryCommand.cs | 26 +++++++++ .../GetProjectGroupQueryCommandHandler.cs | 27 ++++++++++ .../ProjectGroup/SetProjectGroupCommand.cs | 13 +++++ .../SetProjectGroupCommandHandler.cs | 34 ++++++++++++ .../Controller/ProjectGroupControllerTests.cs | 2 +- .../Controllers/ProjectGroupController.cs | 43 ++++++++++++++- Dfe.Academies.Academisation.WebApi/Program.cs | 1 + 19 files changed, 313 insertions(+), 48 deletions(-) create mode 100644 Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs create mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs create mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs create mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs create mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index 2421a5505..987662c82 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -1,6 +1,5 @@  using System.Globalization; -using System.Linq; using AutoMapper; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Dfe.Academies.Academisation.Domain.ProjectAggregate; @@ -24,7 +23,7 @@ public ConversionProjectRepository(AcademisationContext context, IMapper mapper) public IUnitOfWork UnitOfWork => _context; public async Task<(IEnumerable, int)> SearchProjects(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, int page, int count, int? urn, IEnumerable? regions = default, IEnumerable? applicationReferences = default) { - IQueryable queryable = this.dbSet; + IQueryable queryable = dbSet; queryable = FilterByRegion(regions, queryable); queryable = FilterByStatus(states, queryable); @@ -42,9 +41,27 @@ public ConversionProjectRepository(AcademisationContext context, IMapper mapper) return (projects, totalProjects); } + public async Task AreProjectsAssociateToAnotherProjectGroupAsync(List projectsUrns, CancellationToken cancellationToken) + { + return await dbSet.AsNoTracking().AnyAsync(x => projectsUrns.Contains(x.Details.Urn) && x.ProjectGroupId != null, cancellationToken); + } + + public async Task?> GetProjectsByProjectGroupAsync(int projectGroupId, CancellationToken cancellationToken) + { + return await _context.Projects + .Where(p => p.ProjectGroupId == projectGroupId) + .ToListAsync(cancellationToken); + } + + public async Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, CancellationToken cancellationToken) + { + await dbSet.Where(x => projectsUrns.Contains(x.Details.Urn)) + .ExecuteUpdateAsync(u => u.SetProperty(p => p.ProjectGroupId, projectGroupId), cancellationToken); + } + public async Task GetFilterParameters() { - var advisoryBoardDates = await this.dbSet + var advisoryBoardDates = await dbSet .OrderByDescending(p => p.Details.HeadTeacherBoardDate) .AsNoTracking() .Where(p => p.Details.HeadTeacherBoardDate.HasValue) @@ -55,13 +72,13 @@ public async Task GetFilterParameters() ProjectFilterParameters filterParameters = new ProjectFilterParameters { - Statuses = (await this.dbSet + Statuses = (await dbSet .AsNoTracking() .Select(p => p.Details.ProjectStatus) .Distinct() .OrderBy(p => p) .ToListAsync())!, - AssignedUsers = (await this.dbSet + AssignedUsers = (await dbSet .OrderByDescending(p => p.Details.AssignedUser.FullName) .AsNoTracking() .Select(p => p.Details.AssignedUser.FullName) @@ -69,7 +86,7 @@ public async Task GetFilterParameters() .Distinct() .ToListAsync())!, - LocalAuthorities = (await this.dbSet + LocalAuthorities = (await dbSet .OrderByDescending(p => p.Details.LocalAuthority) .AsNoTracking() .Select(p => p.Details.LocalAuthority) @@ -185,7 +202,7 @@ private static IQueryable FilterByDeliveryOfficer(IEnumerable? public async Task GetConversionProject(int id, CancellationToken cancellationToken) { - return await this.DefaultIncludes().SingleOrDefaultAsync(x => x.Id == id, cancellationToken); + return await DefaultIncludes().SingleOrDefaultAsync(x => x.Id == id, cancellationToken); } private IQueryable DefaultIncludes() @@ -200,7 +217,7 @@ private IQueryable DefaultIncludes() public async Task<(IEnumerable projects, int totalCount)> SearchProjectsV2(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, IEnumerable? regions, IEnumerable? localAuthorities, IEnumerable? advisoryBoardDates, int page, int count) { - IQueryable queryable = this.dbSet; + IQueryable queryable = dbSet; queryable = FilterByRegion(regions, queryable); queryable = FilterByStatus(states, queryable); @@ -220,7 +237,7 @@ private IQueryable DefaultIncludes() public async Task<(IEnumerable projects, int totalCount)> SearchFormAMatProjects(IEnumerable? states, string? title, IEnumerable? deliveryOfficers, IEnumerable? regions, IEnumerable? localAuthorities, IEnumerable? advisoryBoardDates) { - IQueryable queryable = this.dbSet; + IQueryable queryable = dbSet; queryable = FilterFormAMAT(queryable); queryable = FilterByRegion(regions, queryable); @@ -263,18 +280,18 @@ private static IQueryable FilterByLocalAuthority(IEnumerable lo public async Task> GetConversionProjectsThatRequireFormAMatCreation(CancellationToken cancellationToken) { - return await this.dbSet.Where(x => !x.FormAMatProjectId.HasValue && x.Details.IsFormAMat.HasValue && x.Details.IsFormAMat.Value).ToListAsync(cancellationToken).ConfigureAwait(false); + return await dbSet.Where(x => !x.FormAMatProjectId.HasValue && x.Details.IsFormAMat.HasValue && x.Details.IsFormAMat.Value).ToListAsync(cancellationToken).ConfigureAwait(false); } public async Task> GetConversionProjectsByFormAMatId(int? id, CancellationToken cancellationToken) { - return await this.dbSet.Where(x => x.FormAMatProjectId == id).ToListAsync(cancellationToken).ConfigureAwait(false); + return await dbSet.Where(x => x.FormAMatProjectId == id).ToListAsync(cancellationToken).ConfigureAwait(false); } public async Task> GetConversionProjectsByFormAMatIds(IEnumerable ids, CancellationToken cancellationToken) { var formAMatProjectIds = ids.ToList(); - return await this.dbSet.Where(x => formAMatProjectIds.Contains(x.FormAMatProjectId)).ToListAsync(cancellationToken).ConfigureAwait(false); + return await dbSet.Where(x => formAMatProjectIds.Contains(x.FormAMatProjectId)).ToListAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs index f9498e813..bf15c9f01 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs @@ -1,5 +1,7 @@ using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; +using Microsoft.EntityFrameworkCore; + namespace Dfe.Academies.Academisation.Data.Repositories { public class ProjectGroupRepository : GenericRepository, IProjectGroupRepository @@ -9,5 +11,17 @@ public ProjectGroupRepository(AcademisationContext context) : base(context) => _context = context ?? throw new ArgumentNullException(nameof(context)); public IUnitOfWork UnitOfWork => _context; + + private IQueryable DefaultIncludes() + { + var x = dbSet + .Include(x => x.AssignedUser) + .AsQueryable(); + + return x; + } + + public async Task GetByReferenceNumber(string referenceNumber, CancellationToken cancellationToken) + => await DefaultIncludes().SingleOrDefaultAsync(x => x.ReferenceNumber == referenceNumber, cancellationToken); } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index 3df94ac90..68ea2aac8 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -21,4 +21,8 @@ public interface IConversionProjectRepository : IRepository, IGenericRe IEnumerable? states, string? title, IEnumerable? deliveryOfficers, IEnumerable? regions, IEnumerable? localAuthorities, IEnumerable? advisoryBoardDates); Task?> GetIncompleteProjects(); + + Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, CancellationToken cancellationToken); + Task AreProjectsAssociateToAnotherProjectGroupAsync(List projectsUrns, CancellationToken cancellationToken); + Task?> GetProjectsByProjectGroupAsync(int projectGroupId, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs index 8fd24c572..f29ec3897 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs @@ -34,6 +34,7 @@ private Project(ProjectDetails projectDetails) private readonly List _schoolImprovementPlans = new(); public int? FormAMatProjectId { get; private set; } + public int? ProjectGroupId { get; private set; } public DateTime? DeletedAt { get; private set; } /// @@ -560,6 +561,11 @@ public void SetDeletedAt() DeletedAt = DateTime.UtcNow; } + public void SetProjectGroupId(int? projectGroupId) + { + ProjectGroupId = projectGroupId; + } + public void AddNote(string subject, string note, string author, DateTime date) { _notes.Add(new ProjectNote(subject, note, author, date, Id)); diff --git a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs index 3812b7ced..d3d3ee1c9 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs @@ -4,5 +4,6 @@ namespace Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate { public interface IProjectGroupRepository : IRepository, IGenericRepository { + Task GetByReferenceNumber(string referenceNumber, CancellationToken cancellationToken); } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs index e0bfdd9e0..76a385986 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs @@ -1,9 +1,5 @@ -using System.ComponentModel.DataAnnotations; -using Ardalis.GuardClauses; -using Dfe.Academies.Academisation.Core; -using Dfe.Academies.Academisation.Domain.Core.ProjectAggregate; +using Dfe.Academies.Academisation.Domain.Core.ProjectAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; -using Dfe.Academies.Academisation.IDomain.ConversionAdvisoryBoardDecisionAggregate; using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; namespace Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate @@ -14,28 +10,38 @@ public class ProjectGroup : Entity, IProjectGroup, IAggregateRoot public User? AssignedUser { private set; get; } - public string ReferenceNumber { private set; get; } = string.Empty; + public string? ReferenceNumber { private set; get; } public void SetAssignedUser(Guid userId, string fullName, string emailAddress) { - throw new NotImplementedException(); + AssignedUser = new User(userId, fullName, emailAddress); + LastModifiedOn = DateTime.UtcNow; } - public void SetProjectReference(int id) + public void SetProjectGroup(string trustReference, DateTime lastModifiedOnUtc) { - throw new NotImplementedException(); + TrustReference = trustReference; + LastModifiedOn = lastModifiedOnUtc; } - public static ProjectGroup Create(string trustReference, string referenceNumber, DateTime createdOn) + public static ProjectGroup Create(string trustReference, DateTime createdOn) { - return new ProjectGroup(trustReference, referenceNumber, createdOn); + return new ProjectGroup(trustReference, createdOn); } - private ProjectGroup(string trustReference, string referenceNumber, DateTime createdOn) + private ProjectGroup(string trustReference, DateTime createdOn) { - TrustReference = trustReference; - ReferenceNumber = referenceNumber; + TrustReference = trustReference; CreatedOn = createdOn; } + + public void SetProjectReference(int id) + { + // Convert the id to string and pad it with zeros to ensure it is 8 characters long + string paddedId = id.ToString().PadLeft(8, '0'); + + // Set the ReferenceNumber property with the formatted string + ReferenceNumber = $"GRP_{paddedId}"; + } } } diff --git a/Dfe.Academies.Academisation.IDomain/ProjectGroupAggregate/IProjectGroup.cs b/Dfe.Academies.Academisation.IDomain/ProjectGroupAggregate/IProjectGroup.cs index f84cb28d6..6e088b4c1 100644 --- a/Dfe.Academies.Academisation.IDomain/ProjectGroupAggregate/IProjectGroup.cs +++ b/Dfe.Academies.Academisation.IDomain/ProjectGroupAggregate/IProjectGroup.cs @@ -5,14 +5,19 @@ namespace Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate public interface IProjectGroup { int Id { get; } + string TrustReference{ get; } User? AssignedUser { get; } DateTime CreatedOn { get; } - string ReferenceNumber { get; } + + string? ReferenceNumber { get; } void SetAssignedUser(Guid userId, string fullName, string emailAddress); + void SetProjectReference(int id); + + void SetProjectGroup(string trustReference, DateTime lastModifiedOnUtc); } } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index f8bf52af3..2b04f7c60 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -1,11 +1,14 @@ using System.ComponentModel.DataAnnotations; using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Core.Utils; +using Dfe.Academies.Academisation.Domain.ApplicationAggregate; +using Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using FluentAssertions; +using Microsoft.EntityFrameworkCore.Query; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Moq; using Xunit; @@ -20,6 +23,7 @@ public class CreateProjectGroupCommandHandlerTests private Mock _mockDateTimeProvider; private CreateProjectGroupCommandValidator _validator; + private Mock mockCnversionProjectRepository; public CreateProjectGroupCommandHandlerTests() { @@ -28,6 +32,7 @@ public CreateProjectGroupCommandHandlerTests() _mockProjectGroupRepository = _mockRepository.Create(); _mockDateTimeProvider = _mockRepository.Create(); _validator = new CreateProjectGroupCommandValidator(); + mockCnversionProjectRepository = _mockRepository.Create(); var mockContext = new Mock(); _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); @@ -38,11 +43,12 @@ private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() return new CreateProjectGroupCommandHandler( _mockProjectGroupRepository.Object, _mockDateTimeProvider.Object, - _validator); + _validator, + mockCnversionProjectRepository.Object); } [Fact] - public async Task Handle_ValidCommand_PersistsExpectedProjectGroup() + public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectGroup() { var now = DateTime.Now; _mockDateTimeProvider.Setup(x => x.Now).Returns(now); @@ -60,10 +66,40 @@ public async Task Handle_ValidCommand_PersistsExpectedProjectGroup() // Assert _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReference - && x.ReferenceNumber == request.ReferenceNumber + && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); + mockCnversionProjectRepository.Verify(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken)), Times.Never()); + mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Never()); + } + + [Fact] + public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGroup() + { + // Arrange + var now = DateTime.Now; + _mockDateTimeProvider.Setup(x => x.Now).Returns(now); + var createTransferProjectCommandHandler = CreateProjectGroupCommandHandler(); + var request = CreateValidCreateTProjectProjectCommand(); + var cancellationToken = CancellationToken.None; + _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); + mockCnversionProjectRepository.Setup(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.Is(x => x == cancellationToken))).ReturnsAsync(false); ; + mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.IsAny(), It.Is(x => x == cancellationToken))); + + // Act + var result = await createTransferProjectCommandHandler.Handle( + request, + cancellationToken); + + // Assert + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReference + && x.ReferenceNumber != null + && x.CreatedOn == now)), Times.Once()); + + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); + mockCnversionProjectRepository.Verify(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken)), Times.Once()); + mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); } [Fact] @@ -75,7 +111,7 @@ public async Task Handle_InValidCommand_ReturnsBadRequest() // Arrange var createTransferProjectCommandHandler = CreateProjectGroupCommandHandler(); - var request = new CreateProjectGroupCommand(string.Empty, "3424"); + var request = new CreateProjectGroupCommand(string.Empty, [3424]); var cancellationToken = CancellationToken.None; // Act @@ -85,20 +121,19 @@ public async Task Handle_InValidCommand_ReturnsBadRequest() // Assert Assert.IsType(result); - (result as CommandValidationErrorResult).ValidationErrors.Should().HaveCount(3); + (result! as CommandValidationErrorResult).ValidationErrors.Should().HaveCount(2); _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReference - && x.ReferenceNumber == request.ReferenceNumber + && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Never()); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Never()); } - private static CreateProjectGroupCommand CreateValidCreateTProjectProjectCommand() + private static CreateProjectGroupCommand CreateValidCreateTProjectProjectCommand(bool includeConversions = true) { string trustReference = "11112222"; - string referenceNumber = "97857253"; - return new CreateProjectGroupCommand(trustReference, referenceNumber); + return new CreateProjectGroupCommand(trustReference, includeConversions ? [03823] : []); } } } diff --git a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs index 9606a8d92..125571d6d 100644 --- a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs +++ b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs @@ -11,11 +11,6 @@ public CreateProjectGroupCommandValidator() .NotEmpty().WithMessage("Must specify a trust reference") .Length(8).WithMessage("Trust reference must be length 8") .NotNull().WithMessage("Trust Reference must not be null"); - - RuleFor(x => x.ReferenceNumber) - .NotEmpty().WithMessage("Must specify a reference number") - .Length(8).WithMessage("Reference number must be length 8") - .NotNull().WithMessage("Reference must not be null"); } } } diff --git a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs new file mode 100644 index 000000000..ced3052df --- /dev/null +++ b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using FluentValidation; + +namespace Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup +{ + public class SetProjectGroupCommandValidator : AbstractValidator + { + public SetProjectGroupCommandValidator() + { + RuleFor(x => x.TrustReference) + .NotEmpty().WithMessage("Must specify a trust reference") + .Length(8).WithMessage("Trust reference must be length 8") + .NotNull().WithMessage("Trust Reference must not be null"); + + RuleFor(x => x.ReferenceNumber) + .NotEmpty().WithMessage("Must specify a reference number") + .Length(8).WithMessage("Reference number must be length 8") + .NotNull().WithMessage("Reference must not be null"); + } + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs index 131a34b88..468565251 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs @@ -3,11 +3,12 @@ using System.Runtime.Serialization; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommand(string trustReference, string referenceNumber) : IRequest + public class CreateProjectGroupCommand(string trustReference, List conversionProjects) : IRequest { [DataMember] public string TrustReference { get; set; } = trustReference; + [DataMember] - public string ReferenceNumber { get; set; } = referenceNumber; + public List ConversionProjectsUrns { get; set; } = conversionProjects; } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index 28ab479da..e267b0b70 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -3,12 +3,12 @@ using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; +using Dfe.Academies.Academisation.Domain.ApplicationAggregate; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator) : IRequestHandler + public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator, IConversionProjectRepository conversionProjectRepository) : IRequestHandler { - public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) { var validationResult = validator.Validate(message); @@ -17,12 +17,25 @@ public async Task Handle(CreateProjectGroupCommand message, Cance var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create( message.TrustReference, - message.ReferenceNumber, dateTimeProvider.Now); projectGroupRepository.Insert(projectGroup); await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + projectGroup.SetProjectReference(projectGroup.Id); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + if (message.ConversionProjectsUrns.Any()) + { + if(await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionProjectsUrns, cancellationToken)) + { + return new BadRequestCommandResult(); + } + + await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionProjectsUrns, projectGroup.Id, cancellationToken); + await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); + } + return new CommandSuccessResult(); } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs new file mode 100644 index 000000000..846228ad2 --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs @@ -0,0 +1,26 @@ + +using MediatR; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup +{ + public class GetProjectGroupQueryCommand : IRequest + { + public string Urn { get; private set; } + + public GetProjectGroupQueryCommand(string urn) + { + Urn = urn; + } + } + + public class ProjectGroupDto + { + public int Urn { get; set; } + + public string TrustReference { get; set; } + + public string ReferenceNumber { get; set; } + + public List ConversionsProjects { get; set; } + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs new file mode 100644 index 000000000..7d5f30fd6 --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using Dfe.Academies.Academisation.Domain.ApplicationAggregate; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using MediatR; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup +{ + public class GetProjectGroupQueryCommandHandler(IProjectGroupRepository projectGroupRepository, IMapper mapper, IConversionProjectRepository conversionProjectRepository) : IRequestHandler + { + public async Task Handle(GetProjectGroupQueryCommand message, CancellationToken cancellationToken) + { + var projectGroup = await projectGroupRepository.GetByReferenceNumber(message.Urn, cancellationToken); + if (projectGroup == null) { + return null; + } + + var projectGroupDto = mapper.Map(projectGroup); + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); + if (conversionsProjects != null && conversionsProjects.Any()) + { + projectGroupDto.ConversionsProjects = conversionsProjects.Select(x => x.Details.Urn).ToList(); + } + + return projectGroupDto; + } + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs new file mode 100644 index 000000000..418d6949f --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs @@ -0,0 +1,13 @@ +using System.Runtime.Serialization; +using Dfe.Academies.Academisation.Core; +using MediatR; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup +{ + public class SetProjectGroupCommand : IRequest + { + public int Urn { get; set; } + public required string TrustReference { get; set; } + public required string ReferenceNumber { get; set; } + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs new file mode 100644 index 000000000..532680977 --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -0,0 +1,34 @@ +using Dfe.Academies.Academisation.Core.Utils; +using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup +{ + public class SetProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, SetProjectGroupCommandValidator validator, ILogger _logger) : IRequestHandler + { + public async Task Handle(SetProjectGroupCommand message, CancellationToken cancellationToken) + { + var validationResult = validator.Validate(message); + if (!validationResult.IsValid) + return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + + var projectGroup = await projectGroupRepository.GetById(message.Urn); + + if (projectGroup == null) + { + _logger.LogError($"Project group is not found with ref:{message.Urn}"); + return new NotFoundCommandResult(); + } + + projectGroup.SetProjectGroup(message.TrustReference, dateTimeProvider.Now); + + projectGroupRepository.Update(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + return new CommandSuccessResult(); + } + } +} diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index c4c195a7a..f8249e1ae 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -28,7 +28,7 @@ public ProjectGroupControllerTests() [Fact] public async Task CreateProjectGroup_ReturnsOk() { - var command = new CreateProjectGroupCommand("12345679", "UK3423423"); + var command = new CreateProjectGroupCommand("12345679", []); _mediatrMock.Setup(x => x.Send(command, new CancellationToken())) .ReturnsAsync(new CommandSuccessResult()); diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs index 3196501b1..e76eae08b 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs @@ -1,6 +1,12 @@ using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.Domain.TransferProjectAggregate; +using Dfe.Academies.Academisation.IService.ServiceModels.Application.School; +using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Dfe.Academies.Academisation.Service.Commands.TransferProject; +using Dfe.Academies.Academisation.Service.Queries; using Dfe.Academies.Academisation.WebApi.ActionResults; +using DocumentFormat.OpenXml.Drawing.Spreadsheet; using MediatR; using Microsoft.AspNetCore.Mvc; @@ -32,9 +38,44 @@ public async Task CreateProjectGroup( return result switch { CommandSuccessResult => Ok(), - null => BadRequest(), + BadRequestCommandResult => BadRequest(new { Error = "One or more conversions already associated to another project group" }), _ => new InternalServerErrorObjectResult("Error serving request") }; } + + [HttpPost("{urn}/set-project-group", Name = "SetProjectGroup")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task SetProjectGroup(int urn, [FromBody] SetProjectGroupCommand command, CancellationToken cancellationToken) + { + _logger.LogInformation($"Setting project group: {command}"); + command.Urn = urn; + var result = await _mediator.Send(command, cancellationToken).ConfigureAwait(false); + + return result switch + { + CommandSuccessResult => Ok(), + NotFoundCommandResult => NotFound(), + CommandValidationErrorResult validationErrorResult => BadRequest(validationErrorResult.ValidationErrors), + _ => new InternalServerErrorObjectResult("Error serving request") + }; + } + + [HttpGet("{urn}/get-project-group", Name = "GetProjectGroupById")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetProjectGroupById(string urn, CancellationToken cancellationToken) + { + _logger.LogInformation($"Getting project group with urn: {urn}"); + var query = new GetProjectGroupQueryCommand(urn); + var result = await _mediator.Send(query, cancellationToken); + + if (result is null) + { + return NotFound(); + } + + return result is null? NotFound() : Ok(result); + } } } diff --git a/Dfe.Academies.Academisation.WebApi/Program.cs b/Dfe.Academies.Academisation.WebApi/Program.cs index e1769f384..b07a6bf8e 100644 --- a/Dfe.Academies.Academisation.WebApi/Program.cs +++ b/Dfe.Academies.Academisation.WebApi/Program.cs @@ -164,6 +164,7 @@ builder.Services.AddScoped(typeof(IValidator), typeof(CreateLeaseCommandValidator)); builder.Services.AddScoped(typeof(IValidator), typeof(CreateTransferProjectCommandValidator)); builder.Services.AddScoped(typeof(IValidator), typeof(CreateProjectGroupCommandValidator)); +builder.Services.AddScoped(typeof(IValidator), typeof(SetProjectGroupCommandValidator)); builder.Services.AddHostedService(); builder.Services.AddHostedService(); From 74f217b2eaf52b0f2f858b5f1d06ec7254d631be Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Tue, 23 Jul 2024 16:05:55 +0100 Subject: [PATCH 02/15] Wrapped transaction around project group creation and update logic --- .../AcademisationContext.cs | 14 ++ .../Repositories/GenericRepository.cs | 7 +- .../Repositories/ProjectGroupRepository.cs | 2 + .../Dfe.Academies.Academisation.Domain.csproj | 1 + .../SeedWork/IGenericRepository.cs | 1 - .../SeedWork/IUnitOfWork.cs | 7 +- .../ProjectGroup/IProjectGroupQueryService.cs | 9 ++ .../ProjectGroup/ProjectGroupServiceModel.cs | 9 ++ .../CreateProjectGroupCommandHandlerTests.cs | 10 +- .../CreateProjectGroupCommandValidator.cs | 2 +- .../SetProjectGroupCommandValidator.cs | 2 +- .../ProjectGroup/CreateProjectGroupCommand.cs | 6 +- .../CreateProjectGroupCommandHandler.cs | 55 +++++--- .../GetProjectGroupQueryCommand.cs | 24 ---- .../GetProjectGroupQueryCommandHandler.cs | 29 ---- .../QueryService/ProjectGroupQueryService.cs | 30 ++++ .../ProjectGroup/SetProjectGroupCommand.cs | 8 +- .../SetProjectGroupCommandHandler.cs | 54 +++++--- .../Controller/ProjectGroupControllerTests.cs | 131 +++++++++++++++++- .../Controllers/ProjectGroupController.cs | 38 ++--- Dfe.Academies.Academisation.WebApi/Program.cs | 4 +- 21 files changed, 299 insertions(+), 144 deletions(-) create mode 100644 Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs create mode 100644 Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs delete mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs delete mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs create mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs diff --git a/Dfe.Academies.Academisation.Data/AcademisationContext.cs b/Dfe.Academies.Academisation.Data/AcademisationContext.cs index 6f8012556..e7a21fb2b 100644 --- a/Dfe.Academies.Academisation.Data/AcademisationContext.cs +++ b/Dfe.Academies.Academisation.Data/AcademisationContext.cs @@ -16,6 +16,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.IdentityModel.Tokens; @@ -54,6 +55,19 @@ public override int SaveChanges() return base.SaveChanges(); } + public IExecutionStrategy CreateExecutionStrategy() + { + return base.Database.CreateExecutionStrategy(); + } + public async Task BeginTransactionAsync() + { + return await base.Database.BeginTransactionAsync(); + } + public async Task CommitTransactionAsync() + { + await base.Database.CommitTransactionAsync(); + } + public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { await DispatchDomainEventsAsync(); diff --git a/Dfe.Academies.Academisation.Data/Repositories/GenericRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/GenericRepository.cs index 69477260f..2ff24db0e 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/GenericRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/GenericRepository.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Dfe.Academies.Academisation.Domain.SeedWork; +using Dfe.Academies.Academisation.Domain.SeedWork; using Microsoft.EntityFrameworkCore; namespace Dfe.Academies.Academisation.Data.Repositories diff --git a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs index f7ad779d9..42e9e2525 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs @@ -21,6 +21,8 @@ private IQueryable DefaultIncludes() return x; } + public AcademisationContext Context() => _context; + public async Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken) => await DefaultIncludes().SingleOrDefaultAsync(x => x.ReferenceNumber == referenceNumber, cancellationToken); } diff --git a/Dfe.Academies.Academisation.Domain/Dfe.Academies.Academisation.Domain.csproj b/Dfe.Academies.Academisation.Domain/Dfe.Academies.Academisation.Domain.csproj index f54797e8b..344928881 100644 --- a/Dfe.Academies.Academisation.Domain/Dfe.Academies.Academisation.Domain.csproj +++ b/Dfe.Academies.Academisation.Domain/Dfe.Academies.Academisation.Domain.csproj @@ -10,6 +10,7 @@ + diff --git a/Dfe.Academies.Academisation.Domain/SeedWork/IGenericRepository.cs b/Dfe.Academies.Academisation.Domain/SeedWork/IGenericRepository.cs index 36744a772..d339068ef 100644 --- a/Dfe.Academies.Academisation.Domain/SeedWork/IGenericRepository.cs +++ b/Dfe.Academies.Academisation.Domain/SeedWork/IGenericRepository.cs @@ -14,6 +14,5 @@ public interface IGenericRepository where TEntity : class void Update(TEntity obj); void Delete(int id); void Delete(TEntity entityToDelete); - } } diff --git a/Dfe.Academies.Academisation.Domain/SeedWork/IUnitOfWork.cs b/Dfe.Academies.Academisation.Domain/SeedWork/IUnitOfWork.cs index be592f3ae..2656e2acc 100644 --- a/Dfe.Academies.Academisation.Domain/SeedWork/IUnitOfWork.cs +++ b/Dfe.Academies.Academisation.Domain/SeedWork/IUnitOfWork.cs @@ -1,7 +1,12 @@ -namespace Dfe.Academies.Academisation.Domain.SeedWork; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Dfe.Academies.Academisation.Domain.SeedWork; public interface IUnitOfWork : IDisposable { Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)); + IExecutionStrategy CreateExecutionStrategy(); + Task BeginTransactionAsync(); + Task CommitTransactionAsync(); } diff --git a/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs b/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs new file mode 100644 index 000000000..c1b58599b --- /dev/null +++ b/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs @@ -0,0 +1,9 @@ +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; + +namespace Dfe.Academies.Academisation.IService.Query.ProjectGroup +{ + public interface IProjectGroupQueryService + { + Task GetProjectGroupByUrn(string urn, CancellationToken cancellationToken); + } +} diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs new file mode 100644 index 000000000..dadb0ac22 --- /dev/null +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs @@ -0,0 +1,9 @@ +namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup +{ + public class ProjectGroupServiceModel(string trustUrn) + { + public string TrustUrn { get; private set; } = trustUrn; + + public List ConversionsUrns { get; set; } = new(); + } +} diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index 54ea4ae53..b3bdb9754 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -35,12 +35,12 @@ public CreateProjectGroupCommandHandlerTests() _mockDateTimeProvider = _mockRepository.Create(); _validator = new CreateProjectGroupCommandValidator(); mockCnversionProjectRepository = _mockRepository.Create(); - _mocklogger = _mockRepository.Create>(); + _mocklogger = new Mock>(); var mockContext = new Mock(); _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); } - + private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() { return new CreateProjectGroupCommandHandler( @@ -89,8 +89,8 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou var cancellationToken = CancellationToken.None; _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); mockCnversionProjectRepository.Setup(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.Is(x => x == cancellationToken))).ReturnsAsync(false); ; - mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken))); - + mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x[0] == request.ConversionProjectsUrns[0]), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + // Act var result = await createTransferProjectCommandHandler.Handle( request, @@ -103,7 +103,7 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); mockCnversionProjectRepository.Verify(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken)), Times.Once()); - mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); + mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); } [Fact] diff --git a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs index 125571d6d..cd19b59ea 100644 --- a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs +++ b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs @@ -7,7 +7,7 @@ public class CreateProjectGroupCommandValidator : AbstractValidator x.TrustReference) + RuleFor(x => x.TrustUrn) .NotEmpty().WithMessage("Must specify a trust reference") .Length(8).WithMessage("Trust reference must be length 8") .NotNull().WithMessage("Trust Reference must not be null"); diff --git a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs index bd7135c5a..d1c3155d2 100644 --- a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs +++ b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/SetProjectGroupCommandValidator.cs @@ -7,7 +7,7 @@ public class SetProjectGroupCommandValidator : AbstractValidator x.TrustReference) + RuleFor(x => x.TrustUrn) .NotEmpty().WithMessage("Must specify a trust reference") .Length(8).WithMessage("Trust reference must be length 8") .NotNull().WithMessage("Trust Reference must not be null"); diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs index e8de5ef41..7b9963b52 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs @@ -2,10 +2,10 @@ using Dfe.Academies.Academisation.Core; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommand(string trustReference, List conversionProjects) : IRequest + public class CreateProjectGroupCommand(string trustUrn, List conversionsUrns) : IRequest { - public string TrustReference { get; set; } = trustReference; + public string TrustUrn { get; set; } = trustUrn; - public List ConversionProjectsUrns { get; set; } = conversionProjects; + public List ConversionsUrns { get; set; } = conversionsUrns; } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index b7feeaa6c..d7b630397 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -5,6 +5,8 @@ using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Microsoft.Extensions.Logging; +using Dfe.Academies.Academisation.Service.Extensions; +using Microsoft.EntityFrameworkCore; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { @@ -16,32 +18,47 @@ public async Task Handle(CreateProjectGroupCommand message, Cance var validationResult = validator.Validate(message); if (!validationResult.IsValid) { - logger.LogError($"Validation failed while creating project group with urn:{message}"); + logger.LogError($"Validation failed while validating the request:{message}"); return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); } - var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create( - message.TrustReference, - dateTimeProvider.Now); - - projectGroupRepository.Insert(projectGroup); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - projectGroup.SetProjectReference(projectGroup.Id); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - if (message.ConversionProjectsUrns.Any()) + if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) + { + logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); + return new BadRequestCommandResult(); + } + + var typeName = message.GetGenericTypeName(); + try { - if(await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionProjectsUrns, cancellationToken)) + var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => { - return new BadRequestCommandResult(); - } + await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); + using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) + { + logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); + var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustUrn, dateTimeProvider.Now); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionProjectsUrns, projectGroup.Id, dateTimeProvider.Now, cancellationToken); - await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); - } + projectGroupRepository.Insert(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + projectGroup.SetProjectReference(projectGroup.Id); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - return new CommandSuccessResult(); + if (message.ConversionsUrns.Any()) + { + await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionsUrns, projectGroup.Id, dateTimeProvider.Now, cancellationToken); + } + await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + } + }); + return new CommandSuccessResult(); + } + catch (Exception ex) { + logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); + throw; + } } } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs deleted file mode 100644 index 06bde7321..000000000 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommand.cs +++ /dev/null @@ -1,24 +0,0 @@ - -using MediatR; - -namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup -{ - public class GetProjectGroupQueryCommand : IRequest - { - public string Urn { get; private set; } - - public GetProjectGroupQueryCommand(string urn) - { - Urn = urn; - } - } - - public class ProjectGroupDto - { - public int Urn { get; set; } - - public string TrustReference { get; set; } - - public List ConversionsProjects { get; set; } - } -} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs deleted file mode 100644 index 4134640dd..000000000 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/GetProjectGroupQueryCommandHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using AutoMapper; -using Dfe.Academies.Academisation.Domain.ApplicationAggregate; -using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; -using MediatR; -using Microsoft.Extensions.Logging; - -namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup -{ - public class GetProjectGroupQueryCommandHandler(IProjectGroupRepository projectGroupRepository, IMapper mapper, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler - { - public async Task Handle(GetProjectGroupQueryCommand message, CancellationToken cancellationToken) - { - logger.LogError($"Getting project group with urn:{message.Urn}"); - var projectGroup = await projectGroupRepository.GetByReferenceNumberAsync(message.Urn, cancellationToken); - if (projectGroup == null) { - return null; - } - - var projectGroupDto = mapper.Map(projectGroup); - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); - if (conversionsProjects != null && conversionsProjects.Any()) - { - projectGroupDto.ConversionsProjects = conversionsProjects.Select(x => x.Details.Urn).ToList(); - } - - return projectGroupDto; - } - } -} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs new file mode 100644 index 000000000..6a6bd7628 --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs @@ -0,0 +1,30 @@ +using Dfe.Academies.Academisation.Domain.ApplicationAggregate; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using Dfe.Academies.Academisation.IService.Query.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Microsoft.Extensions.Logging; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService +{ + public class ProjectGroupQueryService(IProjectGroupRepository projectGroupRepository, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IProjectGroupQueryService + { + public async Task GetProjectGroupByUrn(string urn, CancellationToken cancellationToken) + { + logger.LogError($"Getting project group with urn:{urn}"); + var projectGroup = await projectGroupRepository.GetByReferenceNumberAsync(urn, cancellationToken); + if (projectGroup == null) + { + return null; + } + + var projectGroupServiceModel = new ProjectGroupServiceModel(projectGroup.ReferenceNumber!); + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); + if (conversionsProjects != null && conversionsProjects.Any()) + { + projectGroupServiceModel.ConversionsUrns = conversionsProjects.Select(x => x.Details.Urn).ToList(); + } + + return projectGroupServiceModel; + } + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs index 38196da08..6ec6df791 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommand.cs @@ -3,10 +3,10 @@ namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class SetProjectGroupCommand : IRequest + public class SetProjectGroupCommand(string trustUrn, List conversionsUrns) : IRequest { - public required string TrustReference { get; set; } - public required string Urn { get; set; } - public List ConversionProjectsUrns { get; set; } + public string TrustUrn { get; set; } = trustUrn; + public string Urn { get; set; } = string.Empty; + public List ConversionsUrns { get; set; } = conversionsUrns; } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs index fff605c81..2184eeec1 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -5,7 +5,8 @@ using MediatR; using Microsoft.Extensions.Logging; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; -using System.Linq; +using Dfe.Academies.Academisation.Service.Extensions; +using Microsoft.EntityFrameworkCore; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { @@ -27,28 +28,45 @@ public async Task Handle(SetProjectGroupCommand message, Cancella return new NotFoundCommandResult(); } - projectGroup.SetProjectGroup(message.TrustReference, dateTimeProvider.Now); - - projectGroupRepository.Update(projectGroup); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); - if (conversionsProjects != null && conversionsProjects.Any()) + var typeName = message.GetGenericTypeName(); + try { - logger.LogError($"Getting conversions with project group id:{projectGroup.Id}"); + var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); + using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) + { + logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); + projectGroup.SetProjectGroup(message.TrustUrn, dateTimeProvider.Now); - var removedConversionProjectsUrns = conversionsProjects.Where(x - => !message.ConversionProjectsUrns.Contains(x.ProjectGroupId.GetValueOrDefault())).Select(x => x.Details.Urn).ToList(); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(removedConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); - - var addConversionProjectsUrns = message.ConversionProjectsUrns.Except(removedConversionProjectsUrns).ToList(); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(addConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); + projectGroupRepository.Update(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); - } + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); + if (conversionsProjects != null && conversionsProjects.Any()) + { + logger.LogInformation($"Setting conversions with project group id:{projectGroup.Id}"); + + var removedConversionProjectsUrns = conversionsProjects.Where(x + => !message.ConversionsUrns.Contains(x.ProjectGroupId.GetValueOrDefault())).Select(x => x.Details.Urn).ToList(); + await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(removedConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); + var addConversionProjectsUrns = message.ConversionsUrns.Except(removedConversionProjectsUrns).ToList(); + await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(addConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); - return new CommandSuccessResult(); + await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); + } + await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + } + }); + return new CommandSuccessResult(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); + throw; + } } } } diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index f8249e1ae..d039ec0a9 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -1,5 +1,4 @@ using Dfe.Academies.Academisation.Core; -using Dfe.Academies.Academisation.IService.ServiceModels.Application.School; using System.Threading; using Dfe.Academies.Academisation.WebApi.Controllers; using MediatR; @@ -9,7 +8,11 @@ using System.Threading.Tasks; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Http.HttpResults; +using System.Collections.Generic; +using AutoFixture; +using System.Linq; +using Dfe.Academies.Academisation.IService.Query.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; namespace Dfe.Academies.Academisation.WebApi.UnitTest.Controller { @@ -18,25 +21,141 @@ public class ProjectGroupControllerTests private readonly Mock> _loggerMock; private readonly Mock _mediatrMock; private CancellationToken _cancellationToken; + private ProjectGroupController _controller; + private readonly Mock _projectGroupQueryServiceMock; + private readonly Fixture _fixture = new(); + private string _trustUrn; public ProjectGroupControllerTests() { _mediatrMock = new Mock(); _loggerMock = new Mock>(); _cancellationToken = CancellationToken.None; + _projectGroupQueryServiceMock = new Mock(); + _controller = new ProjectGroupController(_mediatrMock.Object, _loggerMock.Object, _projectGroupQueryServiceMock.Object); + _trustUrn = _fixture.Create()[..8]; } [Fact] public async Task CreateProjectGroup_ReturnsOk() { - var command = new CreateProjectGroupCommand("12345679", []); - _mediatrMock.Setup(x => x.Send(command, new CancellationToken())) + // Arrange + var command = new CreateProjectGroupCommand(_trustUrn, []); + _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new CommandSuccessResult()); - var controller = new ProjectGroupController(_mediatrMock.Object, _loggerMock.Object); + // Action + var result = await _controller.CreateProjectGroup(command, _cancellationToken) as OkResult; - var result = await controller.CreateProjectGroup(command, _cancellationToken) as OkResult; + // Assert + Assert.Equal(result!.StatusCode, System.Net.HttpStatusCode.OK.GetHashCode()); + _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); + } + + [Fact] + public async Task CreateProjectGroup_ReturnsBadRequest() + { + // Arrange + var command = new CreateProjectGroupCommand(_trustUrn, []); + _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) + .ReturnsAsync(new BadRequestCommandResult()); + // Action + var result = await _controller.CreateProjectGroup(command, _cancellationToken) as BadRequestObjectResult; + + // Assert + Assert.Equal(result!.StatusCode, System.Net.HttpStatusCode.BadRequest.GetHashCode()); + _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); + } + + [Fact] + public async Task SetProjectGroup_ReturnsOk() + { + // Arrange + var urn = "34234233"; + var command = new SetProjectGroupCommand(_trustUrn, []); + _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) + .ReturnsAsync(new CommandSuccessResult()); + + // Action + var result = await _controller.SetProjectGroup(urn, command, _cancellationToken) as OkResult; + + // Assert Assert.Equal(result!.StatusCode, System.Net.HttpStatusCode.OK.GetHashCode()); + _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); + } + + + [Fact] + public async Task SetProjectGroup_ReturnsNotFound() + { + // Arrange + var urn = "34234233"; + var command = new SetProjectGroupCommand(_trustUrn, []); + _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) + .ReturnsAsync(new NotFoundCommandResult()); + + // Action + var result = await _controller.SetProjectGroup(urn, command, _cancellationToken) as NotFoundResult; + + // Assert + Assert.Equal(result!.StatusCode, System.Net.HttpStatusCode.NotFound.GetHashCode()); + _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); + } + + [Fact] + public async Task SetProjectGroup_ReturnsBadRequest() + { + // Arrange + var urn = "34234233"; + var expectedValidationErrors = _fixture.CreateMany().ToList(); + var command = new SetProjectGroupCommand(_trustUrn, []); + _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) + .ReturnsAsync(new CommandValidationErrorResult(expectedValidationErrors)); + + // Action + var result = await _controller.SetProjectGroup(urn, command, _cancellationToken); + + // Assert + var badRequestResult = Assert.IsType(result); + var validationErrors = Assert.IsAssignableFrom>(badRequestResult.Value); + Assert.Equal(expectedValidationErrors, validationErrors); + _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); + } + + [Fact] + public async Task GetProjectGroupByUrn_ReturnsOk() + { + // Arrange + var projectGroupUrn = "34234233"; + var projectGroupResponse = new ProjectGroupServiceModel(_trustUrn); + _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken)) + .ReturnsAsync(projectGroupResponse); + + // Action + var result = await _controller.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken); + + // Assert + var okObjectResult = Assert.IsType(result.Result); + var response = Assert.IsType(okObjectResult.Value); + Assert.Equal(response.ConversionsUrns.Count, projectGroupResponse.ConversionsUrns.Count); + Assert.Equal(response.TrustUrn, projectGroupResponse.TrustUrn); + _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken), Times.Once()); + } + + [Fact] + public async Task GetProjectGroupByUrn_ReturnsNotFound() + { + // Arrange + var projectGroupUrn = "34234233"; + _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken)) + .ReturnsAsync((ProjectGroupServiceModel?)null); + + // Action + var result = await _controller.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken); + + // Assert + Assert.IsType(result.Result); + _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken), Times.Once()); } } } diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs index 6321b1215..98ba2ede1 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs @@ -1,4 +1,6 @@ using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.IService.Query.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.WebApi.ActionResults; using MediatR; @@ -9,16 +11,8 @@ namespace Dfe.Academies.Academisation.WebApi.Controllers [Route("project-group")] [ApiController] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public class ProjectGroupController : ControllerBase + public class ProjectGroupController(IMediator mediator, ILogger logger, IProjectGroupQueryService projectGroupQueryService) : ControllerBase { - private readonly ILogger _logger; - private readonly IMediator _mediator; - - public ProjectGroupController(IMediator mediator, ILogger logger) - { - _mediator = mediator; - _logger = logger; - } [HttpPost(Name = "CreateProjectGroup")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -26,8 +20,8 @@ public ProjectGroupController(IMediator mediator, ILogger CreateProjectGroup( [FromBody] CreateProjectGroupCommand command, CancellationToken cancellationToken) { - _logger.LogInformation($"Creating project group: {command}"); - var result = await _mediator.Send(command, cancellationToken).ConfigureAwait(false); + logger.LogInformation($"Creating project group: {command}"); + var result = await mediator.Send(command, cancellationToken).ConfigureAwait(false); return result switch { @@ -42,34 +36,28 @@ public async Task CreateProjectGroup( [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task SetProjectGroup(string urn, [FromBody] SetProjectGroupCommand command, CancellationToken cancellationToken) { - _logger.LogInformation($"Setting project group: {command}"); + logger.LogInformation($"Setting project group: {command}"); command.Urn = urn; - var result = await _mediator.Send(command, cancellationToken).ConfigureAwait(false); + var result = await mediator.Send(command, cancellationToken).ConfigureAwait(false); return result switch { CommandSuccessResult => Ok(), NotFoundCommandResult => NotFound(), - CommandValidationErrorResult validationErrorResult => BadRequest(validationErrorResult.ValidationErrors), + CommandValidationErrorResult validationErrorResult => new BadRequestObjectResult(validationErrorResult.ValidationErrors), _ => new InternalServerErrorObjectResult("Error serving request") }; } - [HttpGet("{urn}/get-project-group", Name = "GetProjectGroupById")] + [HttpGet("{urn}/get-project-group", Name = "GetProjectGroupByUrn")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetProjectGroupById(string urn, CancellationToken cancellationToken) + public async Task> GetProjectGroupByUrn(string urn, CancellationToken cancellationToken) { - _logger.LogInformation($"Getting project group with urn: {urn}"); - var query = new GetProjectGroupQueryCommand(urn); - var result = await _mediator.Send(query, cancellationToken); - - if (result is null) - { - return NotFound(); - } + logger.LogInformation($"Getting project group with urn: {urn}"); + var result = await projectGroupQueryService.GetProjectGroupByUrn(urn, cancellationToken); - return result is null? NotFound() : Ok(result); + return result is null ? NotFound() : Ok(result); } } } diff --git a/Dfe.Academies.Academisation.WebApi/Program.cs b/Dfe.Academies.Academisation.WebApi/Program.cs index b07a6bf8e..b2a700c21 100644 --- a/Dfe.Academies.Academisation.WebApi/Program.cs +++ b/Dfe.Academies.Academisation.WebApi/Program.cs @@ -19,11 +19,13 @@ using Dfe.Academies.Academisation.IDomain.Services; using Dfe.Academies.Academisation.IService.Commands.Legacy.Project; using Dfe.Academies.Academisation.IService.Query; +using Dfe.Academies.Academisation.IService.Query.ProjectGroup; using Dfe.Academies.Academisation.IService.ServiceModels.Application.School; using Dfe.Academies.Academisation.Service.Behaviours; using Dfe.Academies.Academisation.Service.Commands.Application.School; using Dfe.Academies.Academisation.Service.Commands.Legacy.Project; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService; using Dfe.Academies.Academisation.Service.Commands.TransferProject; using Dfe.Academies.Academisation.Service.CommandValidations; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; @@ -121,7 +123,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - +builder.Services.AddScoped(); // utils builder.Services.AddSingleton(); From 15b5ab3f35007f5028ddc6c4878a824b7230ed26 Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Tue, 23 Jul 2024 16:57:50 +0100 Subject: [PATCH 03/15] fixisng tests --- .../CreateProjectGroupCommandHandlerTests.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index b3bdb9754..4d86ce73e 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using AutoFixture.AutoMoq; using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; @@ -9,6 +10,7 @@ using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using FluentAssertions; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Moq; @@ -38,7 +40,11 @@ public CreateProjectGroupCommandHandlerTests() _mocklogger = new Mock>(); var mockContext = new Mock(); - _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); + _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); + mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); + mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); + var mockExecuteStrategy = new Mock(); + mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); } private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() @@ -60,7 +66,7 @@ public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectG // Arrange var createTransferProjectCommandHandler = CreateProjectGroupCommandHandler(); - var request = CreateValidCreateTProjectProjectCommand(); + var request = CreateValidCreateTProjectProjectCommand(false); var cancellationToken = CancellationToken.None; // Act @@ -69,7 +75,7 @@ public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectG cancellationToken); // Assert - _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReference + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); @@ -88,8 +94,8 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou var request = CreateValidCreateTProjectProjectCommand(); var cancellationToken = CancellationToken.None; _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); - mockCnversionProjectRepository.Setup(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.Is(x => x == cancellationToken))).ReturnsAsync(false); ; - mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x[0] == request.ConversionProjectsUrns[0]), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + mockCnversionProjectRepository.Setup(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.Is>(x => x == request.ConversionsUrns), It.Is(x => x == cancellationToken))).ReturnsAsync(false); ; + mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); // Act var result = await createTransferProjectCommandHandler.Handle( @@ -97,13 +103,13 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou cancellationToken); // Assert - _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReference + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); mockCnversionProjectRepository.Verify(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken)), Times.Once()); - mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionProjectsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); + mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); } [Fact] @@ -124,9 +130,9 @@ public async Task Handle_InValidCommand_ReturnsBadRequest() cancellationToken); // Assert - Assert.IsType(result); - (result! as CommandValidationErrorResult).ValidationErrors.Should().HaveCount(2); - _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReference + var validationError = Assert.IsType(result); + validationError.ValidationErrors.Should().HaveCount(2); + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Never()); From 925f4b42bb545c64dcc5f992a3e0a8a83355110f Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Thu, 25 Jul 2024 08:48:43 +0100 Subject: [PATCH 04/15] Refactored code --- .../ConversionProjectRepository.cs | 4 +- .../Repositories/ProjectGroupRepository.cs | 67 +++++++++++++++++++ .../IConversionProjectRepository.cs | 2 +- .../IProjectGroupRepository.cs | 3 + .../ProjectGroup/IProjectGroupQueryService.cs | 5 +- .../ProjectGroup/ProjectGroupResponseModel.cs | 17 +++++ .../ProjectGroup/ProjectGroupSearchModel.cs | 16 +++++ .../ProjectGroup/ProjectGroupServiceModel.cs | 9 --- .../CreateProjectGroupCommandHandlerTests.cs | 7 +- .../ProjectGroup/CreateProjectGroupCommand.cs | 2 +- .../CreateProjectGroupCommandHandler.cs | 22 ++++-- .../QueryService/ProjectGroupQueryService.cs | 35 ++++++---- .../SetProjectGroupCommandHandler.cs | 6 +- .../Controller/ProjectGroupControllerTests.cs | 63 ++++++++++------- .../Controllers/ProjectGroupController.cs | 22 +++--- 15 files changed, 202 insertions(+), 78 deletions(-) create mode 100644 Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs create mode 100644 Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs delete mode 100644 Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index 182e0b5a0..fa9b6f5df 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -46,10 +46,10 @@ public async Task AreProjectsAssociateToAnotherProjectGroupAsync(List return await dbSet.AsNoTracking().AnyAsync(x => projectsUrns.Contains(x.Details.Urn) && x.ProjectGroupId != null, cancellationToken); } - public async Task?> GetProjectsByProjectGroupAsync(int projectGroupId, CancellationToken cancellationToken) + public async Task?> GetProjectsByProjectGroupAsync(List projectGroupIds, CancellationToken cancellationToken) { return await _context.Projects - .Where(p => p.ProjectGroupId == projectGroupId) + .Where(p => projectGroupIds.Contains(p.ProjectGroupId.GetValueOrDefault())) .ToListAsync(cancellationToken); } diff --git a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs index 42e9e2525..f90bd5d9f 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs @@ -1,5 +1,6 @@ using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; +using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; using Microsoft.EntityFrameworkCore; namespace Dfe.Academies.Academisation.Data.Repositories @@ -25,5 +26,71 @@ private IQueryable DefaultIncludes() public async Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken) => await DefaultIncludes().SingleOrDefaultAsync(x => x.ReferenceNumber == referenceNumber, cancellationToken); + + public async Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName, string? academyUkprn, string? companiesHouseNo, CancellationToken cancellationToken) + { + IQueryable queryable = DefaultIncludes(); + queryable = FilterByUrn(urn, queryable); + queryable = FilterByTrust(trustUrn, trustName, queryable); + queryable = FilterByAcademy(academyUkprn, academyName, queryable); + queryable = FilterByCompaniesHouseNo(companiesHouseNo, queryable); + + var totalProjectGroups = queryable.Count(); + var projects = await queryable + .OrderByDescending(acp => acp.CreatedOn) + .Skip((page - 1) * count) + .Take(count).ToListAsync(cancellationToken); + + return (projects, totalProjectGroups); + } + + private IQueryable FilterByCompaniesHouseNo(string? companiesHouseNo, IQueryable queryable) + { + if (companiesHouseNo != null) + { + queryable = queryable.Where(p => p.ReferenceNumber == companiesHouseNo); + } + + return queryable; + } + + private IQueryable FilterByAcademy(string? academyUkprn, string? academyName, IQueryable queryable) + { + if (academyUkprn != null) + { + queryable = queryable.Where(p => p.ReferenceNumber == academyUkprn); + } + + if (academyName != null) + { + queryable = queryable.Where(p => p.ReferenceNumber == academyName); + } + + return queryable; + } + + private static IQueryable FilterByUrn(string? urn, IQueryable queryable) + { + if (urn != null) + { + queryable = queryable.Where(p => p.ReferenceNumber == urn); + } + + return queryable; + } + private IQueryable FilterByTrust(string? trustUrn, string? trustName, IQueryable queryable) + { + if (trustUrn != null) + { + queryable = queryable.Where(p => p.TrustReference == trustUrn); + } + if (trustName != null) + { + trustUrn = "todo"; + queryable = queryable.Where(p => p.TrustReference == trustUrn); + } + + return queryable; + } } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index 4c69b75d8..07305273d 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -24,5 +24,5 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, DateTime lastModifiedOn, CancellationToken cancellationToken); Task AreProjectsAssociateToAnotherProjectGroupAsync(List projectsUrns, CancellationToken cancellationToken); - Task?> GetProjectsByProjectGroupAsync(int projectGroupId, CancellationToken cancellationToken); + Task?> GetProjectsByProjectGroupAsync(List projectGroupIds, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs index 3ce762005..09b794a7a 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs @@ -1,9 +1,12 @@ using Dfe.Academies.Academisation.Domain.SeedWork; +using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; namespace Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate { public interface IProjectGroupRepository : IRepository, IGenericRepository { Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken); + Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName, string? academyUkprn, string? companiesHouseNo, CancellationToken cancellationToken); + } } diff --git a/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs b/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs index c1b58599b..3c6aa98e6 100644 --- a/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs +++ b/Dfe.Academies.Academisation.IService/Query/ProjectGroup/IProjectGroupQueryService.cs @@ -1,9 +1,10 @@ -using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; namespace Dfe.Academies.Academisation.IService.Query.ProjectGroup { public interface IProjectGroupQueryService { - Task GetProjectGroupByUrn(string urn, CancellationToken cancellationToken); + Task?> GetProjectGroupsAsync(ProjectGroupSearchModel searchModel, CancellationToken cancellationToken); } } diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs new file mode 100644 index 000000000..ffef10a40 --- /dev/null +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs @@ -0,0 +1,17 @@ +namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup +{ + public class ProjectGroupResponseModel(string urn, string trustUrn, IEnumerable conversions) + { + public string TrustUrn { get; private set; } = trustUrn; + + public string Urn { get; private set; } = urn; + + public IEnumerable Conversions { get; set; } = conversions; + } + + public class ConversionsResponseModel(int urn, string? schoolName) + { + public int urn { get; private set; } = urn; + public string? SoolName { get; private set; } = schoolName; + } +} diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs new file mode 100644 index 000000000..7b981827a --- /dev/null +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs @@ -0,0 +1,16 @@ + +namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup +{ + public class ProjectGroupSearchModel(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName, string? academyUkprn, string? companiesHouseNo) + { + public int Page { get; set; } = page; + public int Count { get; set; } = count; + public string? Urn { get; set; } = urn; + public string? TrustUrn { get; set; } = trustUrn; + public string? TrustName { get; set; } = trustName; + public string? AcademyName { get; set; } = academyName; + public string? AcademyUkprn { get; set; } = academyUkprn; + public string? CompaniesHouseNo { get; set; } = companiesHouseNo; + + } +} diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs deleted file mode 100644 index dadb0ac22..000000000 --- a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupServiceModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup -{ - public class ProjectGroupServiceModel(string trustUrn) - { - public string TrustUrn { get; private set; } = trustUrn; - - public List ConversionsUrns { get; set; } = new(); - } -} diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index 4d86ce73e..f4bdf2b8f 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -1,18 +1,13 @@ -using System.ComponentModel.DataAnnotations; -using AutoFixture.AutoMoq; -using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; -using Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using FluentAssertions; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Moq; using Xunit; diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs index 7b9963b52..d7e8fa2d5 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs @@ -2,7 +2,7 @@ using Dfe.Academies.Academisation.Core; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommand(string trustUrn, List conversionsUrns) : IRequest + public class CreateProjectGroupCommand(string trustUrn, List conversionsUrns) : IRequest { public string TrustUrn { get; set; } = trustUrn; diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index d7b630397..71ca743a6 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -2,35 +2,39 @@ using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; -using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Microsoft.Extensions.Logging; using Dfe.Academies.Academisation.Service.Extensions; using Microsoft.EntityFrameworkCore; +using FluentValidation; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; +using Azure; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler + public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, IValidator validator, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler { - public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) + public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) { logger.LogError($"Creating project group with urn:{message}"); var validationResult = validator.Validate(message); if (!validationResult.IsValid) { logger.LogError($"Validation failed while validating the request:{message}"); - return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + return new CreateValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); } if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) { logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); - return new BadRequestCommandResult(); + return new CreateValidationErrorResult([new ValidationError("ConversionsUrns", "One or more conversions are part of a different project group")]); } - + var typeName = message.GetGenericTypeName(); try { + var responseModel = new ProjectGroupResponseModel("", message.TrustUrn, []); var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); await strategy.ExecuteAsync(async () => { @@ -51,9 +55,13 @@ await strategy.ExecuteAsync(async () => await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionsUrns, projectGroup.Id, dateTimeProvider.Now, cancellationToken); } await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); + + responseModel = new ProjectGroupResponseModel(projectGroup.ReferenceNumber!, projectGroup.TrustReference, conversionsProjects!.Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!))); } }); - return new CommandSuccessResult(); + return new CreateSuccessResult(responseModel); } catch (Exception ex) { logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs index 6a6bd7628..9ea3bd6af 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs @@ -1,30 +1,37 @@ using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using Dfe.Academies.Academisation.IDomain.ProjectAggregate; +using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; using Dfe.Academies.Academisation.IService.Query.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.Service.Factories; using Microsoft.Extensions.Logging; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService { public class ProjectGroupQueryService(IProjectGroupRepository projectGroupRepository, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IProjectGroupQueryService { - public async Task GetProjectGroupByUrn(string urn, CancellationToken cancellationToken) + public async Task?> GetProjectGroupsAsync(ProjectGroupSearchModel searchModel, CancellationToken cancellationToken) { - logger.LogError($"Getting project group with urn:{urn}"); - var projectGroup = await projectGroupRepository.GetByReferenceNumberAsync(urn, cancellationToken); - if (projectGroup == null) - { - return null; - } + logger.LogError($"Searching project group with :{searchModel}"); - var projectGroupServiceModel = new ProjectGroupServiceModel(projectGroup.ReferenceNumber!); - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); - if (conversionsProjects != null && conversionsProjects.Any()) - { - projectGroupServiceModel.ConversionsUrns = conversionsProjects.Select(x => x.Details.Urn).ToList(); - } + var(projectGroups, totalCount) = await projectGroupRepository.SearchProjectGroups(searchModel.Page, searchModel.Count, + searchModel.Urn, searchModel.TrustUrn, searchModel.TrustName, searchModel.AcademyName, searchModel.AcademyUkprn, + searchModel.CompaniesHouseNo, cancellationToken); - return projectGroupServiceModel; + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroups.Select(x => x.Id).ToList(), cancellationToken); + var response = MapToResponse(projectGroups, conversionsProjects); + + var pageResponse = PagingResponseFactory.Create("project-groups/groups", searchModel.Page, searchModel.Count, totalCount, []); + return new PagedDataResponse(response, pageResponse); + } + + private IEnumerable MapToResponse(IEnumerable projectGroups, IEnumerable? conversionsProjects) + { + return projectGroups.Select(x => new ProjectGroupResponseModel(x.ReferenceNumber!, x.TrustReference, + conversionsProjects == null ? [] : conversionsProjects.Where(c => c.ProjectGroupId.GetValueOrDefault() == x.Id) + .Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!)))).ToList(); } } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs index 2184eeec1..e7f902a7f 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -1,16 +1,16 @@ using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Core; -using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using MediatR; using Microsoft.Extensions.Logging; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Dfe.Academies.Academisation.Service.Extensions; using Microsoft.EntityFrameworkCore; +using FluentValidation; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class SetProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, SetProjectGroupCommandValidator validator, ILogger logger, IConversionProjectRepository conversionProjectRepository) : IRequestHandler + public class SetProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, IValidator validator, ILogger logger, IConversionProjectRepository conversionProjectRepository) : IRequestHandler { public async Task Handle(SetProjectGroupCommand message, CancellationToken cancellationToken) { @@ -43,7 +43,7 @@ await strategy.ExecuteAsync(async () => projectGroupRepository.Update(projectGroup); await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroup.Id, cancellationToken); + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); if (conversionsProjects != null && conversionsProjects.Any()) { logger.LogInformation($"Setting conversions with project group id:{projectGroup.Id}"); diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index d039ec0a9..7986f2405 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -13,6 +13,8 @@ using System.Linq; using Dfe.Academies.Academisation.IService.Query.ProjectGroup; using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; +using Azure; namespace Dfe.Academies.Academisation.WebApi.UnitTest.Controller { @@ -39,15 +41,19 @@ public ProjectGroupControllerTests() public async Task CreateProjectGroup_ReturnsOk() { // Arrange + var response = new ProjectGroupResponseModel("12312", _trustUrn, []); var command = new CreateProjectGroupCommand(_trustUrn, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) - .ReturnsAsync(new CommandSuccessResult()); + .ReturnsAsync(new CreateSuccessResult(response)); // Action - var result = await _controller.CreateProjectGroup(command, _cancellationToken) as OkResult; + var result = await _controller.CreateProjectGroup(command, _cancellationToken); // Assert - Assert.Equal(result!.StatusCode, System.Net.HttpStatusCode.OK.GetHashCode()); + var okObjectResult = Assert.IsType(result); + var responseModel = Assert.IsType(result.Value); + Assert.Equal(responseModel.Urn, response.Urn); + Assert.Equal(responseModel.TrustUrn, response.TrustUrn); _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); } @@ -57,13 +63,13 @@ public async Task CreateProjectGroup_ReturnsBadRequest() // Arrange var command = new CreateProjectGroupCommand(_trustUrn, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) - .ReturnsAsync(new BadRequestCommandResult()); + .ReturnsAsync(new CreateValidationErrorResult([new ValidationError("ConversionsUrns", "Validation Error")])); // Action - var result = await _controller.CreateProjectGroup(command, _cancellationToken) as BadRequestObjectResult; + var result = await _controller.CreateProjectGroup(command, _cancellationToken); // Assert - Assert.Equal(result!.StatusCode, System.Net.HttpStatusCode.BadRequest.GetHashCode()); + var badRequestObjectResult = Assert.IsType(result); _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); } @@ -123,39 +129,50 @@ public async Task SetProjectGroup_ReturnsBadRequest() } [Fact] - public async Task GetProjectGroupByUrn_ReturnsOk() + public async Task GetProjectGroups_ReturnsOk() { - // Arrange - var projectGroupUrn = "34234233"; - var projectGroupResponse = new ProjectGroupServiceModel(_trustUrn); - _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken)) - .ReturnsAsync(projectGroupResponse); + // Arrange + var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null, null, null); + var projectGroupResponse = new List { new(searchModel.Urn!, _trustUrn, []) }; + var pagingResponse = new PagedDataResponse(projectGroupResponse, new PagingResponse() + { + Page = 1, + RecordCount = 1 + }); + _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken)) + .ReturnsAsync(pagingResponse); // Action - var result = await _controller.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken); + var result = await _controller.GetProjectGroups(searchModel, _cancellationToken); // Assert var okObjectResult = Assert.IsType(result.Result); - var response = Assert.IsType(okObjectResult.Value); - Assert.Equal(response.ConversionsUrns.Count, projectGroupResponse.ConversionsUrns.Count); - Assert.Equal(response.TrustUrn, projectGroupResponse.TrustUrn); - _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken), Times.Once()); + var response = Assert.IsType>(okObjectResult.Value); + Assert.Equal(response.Data.Count(), projectGroupResponse.Count); + Assert.Equal(response.Paging.RecordCount, pagingResponse.Paging.RecordCount); + Assert.Equal(response.Paging.Page, pagingResponse.Paging.Page); + _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken), Times.Once()); } [Fact] - public async Task GetProjectGroupByUrn_ReturnsNotFound() + public async Task GetProjectGroups_ReturnsNotFound() { // Arrange - var projectGroupUrn = "34234233"; - _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken)) - .ReturnsAsync((ProjectGroupServiceModel?)null); + var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null, null, null); + var pagingResponse = new PagedDataResponse([], new PagingResponse() + { + Page = 1, + RecordCount = 1 + }); + _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken)) + .ReturnsAsync(pagingResponse); // Action - var result = await _controller.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken); + var result = await _controller.GetProjectGroups(searchModel, _cancellationToken); // Assert Assert.IsType(result.Result); - _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupByUrn(projectGroupUrn, _cancellationToken), Times.Once()); + _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken), Times.Once()); } } } diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs index 98ba2ede1..a80614843 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs @@ -1,10 +1,13 @@ using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.IService.Query.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.TransferProject; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.WebApi.ActionResults; using MediatR; using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; namespace Dfe.Academies.Academisation.WebApi.Controllers { @@ -14,10 +17,10 @@ namespace Dfe.Academies.Academisation.WebApi.Controllers public class ProjectGroupController(IMediator mediator, ILogger logger, IProjectGroupQueryService projectGroupQueryService) : ControllerBase { - [HttpPost(Name = "CreateProjectGroup")] + [HttpPost("/create-project-group", Name = "CreateProjectGroup")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateProjectGroup( + public async Task> CreateProjectGroup( [FromBody] CreateProjectGroupCommand command, CancellationToken cancellationToken) { logger.LogInformation($"Creating project group: {command}"); @@ -25,13 +28,13 @@ public async Task CreateProjectGroup( return result switch { - CommandSuccessResult => Ok(), - BadRequestCommandResult => BadRequest(new { Error = "One or more conversions already associated to another project group" }), + CreateSuccessResult successResult => Ok(successResult.Payload), + CreateValidationErrorResult validationErrorResult => BadRequest(validationErrorResult.ValidationErrors), _ => new InternalServerErrorObjectResult("Error serving request") }; } - [HttpPost("{urn}/set-project-group", Name = "SetProjectGroup")] + [HttpPut("{urn}/set-project-group", Name = "SetProjectGroup")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task SetProjectGroup(string urn, [FromBody] SetProjectGroupCommand command, CancellationToken cancellationToken) @@ -49,15 +52,14 @@ public async Task SetProjectGroup(string urn, [FromBody] SetProje }; } - [HttpGet("{urn}/get-project-group", Name = "GetProjectGroupByUrn")] + [HttpGet("{urn}/get-project-groups", Name = "GetProjectGroups")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetProjectGroupByUrn(string urn, CancellationToken cancellationToken) + public async Task>> GetProjectGroups(ProjectGroupSearchModel searchModel, CancellationToken cancellationToken) { - logger.LogInformation($"Getting project group with urn: {urn}"); - var result = await projectGroupQueryService.GetProjectGroupByUrn(urn, cancellationToken); + var result = await projectGroupQueryService.GetProjectGroupsAsync(searchModel, cancellationToken); - return result is null ? NotFound() : Ok(result); + return result is null || result.Data.IsNullOrEmpty() ? NotFound() : Ok(result); } } } From 86150365da243b8f599978f3eaba44ef84244758 Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Thu, 25 Jul 2024 09:45:54 +0100 Subject: [PATCH 05/15] Fixed search endpoint --- .../Repositories/ProjectGroupRepository.cs | 13 +++++++------ .../Controllers/ProjectGroupController.cs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs index f90bd5d9f..89085a4fb 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs @@ -2,6 +2,7 @@ using Dfe.Academies.Academisation.Domain.SeedWork; using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; namespace Dfe.Academies.Academisation.Data.Repositories { @@ -46,7 +47,7 @@ private IQueryable DefaultIncludes() private IQueryable FilterByCompaniesHouseNo(string? companiesHouseNo, IQueryable queryable) { - if (companiesHouseNo != null) + if (!companiesHouseNo.IsNullOrEmpty()) { queryable = queryable.Where(p => p.ReferenceNumber == companiesHouseNo); } @@ -56,12 +57,12 @@ private IQueryable FilterByCompaniesHouseNo(string? companiesHouse private IQueryable FilterByAcademy(string? academyUkprn, string? academyName, IQueryable queryable) { - if (academyUkprn != null) + if (!academyUkprn.IsNullOrEmpty()) { queryable = queryable.Where(p => p.ReferenceNumber == academyUkprn); } - if (academyName != null) + if (!academyName.IsNullOrEmpty()) { queryable = queryable.Where(p => p.ReferenceNumber == academyName); } @@ -71,7 +72,7 @@ private IQueryable FilterByAcademy(string? academyUkprn, string? a private static IQueryable FilterByUrn(string? urn, IQueryable queryable) { - if (urn != null) + if (!urn.IsNullOrEmpty()) { queryable = queryable.Where(p => p.ReferenceNumber == urn); } @@ -80,11 +81,11 @@ private static IQueryable FilterByUrn(string? urn, IQueryable FilterByTrust(string? trustUrn, string? trustName, IQueryable queryable) { - if (trustUrn != null) + if (!trustUrn.IsNullOrEmpty()) { queryable = queryable.Where(p => p.TrustReference == trustUrn); } - if (trustName != null) + if (!trustName.IsNullOrEmpty()) { trustUrn = "todo"; queryable = queryable.Where(p => p.TrustReference == trustUrn); diff --git a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs index b0ce7e2c7..297474aa1 100644 --- a/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs +++ b/Dfe.Academies.Academisation.WebApi/Controllers/ProjectGroupController.cs @@ -52,7 +52,7 @@ public async Task SetProjectGroup(string urn, [FromBody] SetProje }; } - [HttpGet("{urn}/get-project-groups", Name = "GetProjectGroups")] + [HttpGet("/project-group/get-project-groups", Name = "GetProjectGroups")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> GetProjectGroups(ProjectGroupSearchModel searchModel, CancellationToken cancellationToken) From 194ee65a36a2ec7d5bee0237df44423ceb7bc242 Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Thu, 25 Jul 2024 10:21:01 +0100 Subject: [PATCH 06/15] refacored code --- .../Repositories/ConversionProjectRepository.cs | 5 ----- .../ProjectAggregate/IConversionProjectRepository.cs | 1 - .../CreateProjectGroupCommandHandlerTests.cs | 12 ++++++++---- .../ProjectGroup/CreateProjectGroupCommandHandler.cs | 10 ++++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index d1b3b34bc..b559993e3 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -41,11 +41,6 @@ public ConversionProjectRepository(AcademisationContext context, IMapper mapper) return (projects, totalProjects); } - public async Task AreProjectsAssociateToAnotherProjectGroupAsync(List projectsUrns, CancellationToken cancellationToken) - { - return await dbSet.AsNoTracking().AnyAsync(x => projectsUrns.Contains(x.Details.Urn) && x.ProjectGroupId != null, cancellationToken); - } - public async Task?> GetProjectsByProjectGroupAsync(List projectGroupIds, CancellationToken cancellationToken) { return await _context.Projects diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index b9c98d007..060056d35 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -24,6 +24,5 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task> GetConversionProjectsForGroup(string trustReferenceNumber, CancellationToken cancellationToken); Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, DateTime lastModifiedOn, CancellationToken cancellationToken); - Task AreProjectsAssociateToAnotherProjectGroupAsync(List projectsUrns, CancellationToken cancellationToken); Task?> GetProjectsByProjectGroupAsync(List projectGroupIds, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index f4bdf2b8f..ecb05031c 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -1,6 +1,8 @@ -using Dfe.Academies.Academisation.Core; +using AutoFixture; +using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; +using Dfe.Academies.Academisation.Domain.ProjectAggregate; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; @@ -23,6 +25,7 @@ public class CreateProjectGroupCommandHandlerTests private CreateProjectGroupCommandValidator _validator; private Mock mockCnversionProjectRepository; private Mock> _mocklogger; + private readonly Fixture _fixture = new(); public CreateProjectGroupCommandHandlerTests() { @@ -75,7 +78,7 @@ public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectG && x.CreatedOn == now)), Times.Once()); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); - mockCnversionProjectRepository.Verify(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken)), Times.Never()); + mockCnversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Never()); mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Never()); } @@ -89,7 +92,8 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou var request = CreateValidCreateTProjectProjectCommand(); var cancellationToken = CancellationToken.None; _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); - mockCnversionProjectRepository.Setup(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.Is>(x => x == request.ConversionsUrns), It.Is(x => x == cancellationToken))).ReturnsAsync(false); ; + var expectedProjects = _fixture.Create>(); + mockCnversionProjectRepository.Setup(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken))).ReturnsAsync(expectedProjects); mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); // Act @@ -103,7 +107,7 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou && x.CreatedOn == now)), Times.Once()); _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); - mockCnversionProjectRepository.Verify(x => x.AreProjectsAssociateToAnotherProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken)), Times.Once()); + mockCnversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Once()); mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index 71ca743a6..7dab3d336 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -25,11 +25,13 @@ public async Task Handle(CreateProjectGroupCommand message, Cancel return new CreateValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); } - if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) + if (message.ConversionsUrns.Any()) { - logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); - return new CreateValidationErrorResult([new ValidationError("ConversionsUrns", "One or more conversions are part of a different project group")]); - } + var converstions = await conversionProjectRepository.GetConversionProjectsForGroup(message.TrustUrn, cancellationToken); + if (converstions == null) { + logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); + return new CreateValidationErrorResult([new ValidationError("ConversionsUrns", "One or more conversions are part of a different project group")]); + } } var typeName = message.GetGenericTypeName(); try From 4f8e97ec5ae225a3770e8caa8d8a47d836270303 Mon Sep 17 00:00:00 2001 From: plockwood Date: Thu, 25 Jul 2024 12:21:44 +0100 Subject: [PATCH 07/15] merge from remote --- .../IConversionProjectRepository.cs | 1 + .../ProjectAggregate/Project.cs | 5 + .../ProjectAggregate/IProject.cs | 1 + .../CreateProjectGroupCommandHandler.cs | 138 +++++++++++------- .../QueryService/ProjectGroupQueryService.cs | 37 ----- Dfe.Academies.Academisation.WebApi/Program.cs | 1 - 6 files changed, 92 insertions(+), 91 deletions(-) delete mode 100644 Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index bf1cded56..ce5ab7cd4 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -15,6 +15,7 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task<(IEnumerable projects, int totalCount)> SearchProjectsV2( IEnumerable? states, string? title, IEnumerable? deliveryOfficers, IEnumerable? regions, IEnumerable? localAuthorities, IEnumerable? advisoryBoardDates, int page, int count); Task> GetConversionProjectsThatRequireFormAMatCreation(CancellationToken cancellationToken); + Task> GetConversionProjectsByUrns(IEnumerable urns, CancellationToken cancellationToken); Task> GetConversionProjectsByFormAMatId(int? id, CancellationToken cancellationToken); Task> GetConversionProjectsByFormAMatIds(IEnumerable ids, CancellationToken cancellationToken); Task<(IEnumerable projects, int totalCount)> SearchFormAMatProjects( diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs index 0a5facc4e..fa974f591 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs @@ -550,6 +550,11 @@ public void SetFormAMatProjectId(int id) } } + public void SetProjectGroupId(int id) + { + ProjectGroupId = id; + } + public void SetIncomingTrust(string trustReferrenceNumber, string trustName) { Details.SetIncomingTrust(trustReferrenceNumber, trustName); diff --git a/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs b/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs index 8ccf44767..20522b233 100644 --- a/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs +++ b/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs @@ -47,6 +47,7 @@ public void SetSchoolOverview( public void SetPerformanceData(string? keyStage2PerformanceAdditionalInformation, string? keyStage4PerformanceAdditionalInformation, string? keyStage5PerformanceAdditionalInformation, string? educationalAttendanceAdditionalInformation); void SetFormAMatProjectId(int id); + void SetProjectGroupId(int id); void SetRoute(string route); void AddNote(string subject, string note, string author, DateTime date); void RemoveNote(int id); diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index a364c08b4..99e99f78f 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -5,71 +5,103 @@ using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Microsoft.Extensions.Logging; -using Dfe.Academies.Academisation.Service.Extensions; -using Microsoft.EntityFrameworkCore; -using FluentValidation; -using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; -using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; -using Azure; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler { + //public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) + //{ + // logger.LogError($"Creating project group with urn:{message}"); + // var validationResult = validator.Validate(message); + // if (!validationResult.IsValid) + // { + // logger.LogError($"Validation failed while validating the request:{message}"); + // return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + // } + + // if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) + // { + // logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); + // return new BadRequestCommandResult(); + // } + + // var typeName = message.GetGenericTypeName(); + // try + // { + // var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); + // await strategy.ExecuteAsync(async () => + // { + // await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); + // using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) + // { + // logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); + // var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustUrn, dateTimeProvider.Now); + + // projectGroupRepository.Insert(projectGroup); + // await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + // projectGroup.SetProjectReference(projectGroup.Id); + // await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + // if (message.ConversionsUrns.Any()) + // { + // await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionsUrns, projectGroup.Id, dateTimeProvider.Now, cancellationToken); + // } + // await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + // } + // }); + // return new CommandSuccessResult(); + // } + // catch (Exception ex) { + // logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); + // throw; + // } + //} + public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) { - logger.LogError($"Creating project group with urn:{message}"); - var validationResult = validator.Validate(message); - if (!validationResult.IsValid) + // logger.LogError($"Creating project group with urn:{message}"); + // var validationResult = validator.Validate(message); + // if (!validationResult.IsValid) + // { + // logger.LogError($"Validation failed while validating the request:{message}"); + // return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + // } + + // if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) + // { + // logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); + // return new BadRequestCommandResult(); + // } + + var conversionProjects = await conversionProjectRepository.GetConversionProjectsByUrns(message.ConversionsUrns, cancellationToken).ConfigureAwait(false); + + if (conversionProjects == null || !conversionProjects.Any()) { - logger.LogError($"Validation failed while validating the request:{message}"); - return new CreateValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + logger.LogInformation($"No conversion projects found which require form a mat projects creating."); + return new NotFoundCommandResult(); } - if (message.ConversionsUrns.Any()) + foreach (var conversionProject in conversionProjects) { - var converstions = await conversionProjectRepository.GetConversionProjectsForGroup(message.TrustUrn, cancellationToken); - if (converstions == null) { - logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); - return new CreateValidationErrorResult([new ValidationError("ConversionsUrns", "One or more conversions are part of a different project group")]); - } } - - var typeName = message.GetGenericTypeName(); - try - { - var responseModel = new ProjectGroupResponseModel("", message.TrustUrn, []); - var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); - await strategy.ExecuteAsync(async () => - { - await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); - using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) - { - logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); - var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustUrn, dateTimeProvider.Now); - - projectGroupRepository.Insert(projectGroup); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - projectGroup.SetProjectReference(projectGroup.Id); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - if (message.ConversionsUrns.Any()) - { - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionsUrns, projectGroup.Id, dateTimeProvider.Now, cancellationToken); - } - await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); - - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); - - responseModel = new ProjectGroupResponseModel(projectGroup.ReferenceNumber!, projectGroup.TrustReference, conversionsProjects!.Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!))); - } - }); - return new CreateSuccessResult(responseModel); - } - catch (Exception ex) { - logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); - throw; + // create project group + var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustUrn, dateTimeProvider.Now); + + projectGroupRepository.Insert(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + conversionProject.SetProjectGroupId(projectGroup.Id); + + conversionProjectRepository.Update(conversionProject as Domain.ProjectAggregate.Project); } + + + await conversionProjectRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + + + // returning 'CommandSuccessResult', client will have to retrieve the updated transfer project to refresh data + return new CommandSuccessResult(); } } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs deleted file mode 100644 index 9ea3bd6af..000000000 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/QueryService/ProjectGroupQueryService.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Dfe.Academies.Academisation.Domain.ApplicationAggregate; -using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; -using Dfe.Academies.Academisation.IDomain.ProjectAggregate; -using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; -using Dfe.Academies.Academisation.IService.Query.ProjectGroup; -using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; -using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; -using Dfe.Academies.Academisation.Service.Factories; -using Microsoft.Extensions.Logging; - -namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService -{ - public class ProjectGroupQueryService(IProjectGroupRepository projectGroupRepository, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IProjectGroupQueryService - { - public async Task?> GetProjectGroupsAsync(ProjectGroupSearchModel searchModel, CancellationToken cancellationToken) - { - logger.LogError($"Searching project group with :{searchModel}"); - - var(projectGroups, totalCount) = await projectGroupRepository.SearchProjectGroups(searchModel.Page, searchModel.Count, - searchModel.Urn, searchModel.TrustUrn, searchModel.TrustName, searchModel.AcademyName, searchModel.AcademyUkprn, - searchModel.CompaniesHouseNo, cancellationToken); - - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroups.Select(x => x.Id).ToList(), cancellationToken); - var response = MapToResponse(projectGroups, conversionsProjects); - - var pageResponse = PagingResponseFactory.Create("project-groups/groups", searchModel.Page, searchModel.Count, totalCount, []); - return new PagedDataResponse(response, pageResponse); - } - - private IEnumerable MapToResponse(IEnumerable projectGroups, IEnumerable? conversionsProjects) - { - return projectGroups.Select(x => new ProjectGroupResponseModel(x.ReferenceNumber!, x.TrustReference, - conversionsProjects == null ? [] : conversionsProjects.Where(c => c.ProjectGroupId.GetValueOrDefault() == x.Id) - .Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!)))).ToList(); - } - } -} diff --git a/Dfe.Academies.Academisation.WebApi/Program.cs b/Dfe.Academies.Academisation.WebApi/Program.cs index dd8ea273e..554cd40fe 100644 --- a/Dfe.Academies.Academisation.WebApi/Program.cs +++ b/Dfe.Academies.Academisation.WebApi/Program.cs @@ -26,7 +26,6 @@ using Dfe.Academies.Academisation.Service.Commands.Application.School; using Dfe.Academies.Academisation.Service.Commands.Legacy.Project; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; -using Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService; using Dfe.Academies.Academisation.Service.Commands.TransferProject; using Dfe.Academies.Academisation.Service.CommandValidations; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; From 5e3d2780ec312c06341ed58bd0fea7d26ce8a5d9 Mon Sep 17 00:00:00 2001 From: plockwood Date: Thu, 25 Jul 2024 12:22:45 +0100 Subject: [PATCH 08/15] move query service --- .../Queries/ProjectGroupQueryService.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs diff --git a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs new file mode 100644 index 000000000..9ea3bd6af --- /dev/null +++ b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs @@ -0,0 +1,37 @@ +using Dfe.Academies.Academisation.Domain.ApplicationAggregate; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using Dfe.Academies.Academisation.IDomain.ProjectAggregate; +using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; +using Dfe.Academies.Academisation.IService.Query.ProjectGroup; +using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; +using Dfe.Academies.Academisation.Service.Factories; +using Microsoft.Extensions.Logging; + +namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService +{ + public class ProjectGroupQueryService(IProjectGroupRepository projectGroupRepository, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IProjectGroupQueryService + { + public async Task?> GetProjectGroupsAsync(ProjectGroupSearchModel searchModel, CancellationToken cancellationToken) + { + logger.LogError($"Searching project group with :{searchModel}"); + + var(projectGroups, totalCount) = await projectGroupRepository.SearchProjectGroups(searchModel.Page, searchModel.Count, + searchModel.Urn, searchModel.TrustUrn, searchModel.TrustName, searchModel.AcademyName, searchModel.AcademyUkprn, + searchModel.CompaniesHouseNo, cancellationToken); + + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroups.Select(x => x.Id).ToList(), cancellationToken); + var response = MapToResponse(projectGroups, conversionsProjects); + + var pageResponse = PagingResponseFactory.Create("project-groups/groups", searchModel.Page, searchModel.Count, totalCount, []); + return new PagedDataResponse(response, pageResponse); + } + + private IEnumerable MapToResponse(IEnumerable projectGroups, IEnumerable? conversionsProjects) + { + return projectGroups.Select(x => new ProjectGroupResponseModel(x.ReferenceNumber!, x.TrustReference, + conversionsProjects == null ? [] : conversionsProjects.Where(c => c.ProjectGroupId.GetValueOrDefault() == x.Id) + .Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!)))).ToList(); + } + } +} From e68401cee1a144f46568eefbeb40582a3e82b9c0 Mon Sep 17 00:00:00 2001 From: plockwood Date: Thu, 25 Jul 2024 14:07:08 +0100 Subject: [PATCH 09/15] Refactored the create group handler and fixed tests --- .../ConversionProjectRepository.cs | 7 ++ .../IConversionProjectRepository.cs | 4 +- .../ProjectGroup/ProjectGroupResponseModel.cs | 2 +- .../CreateProjectGroupCommandHandlerTests.cs | 47 ++++--- .../CreateProjectGroupCommandValidator.cs | 2 +- .../ProjectGroup/CreateProjectGroupCommand.cs | 5 +- .../CreateProjectGroupCommandHandler.cs | 119 ++++++------------ .../Controller/ProjectGroupControllerTests.cs | 25 ++-- Dfe.Academies.Academisation.WebApi/Program.cs | 1 + 9 files changed, 88 insertions(+), 124 deletions(-) diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index b559993e3..660b49095 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -298,5 +298,12 @@ public async Task> GetConversionProjectsForGroup(string tr return projects; } + + public async Task> GetConversionProjectsByUrns(IEnumerable urns, CancellationToken cancellationToken) + { + var projects = await this.dbSet.Where(x => urns.Contains(x.Details.Urn)).ToListAsync(cancellationToken); + + return projects; + } } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index ce5ab7cd4..d7143addc 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -25,7 +25,5 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task> GetConversionProjectsForGroup(string trustReferenceNumber, CancellationToken cancellationToken); Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, DateTime lastModifiedOn, CancellationToken cancellationToken); - Task AreProjectsAssociateToAnotherProjectGroupAsync(List projectsUrns, CancellationToken cancellationToken); - Task?> GetProjectsByProjectGroupAsync(int projectGroupId, CancellationToken cancellationToken); - Task> GetConversionProjectsForGroup(string trustReferenceNumber, CancellationToken cancellationToken); + Task?> GetProjectsByProjectGroupAsync(List projectGroupIds, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs index ffef10a40..a491bbd7d 100644 --- a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs @@ -12,6 +12,6 @@ public class ProjectGroupResponseModel(string urn, string trustUrn, IEnumerable< public class ConversionsResponseModel(int urn, string? schoolName) { public int urn { get; private set; } = urn; - public string? SoolName { get; private set; } = schoolName; + public string? SchoolName { get; private set; } = schoolName; } } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index ecb05031c..aa3dc1352 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -20,10 +20,10 @@ public class CreateProjectGroupCommandHandlerTests private MockRepository _mockRepository; private Mock _mockProjectGroupRepository; + private Mock _mockConversionProjectRepository; private Mock _mockDateTimeProvider; private CreateProjectGroupCommandValidator _validator; - private Mock mockCnversionProjectRepository; private Mock> _mocklogger; private readonly Fixture _fixture = new(); @@ -33,16 +33,18 @@ public CreateProjectGroupCommandHandlerTests() _mockProjectGroupRepository = _mockRepository.Create(); _mockDateTimeProvider = _mockRepository.Create(); + _mockConversionProjectRepository = _mockRepository.Create(); _validator = new CreateProjectGroupCommandValidator(); - mockCnversionProjectRepository = _mockRepository.Create(); _mocklogger = new Mock>(); var mockContext = new Mock(); - _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); - mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); - mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); - var mockExecuteStrategy = new Mock(); - mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); + _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); + _mockConversionProjectRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); + + //mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); + //mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); + //var mockExecuteStrategy = new Mock(); + //mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); } private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() @@ -51,7 +53,7 @@ private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() _mockProjectGroupRepository.Object, _mockDateTimeProvider.Object, _validator, - mockCnversionProjectRepository.Object, + _mockConversionProjectRepository.Object, _mocklogger.Object); } @@ -73,13 +75,11 @@ public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectG cancellationToken); // Assert - _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReferenceNumber && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); - mockCnversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Never()); - mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Never()); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Exactly(2)); } [Fact] @@ -93,23 +93,21 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou var cancellationToken = CancellationToken.None; _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); var expectedProjects = _fixture.Create>(); - mockCnversionProjectRepository.Setup(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken))).ReturnsAsync(expectedProjects); - mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByUrns(request.ConversionsUrns, It.Is(x => x == cancellationToken))).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); + // Act var result = await createTransferProjectCommandHandler.Handle( request, cancellationToken); // Assert - _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReferenceNumber && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); - mockCnversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Once()); - mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); - } + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Exactly(3)); + } [Fact] public async Task Handle_InValidCommand_ReturnsBadRequest() @@ -120,7 +118,7 @@ public async Task Handle_InValidCommand_ReturnsBadRequest() // Arrange var createTransferProjectCommandHandler = CreateProjectGroupCommandHandler(); - var request = new CreateProjectGroupCommand(string.Empty, [3424]); + var request = new CreateProjectGroupCommand(string.Empty, string.Empty, [3424]); var cancellationToken = CancellationToken.None; // Act @@ -129,9 +127,9 @@ public async Task Handle_InValidCommand_ReturnsBadRequest() cancellationToken); // Assert - var validationError = Assert.IsType(result); + var validationError = Assert.IsType(result); validationError.ValidationErrors.Should().HaveCount(2); - _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustReferenceNumber && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Never()); @@ -141,8 +139,9 @@ public async Task Handle_InValidCommand_ReturnsBadRequest() private static CreateProjectGroupCommand CreateValidCreateTProjectProjectCommand(bool includeConversions = true) { string trustReference = "11112222"; + string trustUkprn = "1111333"; - return new CreateProjectGroupCommand(trustReference, includeConversions ? [03823] : []); + return new CreateProjectGroupCommand(trustReference, trustUkprn, includeConversions ? [03823] : []); } } } diff --git a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs index cd19b59ea..f1a2cdfc3 100644 --- a/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs +++ b/Dfe.Academies.Academisation.Service/CommandValidations/ProjectGroup/CreateProjectGroupCommandValidator.cs @@ -7,7 +7,7 @@ public class CreateProjectGroupCommandValidator : AbstractValidator x.TrustUrn) + RuleFor(x => x.TrustReferenceNumber) .NotEmpty().WithMessage("Must specify a trust reference") .Length(8).WithMessage("Trust reference must be length 8") .NotNull().WithMessage("Trust Reference must not be null"); diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs index d7e8fa2d5..03f25e95c 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommand.cs @@ -2,9 +2,10 @@ using Dfe.Academies.Academisation.Core; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommand(string trustUrn, List conversionsUrns) : IRequest + public class CreateProjectGroupCommand(string trustReferenceNumber, string trustUkprn, List conversionsUrns) : IRequest { - public string TrustUrn { get; set; } = trustUrn; + public string TrustReferenceNumber { get; set; } = trustReferenceNumber; + public string TrustUkprn { get; set; } = trustUkprn; public List ConversionsUrns { get; set; } = conversionsUrns; } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index 99e99f78f..ecc0c9071 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -5,103 +5,60 @@ using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Microsoft.Extensions.Logging; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler + public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler { - //public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) - //{ - // logger.LogError($"Creating project group with urn:{message}"); - // var validationResult = validator.Validate(message); - // if (!validationResult.IsValid) - // { - // logger.LogError($"Validation failed while validating the request:{message}"); - // return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); - // } - - // if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) - // { - // logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); - // return new BadRequestCommandResult(); - // } - - // var typeName = message.GetGenericTypeName(); - // try - // { - // var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); - // await strategy.ExecuteAsync(async () => - // { - // await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); - // using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) - // { - // logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); - // var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustUrn, dateTimeProvider.Now); - - // projectGroupRepository.Insert(projectGroup); - // await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - // projectGroup.SetProjectReference(projectGroup.Id); - // await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - - // if (message.ConversionsUrns.Any()) - // { - // await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(message.ConversionsUrns, projectGroup.Id, dateTimeProvider.Now, cancellationToken); - // } - // await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); - // } - // }); - // return new CommandSuccessResult(); - // } - // catch (Exception ex) { - // logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); - // throw; - // } - //} - - public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) + public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) { - // logger.LogError($"Creating project group with urn:{message}"); - // var validationResult = validator.Validate(message); - // if (!validationResult.IsValid) - // { - // logger.LogError($"Validation failed while validating the request:{message}"); - // return new CommandValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); - // } + logger.LogError($"Creating project group with urn:{message}"); + var validationResult = validator.Validate(message); + if (!validationResult.IsValid) + { + logger.LogError($"Validation failed while validating the request:{message}"); + return new CreateValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + } - // if (message.ConversionsUrns.Any() && await conversionProjectRepository.AreProjectsAssociateToAnotherProjectGroupAsync(message.ConversionsUrns, cancellationToken)) - // { - // logger.LogError($"Validation failed because one or more conversions are part of a different project group:{message}"); - // return new BadRequestCommandResult(); - // } + // create project group + var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustReferenceNumber, dateTimeProvider.Now); + + projectGroupRepository.Insert(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - var conversionProjects = await conversionProjectRepository.GetConversionProjectsByUrns(message.ConversionsUrns, cancellationToken).ConfigureAwait(false); + projectGroup.SetProjectReference(projectGroup.Id); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - if (conversionProjects == null || !conversionProjects.Any()) - { - logger.LogInformation($"No conversion projects found which require form a mat projects creating."); - return new NotFoundCommandResult(); - } + var conversionsProjectModels = new List(); - foreach (var conversionProject in conversionProjects) + // if any conversion were part of the message add them to the group + if (message.ConversionsUrns.Any()) { - // create project group - var projectGroup = Domain.ProjectGroupsAggregate.ProjectGroup.Create(message.TrustUrn, dateTimeProvider.Now); + var conversionProjects = await conversionProjectRepository.GetConversionProjectsByUrns(message.ConversionsUrns, cancellationToken).ConfigureAwait(false); - projectGroupRepository.Insert(projectGroup); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + if (conversionProjects == null || !conversionProjects.Any()) + { + logger.LogError($"No conversion projects found for the urns passed to create the group."); + return new CreateValidationErrorResult(validationResult.Errors.Select(r => new ValidationError(r.PropertyName, r.ErrorMessage))); + } - conversionProject.SetProjectGroupId(projectGroup.Id); - - conversionProjectRepository.Update(conversionProject as Domain.ProjectAggregate.Project); - } + foreach (var conversionProject in conversionProjects) + { + conversionProject.SetProjectGroupId(projectGroup.Id); + conversionProjectRepository.Update(conversionProject as Domain.ProjectAggregate.Project); + } + + await conversionProjectRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - await conversionProjectRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + conversionsProjectModels = conversionProjects.Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!)).ToList(); + } + + var responseModel = new ProjectGroupResponseModel(projectGroup.ReferenceNumber!, projectGroup.TrustReference, conversionsProjectModels); + return new CreateSuccessResult(responseModel); - // returning 'CommandSuccessResult', client will have to retrieve the updated transfer project to refresh data - return new CommandSuccessResult(); } } } diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index 7986f2405..a4cfa149e 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -26,7 +26,8 @@ public class ProjectGroupControllerTests private ProjectGroupController _controller; private readonly Mock _projectGroupQueryServiceMock; private readonly Fixture _fixture = new(); - private string _trustUrn; + private string _trustReferenceNumber; + private string _trustUkprn; public ProjectGroupControllerTests() { _mediatrMock = new Mock(); @@ -34,15 +35,15 @@ public ProjectGroupControllerTests() _cancellationToken = CancellationToken.None; _projectGroupQueryServiceMock = new Mock(); _controller = new ProjectGroupController(_mediatrMock.Object, _loggerMock.Object, _projectGroupQueryServiceMock.Object); - _trustUrn = _fixture.Create()[..8]; + _trustReferenceNumber = _fixture.Create()[..8]; } [Fact] public async Task CreateProjectGroup_ReturnsOk() { // Arrange - var response = new ProjectGroupResponseModel("12312", _trustUrn, []); - var command = new CreateProjectGroupCommand(_trustUrn, []); + var response = new ProjectGroupResponseModel("12312", _trustReferenceNumber, []); + var command = new CreateProjectGroupCommand(_trustReferenceNumber, _trustUkprn, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new CreateSuccessResult(response)); @@ -50,8 +51,8 @@ public async Task CreateProjectGroup_ReturnsOk() var result = await _controller.CreateProjectGroup(command, _cancellationToken); // Assert - var okObjectResult = Assert.IsType(result); - var responseModel = Assert.IsType(result.Value); + var okObjectResult = Assert.IsType(result.Result); + var responseModel = Assert.IsType(okObjectResult.Value); Assert.Equal(responseModel.Urn, response.Urn); Assert.Equal(responseModel.TrustUrn, response.TrustUrn); _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); @@ -61,7 +62,7 @@ public async Task CreateProjectGroup_ReturnsOk() public async Task CreateProjectGroup_ReturnsBadRequest() { // Arrange - var command = new CreateProjectGroupCommand(_trustUrn, []); + var command = new CreateProjectGroupCommand(_trustReferenceNumber, _trustUkprn, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new CreateValidationErrorResult([new ValidationError("ConversionsUrns", "Validation Error")])); @@ -69,7 +70,7 @@ public async Task CreateProjectGroup_ReturnsBadRequest() var result = await _controller.CreateProjectGroup(command, _cancellationToken); // Assert - var badRequestObjectResult = Assert.IsType(result); + var badRequestObjectResult = Assert.IsType(result.Result); _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); } @@ -78,7 +79,7 @@ public async Task SetProjectGroup_ReturnsOk() { // Arrange var urn = "34234233"; - var command = new SetProjectGroupCommand(_trustUrn, []); + var command = new SetProjectGroupCommand(_trustReferenceNumber, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new CommandSuccessResult()); @@ -96,7 +97,7 @@ public async Task SetProjectGroup_ReturnsNotFound() { // Arrange var urn = "34234233"; - var command = new SetProjectGroupCommand(_trustUrn, []); + var command = new SetProjectGroupCommand(_trustReferenceNumber, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new NotFoundCommandResult()); @@ -114,7 +115,7 @@ public async Task SetProjectGroup_ReturnsBadRequest() // Arrange var urn = "34234233"; var expectedValidationErrors = _fixture.CreateMany().ToList(); - var command = new SetProjectGroupCommand(_trustUrn, []); + var command = new SetProjectGroupCommand(_trustReferenceNumber, []); _mediatrMock.Setup(x => x.Send(command, _cancellationToken)) .ReturnsAsync(new CommandValidationErrorResult(expectedValidationErrors)); @@ -133,7 +134,7 @@ public async Task GetProjectGroups_ReturnsOk() { // Arrange var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null, null, null); - var projectGroupResponse = new List { new(searchModel.Urn!, _trustUrn, []) }; + var projectGroupResponse = new List { new(searchModel.Urn!, _trustReferenceNumber, []) }; var pagingResponse = new PagedDataResponse(projectGroupResponse, new PagingResponse() { Page = 1, diff --git a/Dfe.Academies.Academisation.WebApi/Program.cs b/Dfe.Academies.Academisation.WebApi/Program.cs index 554cd40fe..dd8ea273e 100644 --- a/Dfe.Academies.Academisation.WebApi/Program.cs +++ b/Dfe.Academies.Academisation.WebApi/Program.cs @@ -26,6 +26,7 @@ using Dfe.Academies.Academisation.Service.Commands.Application.School; using Dfe.Academies.Academisation.Service.Commands.Legacy.Project; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService; using Dfe.Academies.Academisation.Service.Commands.TransferProject; using Dfe.Academies.Academisation.Service.CommandValidations; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; From 874d041742b8386be153a07027c687e42a854a3e Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Thu, 25 Jul 2024 15:33:08 +0100 Subject: [PATCH 10/15] added tests --- .../ProjectGroup/ProjectGroupResponseModel.cs | 4 +- .../CreateProjectGroupCommandHandlerTests.cs | 57 +++--- .../SetProjectGroupCommandHandlerTests.cs | 175 ++++++++++++++++++ .../CreateProjectGroupCommandHandler.cs | 13 +- .../SetProjectGroupCommandHandler.cs | 41 ++-- .../Controller/ProjectGroupControllerTests.cs | 6 +- 6 files changed, 240 insertions(+), 56 deletions(-) create mode 100644 Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs index ffef10a40..2cd323076 100644 --- a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupResponseModel.cs @@ -11,7 +11,7 @@ public class ProjectGroupResponseModel(string urn, string trustUrn, IEnumerable< public class ConversionsResponseModel(int urn, string? schoolName) { - public int urn { get; private set; } = urn; - public string? SoolName { get; private set; } = schoolName; + public int Urn { get; private set; } = urn; + public string? SchoolName { get; private set; } = schoolName; } } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index ecb05031c..1205b75ce 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -5,6 +5,7 @@ using Dfe.Academies.Academisation.Domain.ProjectAggregate; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; +using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using FluentAssertions; @@ -23,9 +24,10 @@ public class CreateProjectGroupCommandHandlerTests private Mock _mockDateTimeProvider; private CreateProjectGroupCommandValidator _validator; - private Mock mockCnversionProjectRepository; + private Mock _mockConversionProjectRepository; private Mock> _mocklogger; private readonly Fixture _fixture = new(); + private CancellationToken _cancellationToken; public CreateProjectGroupCommandHandlerTests() { @@ -34,7 +36,7 @@ public CreateProjectGroupCommandHandlerTests() _mockProjectGroupRepository = _mockRepository.Create(); _mockDateTimeProvider = _mockRepository.Create(); _validator = new CreateProjectGroupCommandValidator(); - mockCnversionProjectRepository = _mockRepository.Create(); + _mockConversionProjectRepository = _mockRepository.Create(); _mocklogger = new Mock>(); var mockContext = new Mock(); @@ -42,7 +44,8 @@ public CreateProjectGroupCommandHandlerTests() mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); var mockExecuteStrategy = new Mock(); - mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); + mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); + _cancellationToken = CancellationToken.None; } private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() @@ -51,35 +54,46 @@ private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() _mockProjectGroupRepository.Object, _mockDateTimeProvider.Object, _validator, - mockCnversionProjectRepository.Object, + _mockConversionProjectRepository.Object, _mocklogger.Object); } [Fact] public async Task Handle_ValidCommandWithoutConversions_PersistsExpectedProjectGroup() { + // Arrange var now = DateTime.Now; + var expectedProjects = _fixture.Create>(); _mockDateTimeProvider.Setup(x => x.Now).Returns(now); _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); - - // Arrange var createTransferProjectCommandHandler = CreateProjectGroupCommandHandler(); var request = CreateValidCreateTProjectProjectCommand(false); - var cancellationToken = CancellationToken.None; + _mockConversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupAsync(It.IsAny>(), It.Is(x => x == _cancellationToken))).ReturnsAsync(expectedProjects); + var expectedProjectGroupReference = "GRP_00000000"; // Act var result = await createTransferProjectCommandHandler.Handle( request, - cancellationToken); + _cancellationToken); // Assert + var responseModel = Assert.IsType>(result).Payload; + Assert.Equal(responseModel.TrustUrn, request.TrustUrn); + Assert.Equal(responseModel.Urn, expectedProjectGroupReference); + Assert.Equal(responseModel.Conversions.Count(), expectedProjects.Count); + foreach (var conversion in responseModel.Conversions.Select((Value, Index) => (Value, Index))) + { + Assert.Equal(conversion.Value.Urn, expectedProjects[conversion.Index].Details.Urn); + Assert.Equal(conversion.Value.SchoolName, expectedProjects[conversion.Index].Details.SchoolName); + }; _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); - mockCnversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Never()); - mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Never()); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Exactly(2)); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == _cancellationToken)), Times.Never()); + _mockConversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.Is(x => x == _cancellationToken)), Times.Never()); } [Fact] @@ -93,8 +107,9 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou var cancellationToken = CancellationToken.None; _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); var expectedProjects = _fixture.Create>(); - mockCnversionProjectRepository.Setup(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken))).ReturnsAsync(expectedProjects); - mockCnversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + _mockConversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupAsync(It.IsAny>(), It.Is(x => x == cancellationToken))).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken))).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); // Act var result = await createTransferProjectCommandHandler.Handle( @@ -106,30 +121,30 @@ public async Task Handle_ValidCommandWithConversions_PersistsExpectedProjectGrou && x.ReferenceNumber != null && x.CreatedOn == now)), Times.Once()); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Once); - mockCnversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Once()); - mockCnversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == cancellationToken)), Times.Exactly(2)); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsForGroup(request.TrustUrn, It.Is(x => x == cancellationToken)), Times.Exactly(2)); + _mockConversionProjectRepository.Verify(x => x.UpdateProjectsWithProjectGroupIdAsync(It.Is>(x => x == request.ConversionsUrns), It.IsAny(), It.IsAny(), It.Is(x => x == cancellationToken)), Times.Once()); } [Fact] public async Task Handle_InValidCommand_ReturnsBadRequest() { + // Arrange var now = DateTime.Now; _mockDateTimeProvider.Setup(x => x.Now).Returns(now); _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); - - // Arrange - var createTransferProjectCommandHandler = CreateProjectGroupCommandHandler(); + var createProjectGroupCommandHandler = CreateProjectGroupCommandHandler(); var request = new CreateProjectGroupCommand(string.Empty, [3424]); var cancellationToken = CancellationToken.None; // Act - var result = await createTransferProjectCommandHandler.Handle( + var result = await createProjectGroupCommandHandler.Handle( request, cancellationToken); // Assert - var validationError = Assert.IsType(result); + var validationError = Assert.IsType(result); validationError.ValidationErrors.Should().HaveCount(2); _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber != null diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs new file mode 100644 index 000000000..63410064a --- /dev/null +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs @@ -0,0 +1,175 @@ +using System.Threading; +using AutoFixture; +using Dfe.Academies.Academisation.Core; +using Dfe.Academies.Academisation.Core.Utils; +using Dfe.Academies.Academisation.Domain.ApplicationAggregate; +using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; +using Dfe.Academies.Academisation.Domain.SeedWork; +using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; +using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; +using FluentAssertions; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Dfe.Academies.Academisation.Service.UnitTest.Commands.ProjectGroup +{ + public class SetProjectGroupCommandHandlerTests + { + private MockRepository _mockRepository; + private Mock _mockProjectGroupRepository; + private Mock _mockDateTimeProvider; + private SetProjectGroupCommandValidator _validator; + private Mock _mockCnversionProjectRepository; + private Mock> _mocklogger; + private readonly Fixture _fixture = new(); + private CancellationToken _cancellationToken; + + public SetProjectGroupCommandHandlerTests() + { + _mockRepository = new MockRepository(MockBehavior.Strict); + _cancellationToken = CancellationToken.None; + _mockProjectGroupRepository = _mockRepository.Create(); + _mockDateTimeProvider = _mockRepository.Create(); + _validator = new SetProjectGroupCommandValidator(); + _mockCnversionProjectRepository = _mockRepository.Create(); + _mocklogger = new Mock>(); + + var mockContext = new Mock(); + _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); + mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); + mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); + var mockExecuteStrategy = new Mock(); + mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); + } + private SetProjectGroupCommandHandler SetProjectGroupCommandHandler() + { + return new SetProjectGroupCommandHandler( + _mockProjectGroupRepository.Object, + _mockDateTimeProvider.Object, + _validator, + _mocklogger.Object, + _mockCnversionProjectRepository.Object); + } + + [Fact] + public async Task Handle_InValidCommand_ReturnsCommandValidationErrorResult() + { + var now = DateTime.Now; + _mockDateTimeProvider.Setup(x => x.Now).Returns(now); + _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); + + // Arrange + var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); + var request = new SetProjectGroupCommand(string.Empty, [3424]); + + // Act + var result = await setProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var validationError = Assert.IsType(result); + validationError.ValidationErrors.Should().HaveCount(2); + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn + && x.ReferenceNumber != null + && x.CreatedOn == now)), Times.Never()); + + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Never()); + } + + [Fact] + public async Task Handle_ProjectGroupDoesNotExists_ReturnsNotFoundCommandResult() + { + // Arrange + var now = DateTime.Now; + _mockDateTimeProvider.Setup(x => x.Now).Returns(now); + var request = new SetProjectGroupCommand(_fixture.Create()[..8], [3424]); + _mockProjectGroupRepository.Setup(x => x.Insert(It.IsAny())); + _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.Urn, _cancellationToken)).ReturnsAsync((Domain.ProjectGroupsAggregate.ProjectGroup?)null); + var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); + + // Act + var result = await setProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var notFoundCommandResult = Assert.IsType(result); + _mockProjectGroupRepository.Verify(x => x.Insert(It.Is(x => x.TrustReference == request.TrustUrn + && x.ReferenceNumber != null + && x.CreatedOn == now)), Times.Never()); + + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Never()); + } + + [Fact] + public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() + { + // Arrange + var now = DateTime.Now; + _mockDateTimeProvider.Setup(x => x.Now).Returns(now); + var expectedProjectGroup = _fixture.Create(); + expectedProjectGroup.SetProjectGroup(_fixture.Create()[..8], now); + expectedProjectGroup.SetProjectReference(1); + var request = new SetProjectGroupCommand(expectedProjectGroup.TrustReference, _fixture.Create>()) + { + Urn = expectedProjectGroup.ReferenceNumber! + }; + _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); + _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.Urn, _cancellationToken)).ReturnsAsync(expectedProjectGroup); + _mockCnversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([]); + var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); + + // Act + var result = await setProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var commandSuccessResult = Assert.IsType(result); + _mockCnversionProjectRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Never); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Once); + _mockProjectGroupRepository.Verify(x => x.Update(It.Is(x => x.TrustReference == request.TrustUrn + && x.ReferenceNumber == request.Urn + && x.LastModifiedOn == now)), Times.Once); + + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Once); + } + + [Fact] + public async Task Handle_ValidRequestConversions_ReturnsSuccess() + { + // Arrange + var now = DateTime.Now; + _mockDateTimeProvider.Setup(x => x.Now).Returns(now); + var expectedProjectGroup = _fixture.Create(); + expectedProjectGroup.SetProjectGroup(_fixture.Create()[..8], now); + expectedProjectGroup.SetProjectReference(1); + var request = new SetProjectGroupCommand(expectedProjectGroup.TrustReference, _fixture.Create>()) + { + Urn = expectedProjectGroup.ReferenceNumber! + }; + _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); + _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.Urn, _cancellationToken)).ReturnsAsync(expectedProjectGroup); + _mockCnversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([]); + var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); + + // Act + var result = await setProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var commandSuccessResult = Assert.IsType(result); + _mockCnversionProjectRepository.Verify(x => x.Update(It.IsAny()), Times.Never); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Once); + _mockProjectGroupRepository.Verify(x => x.Update(It.Is(x => x.TrustReference == request.TrustUrn + && x.ReferenceNumber == request.Urn + && x.LastModifiedOn == now)), Times.Once); + + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Once); + } + } +} diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index 7dab3d336..5010eb77e 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -5,11 +5,8 @@ using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Microsoft.Extensions.Logging; using Dfe.Academies.Academisation.Service.Extensions; -using Microsoft.EntityFrameworkCore; using FluentValidation; using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; -using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; -using Azure; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { @@ -38,8 +35,8 @@ public async Task Handle(CreateProjectGroupCommand message, Cancel { var responseModel = new ProjectGroupResponseModel("", message.TrustUrn, []); var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); - await strategy.ExecuteAsync(async () => - { + //await strategy.ExecuteAsync(async () => + //{ await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) { @@ -58,11 +55,11 @@ await strategy.ExecuteAsync(async () => } await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); + var conversionsProjects = await conversionProjectRepository.GetConversionProjectsForGroup(message.TrustUrn, cancellationToken); - responseModel = new ProjectGroupResponseModel(projectGroup.ReferenceNumber!, projectGroup.TrustReference, conversionsProjects!.Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!))); + responseModel = new ProjectGroupResponseModel(projectGroup.ReferenceNumber!, projectGroup.TrustReference, conversionsProjects!.Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!))); } - }); + //}); return new CreateSuccessResult(responseModel); } catch (Exception ex) { diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs index e7f902a7f..3781c38bd 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; using Dfe.Academies.Academisation.Service.Extensions; -using Microsoft.EntityFrameworkCore; using FluentValidation; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup @@ -32,34 +31,32 @@ public async Task Handle(SetProjectGroupCommand message, Cancella try { var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); - await strategy.ExecuteAsync(async () => + await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); + using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) { - await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); - using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) - { - logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); - projectGroup.SetProjectGroup(message.TrustUrn, dateTimeProvider.Now); + logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); + projectGroup.SetProjectGroup(message.TrustUrn, dateTimeProvider.Now); - projectGroupRepository.Update(projectGroup); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + projectGroupRepository.Update(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); - if (conversionsProjects != null && conversionsProjects.Any()) - { - logger.LogInformation($"Setting conversions with project group id:{projectGroup.Id}"); + var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); + if (conversionsProjects != null && conversionsProjects.Any()) + { + logger.LogInformation($"Setting conversions with project group id:{projectGroup.Id}"); - var removedConversionProjectsUrns = conversionsProjects.Where(x - => !message.ConversionsUrns.Contains(x.ProjectGroupId.GetValueOrDefault())).Select(x => x.Details.Urn).ToList(); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(removedConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); + var removedConversionProjectsUrns = conversionsProjects.Where(x + => !message.ConversionsUrns.Contains(x.ProjectGroupId.GetValueOrDefault())).Select(x => x.Details.Urn).ToList(); + await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(removedConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); - var addConversionProjectsUrns = message.ConversionsUrns.Except(removedConversionProjectsUrns).ToList(); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(addConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); + var addConversionProjectsUrns = message.ConversionsUrns.Except(removedConversionProjectsUrns).ToList(); + await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(addConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); - await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); - } - await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); } - }); + await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + } + return new CommandSuccessResult(); } catch (Exception ex) diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index 7986f2405..95545da3d 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -50,8 +50,8 @@ public async Task CreateProjectGroup_ReturnsOk() var result = await _controller.CreateProjectGroup(command, _cancellationToken); // Assert - var okObjectResult = Assert.IsType(result); - var responseModel = Assert.IsType(result.Value); + var okObjectResult = Assert.IsType(result.Result); + var responseModel = Assert.IsType(okObjectResult.Value); Assert.Equal(responseModel.Urn, response.Urn); Assert.Equal(responseModel.TrustUrn, response.TrustUrn); _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); @@ -69,7 +69,7 @@ public async Task CreateProjectGroup_ReturnsBadRequest() var result = await _controller.CreateProjectGroup(command, _cancellationToken); // Assert - var badRequestObjectResult = Assert.IsType(result); + var badRequestObjectResult = Assert.IsType(result.Result); _mediatrMock.Verify(x => x.Send(command, _cancellationToken), Times.Once()); } From 48e4d6b53ce458efdd5b59e36ed9523cf192c14c Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 26 Jul 2024 11:25:44 +0100 Subject: [PATCH 11/15] Added more tests in set project group --- .../ConversionProjectRepository.cs | 8 -- .../Repositories/ProjectGroupRepository.cs | 37 +++------ .../IConversionProjectRepository.cs | 2 - .../ProjectAggregate/Project.cs | 6 -- .../IProjectGroupRepository.cs | 2 +- .../ProjectAggregate/IProject.cs | 2 +- .../ProjectGroup/ProjectGroupSearchModel.cs | 4 +- .../CreateProjectGroupCommandHandlerTests.cs | 5 -- .../SetProjectGroupCommandHandlerTests.cs | 78 +++++++++++++------ .../SetProjectGroupCommandHandler.cs | 50 +++++------- .../Queries/ProjectGroupQueryService.cs | 3 +- .../Controller/ProjectGroupControllerTests.cs | 4 +- 12 files changed, 89 insertions(+), 112 deletions(-) diff --git a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs index 660b49095..b681a345b 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ConversionProjectRepository.cs @@ -48,14 +48,6 @@ public ConversionProjectRepository(AcademisationContext context, IMapper mapper) .ToListAsync(cancellationToken); } - public async Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, DateTime lastModifiedOn, CancellationToken cancellationToken) - { - await dbSet.Where(x => projectsUrns.Contains(x.Details.Urn)) - .ExecuteUpdateAsync(u => - u.SetProperty(p => p.ProjectGroupId, projectGroupId) - .SetProperty(p => p.LastModifiedOn, lastModifiedOn), cancellationToken); - } - public async Task GetFilterParameters() { var advisoryBoardDates = await dbSet diff --git a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs index 89085a4fb..b6305b9f0 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs @@ -28,13 +28,12 @@ private IQueryable DefaultIncludes() public async Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken) => await DefaultIncludes().SingleOrDefaultAsync(x => x.ReferenceNumber == referenceNumber, cancellationToken); - public async Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName, string? academyUkprn, string? companiesHouseNo, CancellationToken cancellationToken) + public async Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? academyName, List trustUrns, CancellationToken cancellationToken) { IQueryable queryable = DefaultIncludes(); queryable = FilterByUrn(urn, queryable); - queryable = FilterByTrust(trustUrn, trustName, queryable); - queryable = FilterByAcademy(academyUkprn, academyName, queryable); - queryable = FilterByCompaniesHouseNo(companiesHouseNo, queryable); + queryable = FilterByTrust(trustUrns, queryable); + queryable = FilterByAcademy(academyName, queryable); var totalProjectGroups = queryable.Count(); var projects = await queryable @@ -45,26 +44,12 @@ private IQueryable DefaultIncludes() return (projects, totalProjectGroups); } - private IQueryable FilterByCompaniesHouseNo(string? companiesHouseNo, IQueryable queryable) + private IQueryable FilterByAcademy(string? academyName, IQueryable queryable) { - if (!companiesHouseNo.IsNullOrEmpty()) - { - queryable = queryable.Where(p => p.ReferenceNumber == companiesHouseNo); - } - - return queryable; - } - - private IQueryable FilterByAcademy(string? academyUkprn, string? academyName, IQueryable queryable) - { - if (!academyUkprn.IsNullOrEmpty()) - { - queryable = queryable.Where(p => p.ReferenceNumber == academyUkprn); - } if (!academyName.IsNullOrEmpty()) { - queryable = queryable.Where(p => p.ReferenceNumber == academyName); + queryable = queryable.Where(p => EF.Functions.Like(p.ReferenceNumber, $"%{academyName}%")); } return queryable; @@ -79,16 +64,12 @@ private static IQueryable FilterByUrn(string? urn, IQueryable FilterByTrust(string? trustUrn, string? trustName, IQueryable queryable) + + private IQueryable FilterByTrust(List trustUrns, IQueryable queryable) { - if (!trustUrn.IsNullOrEmpty()) - { - queryable = queryable.Where(p => p.TrustReference == trustUrn); - } - if (!trustName.IsNullOrEmpty()) + if (trustUrns.IsNullOrEmpty()) { - trustUrn = "todo"; - queryable = queryable.Where(p => p.TrustReference == trustUrn); + queryable = queryable.Where(p => trustUrns.Contains(p.TrustReference)); } return queryable; diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs index d7143addc..33043f7e3 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/IConversionProjectRepository.cs @@ -23,7 +23,5 @@ public interface IConversionProjectRepository : IRepository, IGenericRe Task?> GetIncompleteProjects(); Task> GetConversionProjectsForGroup(string trustReferenceNumber, CancellationToken cancellationToken); - - Task UpdateProjectsWithProjectGroupIdAsync(List projectsUrns, int? projectGroupId, DateTime lastModifiedOn, CancellationToken cancellationToken); Task?> GetProjectsByProjectGroupAsync(List projectGroupIds, CancellationToken cancellationToken); } diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs index fa974f591..81d0c7115 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs @@ -5,7 +5,6 @@ using Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.SchoolImprovemenPlans; using Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; -using Dfe.Academies.Academisation.Domain.TransferProjectAggregate; using Dfe.Academies.Academisation.IDomain.ApplicationAggregate; using Dfe.Academies.Academisation.IDomain.ProjectAggregate; @@ -550,11 +549,6 @@ public void SetFormAMatProjectId(int id) } } - public void SetProjectGroupId(int id) - { - ProjectGroupId = id; - } - public void SetIncomingTrust(string trustReferrenceNumber, string trustName) { Details.SetIncomingTrust(trustReferrenceNumber, trustName); diff --git a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs index 09b794a7a..37e8b4100 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs @@ -6,7 +6,7 @@ namespace Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate public interface IProjectGroupRepository : IRepository, IGenericRepository { Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken); - Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName, string? academyUkprn, string? companiesHouseNo, CancellationToken cancellationToken); + Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? academyName, List trustUrns, CancellationToken cancellationToken); } } diff --git a/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs b/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs index 20522b233..c7740c6d4 100644 --- a/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs +++ b/Dfe.Academies.Academisation.IDomain/ProjectAggregate/IProject.cs @@ -47,7 +47,7 @@ public void SetSchoolOverview( public void SetPerformanceData(string? keyStage2PerformanceAdditionalInformation, string? keyStage4PerformanceAdditionalInformation, string? keyStage5PerformanceAdditionalInformation, string? educationalAttendanceAdditionalInformation); void SetFormAMatProjectId(int id); - void SetProjectGroupId(int id); + void SetProjectGroupId(int? id); void SetRoute(string route); void AddNote(string subject, string note, string author, DateTime date); void RemoveNote(int id); diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs index 7b981827a..47fcafffe 100644 --- a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs @@ -1,7 +1,7 @@  namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup { - public class ProjectGroupSearchModel(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName, string? academyUkprn, string? companiesHouseNo) + public class ProjectGroupSearchModel(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName) { public int Page { get; set; } = page; public int Count { get; set; } = count; @@ -9,8 +9,6 @@ public class ProjectGroupSearchModel(int page, int count, string? urn, string? t public string? TrustUrn { get; set; } = trustUrn; public string? TrustName { get; set; } = trustName; public string? AcademyName { get; set; } = academyName; - public string? AcademyUkprn { get; set; } = academyUkprn; - public string? CompaniesHouseNo { get; set; } = companiesHouseNo; } } diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index f5ac4e7bd..4dc0b84a4 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -42,11 +42,6 @@ public CreateProjectGroupCommandHandlerTests() var mockContext = new Mock(); _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); _mockConversionProjectRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); - - //mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); - //mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); - //var mockExecuteStrategy = new Mock(); - //mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); } private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs index e3fad2092..52ce24fef 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/SetProjectGroupCommandHandlerTests.cs @@ -1,6 +1,4 @@ -using System.Runtime.CompilerServices; -using System.Threading; -using AutoFixture; +using AutoFixture; using Dfe.Academies.Academisation.Core; using Dfe.Academies.Academisation.Core.Utils; using Dfe.Academies.Academisation.Domain.ApplicationAggregate; @@ -9,7 +7,6 @@ using Dfe.Academies.Academisation.Service.Commands.ProjectGroup; using Dfe.Academies.Academisation.Service.CommandValidations.ProjectGroup; using FluentAssertions; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -22,7 +19,7 @@ public class SetProjectGroupCommandHandlerTests private Mock _mockProjectGroupRepository; private Mock _mockDateTimeProvider; private SetProjectGroupCommandValidator _validator; - private Mock _mockCnversionProjectRepository; + private Mock _mockConversionProjectRepository; private Mock> _mocklogger; private readonly Fixture _fixture = new(); private CancellationToken _cancellationToken; @@ -34,15 +31,12 @@ public SetProjectGroupCommandHandlerTests() _mockProjectGroupRepository = _mockRepository.Create(); _mockDateTimeProvider = _mockRepository.Create(); _validator = new SetProjectGroupCommandValidator(); - _mockCnversionProjectRepository = _mockRepository.Create(); + _mockConversionProjectRepository = _mockRepository.Create(); _mocklogger = new Mock>(); var mockContext = new Mock(); _mockProjectGroupRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); - mockContext.Setup(x => x.BeginTransactionAsync()).ReturnsAsync((new Mock()).Object); - mockContext.Setup(x => x.CommitTransactionAsync()).Returns(Task.CompletedTask); - var mockExecuteStrategy = new Mock(); - mockContext.Setup(x => x.CreateExecutionStrategy()).Returns(mockExecuteStrategy.Object); + _mockConversionProjectRepository.Setup(x => x.UnitOfWork).Returns(mockContext.Object); } private SetProjectGroupCommandHandler SetProjectGroupCommandHandler() { @@ -51,7 +45,7 @@ private SetProjectGroupCommandHandler SetProjectGroupCommandHandler() _mockDateTimeProvider.Object, _validator, _mocklogger.Object, - _mockCnversionProjectRepository.Object); + _mockConversionProjectRepository.Object); } [Fact] @@ -120,7 +114,7 @@ public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.Urn, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockCnversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([]); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByUrns(request.ConversionsUrns, _cancellationToken)).ReturnsAsync([]); var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); // Act @@ -129,33 +123,32 @@ public async Task Handle_ValidRequestWithoutConversions_ReturnsSuccess() _cancellationToken); // Assert - var commandSuccessResult = Assert.IsType(result); - _mockCnversionProjectRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Never); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Once); + var commandSuccessResult = Assert.IsType(result); _mockProjectGroupRepository.Verify(x => x.Update(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber == request.Urn && x.LastModifiedOn == now)), Times.Once); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Once); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Once); } - [Fact(Skip = "specific reason")] - public async Task Handle_ValidRequestConversions_ReturnsSuccess() + [Fact] + public async Task Handle_ValidRequestWithNoRemovedConversions_ReturnsSuccess() { // Arrange var now = DateTime.Now; _mockDateTimeProvider.Setup(x => x.Now).Returns(now); - var expectedProject = _fixture.Create(); + var expectedProjects = _fixture.Create>(); var expectedProjectGroup = _fixture.Create(); expectedProjectGroup.SetProjectGroup(_fixture.Create()[..8], now); expectedProjectGroup.SetProjectReference(1); - var request = new SetProjectGroupCommand(expectedProjectGroup.TrustReference, _fixture.Create>()) + var request = new SetProjectGroupCommand(expectedProjectGroup.TrustReference, expectedProjects.Select(x => x.Details.Urn).ToList()) { Urn = expectedProjectGroup.ReferenceNumber! }; _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.Urn, _cancellationToken)).ReturnsAsync(expectedProjectGroup); - _mockCnversionProjectRepository.Setup(x => x.GetProjectsByProjectGroupAsync(new List { expectedProjectGroup.Id }, _cancellationToken)).ReturnsAsync([expectedProject]); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByUrns(request.ConversionsUrns, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); // Act @@ -165,13 +158,48 @@ public async Task Handle_ValidRequestConversions_ReturnsSuccess() // Assert var commandSuccessResult = Assert.IsType(result); - _mockCnversionProjectRepository.Verify(x => x.Update(It.IsAny()), Times.Never); - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.CommitTransactionAsync(), Times.Once); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByUrns(request.ConversionsUrns, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.Update(It.IsAny()), Times.Exactly(expectedProjects.Count)); _mockProjectGroupRepository.Verify(x => x.Update(It.Is(x => x.TrustReference == request.TrustUrn && x.ReferenceNumber == request.Urn - && x.LastModifiedOn == now)), Times.Once); + && x.LastModifiedOn == now)), Times.Once); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Exactly(2)); + } - _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(It.Is(x => x == _cancellationToken)), Times.Once); + [Fact] + public async Task Handle_ValidRequestWithOneRemovedConversions_ReturnsSuccess() + { + // Arrange + var now = DateTime.Now; + _mockDateTimeProvider.Setup(x => x.Now).Returns(now); + var expectedProjects = _fixture.Create>(); + var expectedProjectGroup = _fixture.Create(); + expectedProjectGroup.SetProjectGroup(_fixture.Create()[..8], now); + expectedProjectGroup.SetProjectReference(1); + _mockConversionProjectRepository.Setup(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken)).ReturnsAsync(1); + var request = new SetProjectGroupCommand(expectedProjectGroup.TrustReference, expectedProjects.Take(2).Select(x => x.Details.Urn).ToList()) + { + Urn = expectedProjectGroup.ReferenceNumber! + }; + _mockProjectGroupRepository.Setup(x => x.Update(It.IsAny())); + _mockProjectGroupRepository.Setup(x => x.GetByReferenceNumberAsync(request.Urn, _cancellationToken)).ReturnsAsync(expectedProjectGroup); + _mockConversionProjectRepository.Setup(x => x.GetConversionProjectsByUrns(request.ConversionsUrns, _cancellationToken)).ReturnsAsync(expectedProjects); + _mockConversionProjectRepository.Setup(x => x.Update(It.IsAny())); + var setProjectGroupCommandHandler = SetProjectGroupCommandHandler(); + + // Act + var result = await setProjectGroupCommandHandler.Handle( + request, + _cancellationToken); + + // Assert + var commandSuccessResult = Assert.IsType(result); + _mockConversionProjectRepository.Verify(x => x.GetConversionProjectsByUrns(request.ConversionsUrns, _cancellationToken), Times.Once); + _mockConversionProjectRepository.Verify(x => x.Update(It.IsAny()), Times.Exactly(expectedProjects.Count)); + _mockProjectGroupRepository.Verify(x => x.Update(It.Is(x => x.TrustReference == request.TrustUrn + && x.ReferenceNumber == request.Urn + && x.LastModifiedOn == now)), Times.Once); + _mockProjectGroupRepository.Verify(x => x.UnitOfWork.SaveChangesAsync(_cancellationToken), Times.Exactly(2)); } } } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs index 3781c38bd..a9656236d 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/SetProjectGroupCommandHandler.cs @@ -27,43 +27,35 @@ public async Task Handle(SetProjectGroupCommand message, Cancella return new NotFoundCommandResult(); } - var typeName = message.GetGenericTypeName(); - try - { - var strategy = projectGroupRepository.UnitOfWork.CreateExecutionStrategy(); - await using var transaction = await projectGroupRepository.UnitOfWork.BeginTransactionAsync(); - using (logger.BeginScope(new List> { new("TransactionContext", transaction.TransactionId) })) - { - logger.LogInformation("Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, message); - projectGroup.SetProjectGroup(message.TrustUrn, dateTimeProvider.Now); + projectGroup.SetProjectGroup(message.TrustUrn, dateTimeProvider.Now); - projectGroupRepository.Update(projectGroup); - await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); + projectGroupRepository.Update(projectGroup); + await projectGroupRepository.UnitOfWork.SaveChangesAsync(cancellationToken); - var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync([projectGroup.Id], cancellationToken); - if (conversionsProjects != null && conversionsProjects.Any()) - { - logger.LogInformation($"Setting conversions with project group id:{projectGroup.Id}"); + var conversionProjects = await conversionProjectRepository.GetConversionProjectsByUrns(message.ConversionsUrns, cancellationToken).ConfigureAwait(false); - var removedConversionProjectsUrns = conversionsProjects.Where(x - => !message.ConversionsUrns.Contains(x.ProjectGroupId.GetValueOrDefault())).Select(x => x.Details.Urn).ToList(); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(removedConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); + if (conversionProjects != null && conversionProjects.Any()) + { + logger.LogInformation($"Setting conversions with project group id:{projectGroup.Id}"); - var addConversionProjectsUrns = message.ConversionsUrns.Except(removedConversionProjectsUrns).ToList(); - await conversionProjectRepository.UpdateProjectsWithProjectGroupIdAsync(addConversionProjectsUrns, null, dateTimeProvider.Now, cancellationToken); + var removedConversionProjects = conversionProjects.Where(x + => !message.ConversionsUrns.Contains(x.Details.Urn)).ToList(); + foreach (var removedConversionProject in removedConversionProjects) + { + removedConversionProject.SetProjectGroupId(null); + conversionProjectRepository.Update(removedConversionProject as Domain.ProjectAggregate.Project); + } - await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); - } - await projectGroupRepository.UnitOfWork.CommitTransactionAsync(); + var addConversionProjects = conversionProjects.Except(removedConversionProjects).ToList(); + foreach (var addConversionProject in addConversionProjects) + { + addConversionProject.SetProjectGroupId(projectGroup.Id); + conversionProjectRepository.Update(addConversionProject as Domain.ProjectAggregate.Project); } - return new CommandSuccessResult(); - } - catch (Exception ex) - { - logger.LogError(ex, "Error Handling transaction for {CommandName} ({@Command} - {Error})", typeName, message, ex.Message); - throw; + await conversionProjectRepository.UnitOfWork.SaveChangesAsync(); } + return new CommandSuccessResult(); } } } diff --git a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs index 9ea3bd6af..e4245f4ff 100644 --- a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs +++ b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs @@ -17,8 +17,7 @@ public class ProjectGroupQueryService(IProjectGroupRepository projectGroupReposi logger.LogError($"Searching project group with :{searchModel}"); var(projectGroups, totalCount) = await projectGroupRepository.SearchProjectGroups(searchModel.Page, searchModel.Count, - searchModel.Urn, searchModel.TrustUrn, searchModel.TrustName, searchModel.AcademyName, searchModel.AcademyUkprn, - searchModel.CompaniesHouseNo, cancellationToken); + searchModel.Urn, searchModel.AcademyName, [searchModel.TrustUrn], cancellationToken); var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroups.Select(x => x.Id).ToList(), cancellationToken); var response = MapToResponse(projectGroups, conversionsProjects); diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index a4cfa149e..d095f833d 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -133,7 +133,7 @@ public async Task SetProjectGroup_ReturnsBadRequest() public async Task GetProjectGroups_ReturnsOk() { // Arrange - var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null, null, null); + var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null); var projectGroupResponse = new List { new(searchModel.Urn!, _trustReferenceNumber, []) }; var pagingResponse = new PagedDataResponse(projectGroupResponse, new PagingResponse() { @@ -159,7 +159,7 @@ public async Task GetProjectGroups_ReturnsOk() public async Task GetProjectGroups_ReturnsNotFound() { // Arrange - var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null, null, null); + var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null); var pagingResponse = new PagedDataResponse([], new PagingResponse() { Page = 1, From d7f81797ca14f02873121942ae0ca9131aa065ee Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 26 Jul 2024 12:34:38 +0100 Subject: [PATCH 12/15] Updated search model --- .../Repositories/ProjectGroupRepository.cs | 30 ++++++------------- .../IProjectGroupRepository.cs | 2 +- .../ProjectGroupsAggregate/ProjectGroup.cs | 7 +++++ .../ProjectGroup/ProjectGroupSearchModel.cs | 9 +++--- .../Queries/ProjectGroupQueryService.cs | 12 ++++++-- .../Controller/ProjectGroupControllerTests.cs | 15 +++++----- 6 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs index b6305b9f0..6c350243a 100644 --- a/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Data/Repositories/ProjectGroupRepository.cs @@ -28,12 +28,11 @@ private IQueryable DefaultIncludes() public async Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken) => await DefaultIncludes().SingleOrDefaultAsync(x => x.ReferenceNumber == referenceNumber, cancellationToken); - public async Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? academyName, List trustUrns, CancellationToken cancellationToken) + public async Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? referenceNumber, IEnumerable? trustReferences, CancellationToken cancellationToken) { IQueryable queryable = DefaultIncludes(); - queryable = FilterByUrn(urn, queryable); - queryable = FilterByTrust(trustUrns, queryable); - queryable = FilterByAcademy(academyName, queryable); + queryable = FilterByReferenceNumber(referenceNumber, queryable); + queryable = FilterByTrust(trustReferences, queryable); var totalProjectGroups = queryable.Count(); var projects = await queryable @@ -44,32 +43,21 @@ private IQueryable DefaultIncludes() return (projects, totalProjectGroups); } - private IQueryable FilterByAcademy(string? academyName, IQueryable queryable) + private static IQueryable FilterByReferenceNumber(string? referenceNumber, IQueryable queryable) { - - if (!academyName.IsNullOrEmpty()) - { - queryable = queryable.Where(p => EF.Functions.Like(p.ReferenceNumber, $"%{academyName}%")); - } - - return queryable; - } - - private static IQueryable FilterByUrn(string? urn, IQueryable queryable) - { - if (!urn.IsNullOrEmpty()) + if (!referenceNumber.IsNullOrEmpty()) { - queryable = queryable.Where(p => p.ReferenceNumber == urn); + queryable = queryable.Where(p => p.ReferenceNumber == referenceNumber); } return queryable; } - private IQueryable FilterByTrust(List trustUrns, IQueryable queryable) + private IQueryable FilterByTrust(IEnumerable trustReferences, IQueryable queryable) { - if (trustUrns.IsNullOrEmpty()) + if (trustReferences.IsNullOrEmpty()) { - queryable = queryable.Where(p => trustUrns.Contains(p.TrustReference)); + queryable = queryable.Where(p => trustReferences.Contains(p.TrustReference)); } return queryable; diff --git a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs index 37e8b4100..c7a3c1416 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/IProjectGroupRepository.cs @@ -6,7 +6,7 @@ namespace Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate public interface IProjectGroupRepository : IRepository, IGenericRepository { Task GetByReferenceNumberAsync(string referenceNumber, CancellationToken cancellationToken); - Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? urn, string? academyName, List trustUrns, CancellationToken cancellationToken); + Task<(IEnumerable, int totalcount)> SearchProjectGroups(int page, int count, string? referenceNumber, IEnumerable? trustReferences, CancellationToken cancellationToken); } } diff --git a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs index b881db765..9b588fddc 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectGroupsAggregate/ProjectGroup.cs @@ -1,4 +1,5 @@ using Dfe.Academies.Academisation.Domain.Core.ProjectAggregate; +using Dfe.Academies.Academisation.Domain.ProjectAggregate; using Dfe.Academies.Academisation.Domain.SeedWork; using Dfe.Academies.Academisation.IDomain.ProjectGroupAggregate; @@ -18,6 +19,12 @@ public void SetAssignedUser(Guid userId, string fullName, string emailAddress) LastModifiedOn = DateTime.UtcNow; } + public void SetProjects(Guid userId, string fullName, string emailAddress) + { + AssignedUser = new User(userId, fullName, emailAddress); + LastModifiedOn = DateTime.UtcNow; + } + public void SetProjectGroup(string trustReference, DateTime lastModifiedOnUtc) { TrustReference = trustReference; diff --git a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs index 47fcafffe..b47ae14a2 100644 --- a/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs +++ b/Dfe.Academies.Academisation.IService/ServiceModels/ProjectGroup/ProjectGroupSearchModel.cs @@ -1,14 +1,13 @@  namespace Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup { - public class ProjectGroupSearchModel(int page, int count, string? urn, string? trustUrn, string? trustName, string? academyName) + public class ProjectGroupSearchModel(int page, int count, string? referenceNumber, string? trustReference, string? title) { public int Page { get; set; } = page; public int Count { get; set; } = count; - public string? Urn { get; set; } = urn; - public string? TrustUrn { get; set; } = trustUrn; - public string? TrustName { get; set; } = trustName; - public string? AcademyName { get; set; } = academyName; + public string? ReferenceNumber { get; set; } = referenceNumber; + public string? TrustReference { get; set; } = trustReference; + public string? Title { get; set; } = title; } } diff --git a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs index e4245f4ff..1a69870c4 100644 --- a/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs +++ b/Dfe.Academies.Academisation.Service/Queries/ProjectGroupQueryService.cs @@ -7,6 +7,7 @@ using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; using Dfe.Academies.Academisation.Service.Factories; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup.QueryService { @@ -16,8 +17,9 @@ public class ProjectGroupQueryService(IProjectGroupRepository projectGroupReposi { logger.LogError($"Searching project group with :{searchModel}"); - var(projectGroups, totalCount) = await projectGroupRepository.SearchProjectGroups(searchModel.Page, searchModel.Count, - searchModel.Urn, searchModel.AcademyName, [searchModel.TrustUrn], cancellationToken); + var (conversionProjects, _) = await conversionProjectRepository.SearchProjectsV2(null, searchModel.Title, null, null, null, null, searchModel.Page, searchModel.Count); + var (projectGroups, totalCount) = await projectGroupRepository.SearchProjectGroups(searchModel.Page, searchModel.Count, + searchModel.ReferenceNumber, CombineTrustReferences(conversionProjects.Select(x => x.Details.TrustReferenceNumber), searchModel.TrustReference), cancellationToken); var conversionsProjects = await conversionProjectRepository.GetProjectsByProjectGroupAsync(projectGroups.Select(x => x.Id).ToList(), cancellationToken); var response = MapToResponse(projectGroups, conversionsProjects); @@ -32,5 +34,11 @@ private IEnumerable MapToResponse(IEnumerable c.ProjectGroupId.GetValueOrDefault() == x.Id) .Select(p => new ConversionsResponseModel(p.Details.Urn, p.Details.SchoolName!)))).ToList(); } + + private IEnumerable CombineTrustReferences(IEnumerable trustReferences, string? trustReference) + { + var trustReferencesList = trustReferences.IsNullOrEmpty() ? [] : trustReferences; + return trustReference.IsNullOrEmpty() ? trustReferencesList : trustReferencesList.Concat([trustReference]); + } } } diff --git a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs index d095f833d..6f44d941f 100644 --- a/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs +++ b/Dfe.Academies.Academisation.WebApi.UnitTest/Controller/ProjectGroupControllerTests.cs @@ -14,7 +14,6 @@ using Dfe.Academies.Academisation.IService.Query.ProjectGroup; using Dfe.Academies.Academisation.IService.ServiceModels.ProjectGroup; using Dfe.Academies.Academisation.IService.ServiceModels.Legacy.ProjectAggregate; -using Azure; namespace Dfe.Academies.Academisation.WebApi.UnitTest.Controller { @@ -133,14 +132,14 @@ public async Task SetProjectGroup_ReturnsBadRequest() public async Task GetProjectGroups_ReturnsOk() { // Arrange - var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null); - var projectGroupResponse = new List { new(searchModel.Urn!, _trustReferenceNumber, []) }; + var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null); + var projectGroupResponse = new List { new(searchModel.ReferenceNumber!, _trustReferenceNumber, []) }; var pagingResponse = new PagedDataResponse(projectGroupResponse, new PagingResponse() { Page = 1, RecordCount = 1 }); - _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken)) + _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupsAsync(It.Is(x => x.ReferenceNumber == searchModel.ReferenceNumber), _cancellationToken)) .ReturnsAsync(pagingResponse); // Action @@ -152,20 +151,20 @@ public async Task GetProjectGroups_ReturnsOk() Assert.Equal(response.Data.Count(), projectGroupResponse.Count); Assert.Equal(response.Paging.RecordCount, pagingResponse.Paging.RecordCount); Assert.Equal(response.Paging.Page, pagingResponse.Paging.Page); - _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken), Times.Once()); + _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupsAsync(It.Is(x => x.ReferenceNumber == searchModel.ReferenceNumber), _cancellationToken), Times.Once()); } [Fact] public async Task GetProjectGroups_ReturnsNotFound() { // Arrange - var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null, null); + var searchModel = new ProjectGroupSearchModel(1, 10, "34234233", null, null); var pagingResponse = new PagedDataResponse([], new PagingResponse() { Page = 1, RecordCount = 1 }); - _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken)) + _projectGroupQueryServiceMock.Setup(x => x.GetProjectGroupsAsync(It.Is(x => x.ReferenceNumber == searchModel.ReferenceNumber), _cancellationToken)) .ReturnsAsync(pagingResponse); // Action @@ -173,7 +172,7 @@ public async Task GetProjectGroups_ReturnsNotFound() // Assert Assert.IsType(result.Result); - _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupsAsync(It.Is(x => x.Urn == searchModel.Urn), _cancellationToken), Times.Once()); + _projectGroupQueryServiceMock.Verify(x => x.GetProjectGroupsAsync(It.Is(x => x.ReferenceNumber == searchModel.ReferenceNumber), _cancellationToken), Times.Once()); } } } From 53ecc45b83f28b423e031cbfd9b1e2af6eea8d3e Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 26 Jul 2024 14:24:03 +0100 Subject: [PATCH 13/15] Fixed branch issue --- .../ProjectGroup/CreateProjectGroupCommandHandlerTests.cs | 3 --- .../Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs index 4dc0b84a4..adf8875f1 100644 --- a/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs +++ b/Dfe.Academies.Academisation.Service.UnitTest/Commands/ProjectGroup/CreateProjectGroupCommandHandlerTests.cs @@ -24,7 +24,6 @@ public class CreateProjectGroupCommandHandlerTests private Mock _mockConversionProjectRepository; private Mock _mockDateTimeProvider; - private CreateProjectGroupCommandValidator _validator; private Mock> _mocklogger; private readonly Fixture _fixture = new(); private CancellationToken _cancellationToken; @@ -36,7 +35,6 @@ public CreateProjectGroupCommandHandlerTests() _mockProjectGroupRepository = _mockRepository.Create(); _mockDateTimeProvider = _mockRepository.Create(); _mockConversionProjectRepository = _mockRepository.Create(); - _validator = new CreateProjectGroupCommandValidator(); _mocklogger = new Mock>(); var mockContext = new Mock(); @@ -49,7 +47,6 @@ private CreateProjectGroupCommandHandler CreateProjectGroupCommandHandler() return new CreateProjectGroupCommandHandler( _mockProjectGroupRepository.Object, _mockDateTimeProvider.Object, - _validator, _mockConversionProjectRepository.Object, _mocklogger.Object); } diff --git a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs index ecc0c9071..4af3bee70 100644 --- a/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs +++ b/Dfe.Academies.Academisation.Service/Commands/ProjectGroup/CreateProjectGroupCommandHandler.cs @@ -9,8 +9,9 @@ namespace Dfe.Academies.Academisation.Service.Commands.ProjectGroup { - public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, CreateProjectGroupCommandValidator validator, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler + public class CreateProjectGroupCommandHandler(IProjectGroupRepository projectGroupRepository, IDateTimeProvider dateTimeProvider, IConversionProjectRepository conversionProjectRepository, ILogger logger) : IRequestHandler { + private CreateProjectGroupCommandValidator validator = new(); public async Task Handle(CreateProjectGroupCommand message, CancellationToken cancellationToken) { logger.LogError($"Creating project group with urn:{message}"); From aaa08e90da2d62d50f70fe8140c0027ff7f3938f Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 26 Jul 2024 14:31:51 +0100 Subject: [PATCH 14/15] Droped FK index --- ...7_Removed-project-group-FK-Ref.Designer.cs | 2293 +++++++++++++++++ ...0726132737_Removed-project-group-FK-Ref.cs | 43 + .../AcademisationContextModelSnapshot.cs | 8 - 3 files changed, 2336 insertions(+), 8 deletions(-) create mode 100644 Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.Designer.cs create mode 100644 Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.cs diff --git a/Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.Designer.cs b/Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.Designer.cs new file mode 100644 index 000000000..fc191c442 --- /dev/null +++ b/Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.Designer.cs @@ -0,0 +1,2293 @@ +// +using System; +using Dfe.Academies.Academisation.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Dfe.Academies.Academisation.Data.Migrations +{ + [DbContext(typeof(AcademisationContext))] + [Migration("20240726132737_Removed-project-group-FK-Ref")] + partial class RemovedprojectgroupFKRef + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationReference") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("nvarchar(max)") + .HasComputedColumnSql("'A2B_' + CAST([Id] AS NVARCHAR(255))", true); + + b.Property("ApplicationStatus") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ApplicationSubmittedDate") + .HasColumnType("datetime2"); + + b.Property("ApplicationType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DynamicsApplicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("FormTrustId") + .HasColumnType("int"); + + b.Property("JoinTrustId") + .HasColumnType("int"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("FormTrustId") + .IsUnique() + .HasFilter("[FormTrustId] IS NOT NULL"); + + b.HasIndex("JoinTrustId") + .IsUnique() + .HasFilter("[JoinTrustId] IS NOT NULL"); + + b.ToTable("ConversionApplication", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Contributor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ConversionApplicationId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DynamicsApplicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ConversionApplicationId"); + + b.ToTable("ConversionApplicationContributor", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.Lease", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationSchoolId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DynamicsSchoolLeaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("InterestRate") + .HasColumnType("decimal(18,2)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("LeaseTerm") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentsToDate") + .HasColumnType("decimal(18,2)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RepaymentAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("ResponsibleForAssets") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ValueOfAssets") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationSchoolId"); + + b.ToTable("ApplicationSchoolLease", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.Loan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("ApplicationSchoolId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DynamicsSchoolLoanId") + .HasColumnType("uniqueidentifier"); + + b.Property("InterestRate") + .HasColumnType("decimal(18,2)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Schedule") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationSchoolId"); + + b.ToTable("ApplicationSchoolLoan", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.School", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ConversionApplicationId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DioceseFolderIdentifier") + .HasColumnType("nvarchar(max)"); + + b.Property("DioceseName") + .HasColumnType("nvarchar(max)"); + + b.Property("DynamicsApplyingSchoolId") + .HasColumnType("uniqueidentifier"); + + b.Property("ExemptionEndDate") + .HasColumnType("datetimeoffset"); + + b.Property("FoundationConsentFolderIdentifier") + .HasColumnType("nvarchar(max)"); + + b.Property("FoundationTrustOrBodyName") + .HasColumnType("nvarchar(max)"); + + b.Property("FurtherInformation") + .HasColumnType("nvarchar(max)"); + + b.Property("HasLeases") + .HasColumnType("bit"); + + b.Property("HasLoans") + .HasColumnType("bit"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("LocalAuthorityClosurePlanDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("LocalAuthorityReorganisationDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("MainFeederSchools") + .HasColumnType("nvarchar(max)"); + + b.Property("OfstedInspectionDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("PartOfFederation") + .HasColumnType("bit"); + + b.Property("ProtectedCharacteristics") + .HasColumnType("int"); + + b.Property("ResolutionConsentFolderIdentifier") + .HasColumnType("nvarchar(max)"); + + b.Property("Safeguarding") + .HasColumnType("bit"); + + b.Property("TrustBenefitDetails") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ConversionApplicationId"); + + b.ToTable("ApplicationSchool", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.FormTrust", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DynamicsApplicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ApplicationFormTrust", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.JoinTrust", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangesToLaGovernance") + .HasColumnType("bit"); + + b.Property("ChangesToLaGovernanceExplained") + .HasColumnType("nvarchar(max)"); + + b.Property("ChangesToTrust") + .HasColumnType("int"); + + b.Property("ChangesToTrustExplained") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DynamicsApplicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("TrustName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TrustReference") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UKPRN") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("ApplicationJoinTrust", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.TrustKeyPerson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationFormTrustId") + .HasColumnType("int"); + + b.Property("Biography") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DateOfBirth") + .HasColumnType("datetime2"); + + b.Property("DynamicsKeyPersonId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormTrustId"); + + b.ToTable("ApplicationFormTrustKeyPerson", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.TrustKeyPersonRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationFormTrustKeyPersonRoleId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Role") + .HasColumnType("int"); + + b.Property("TimeInRole") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationFormTrustKeyPersonRoleId"); + + b.ToTable("ApplicationFormTrustKeyPersonRole", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("ConversionAdvisoryBoardDecision", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDAORevokedReasonDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdvisoryBoardDecisionId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Details") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AdvisoryBoardDecisionId"); + + b.ToTable("AdvisoryBoardDecisionDAORevokedReason", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDeclinedReasonDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdvisoryBoardDecisionId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Details") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AdvisoryBoardDecisionId"); + + b.ToTable("ConversionAdvisoryBoardDecisionDeclinedReason", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDeferredReasonDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdvisoryBoardDecisionId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Details") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AdvisoryBoardDecisionId"); + + b.ToTable("ConversionAdvisoryBoardDecisionDeferredReason", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardWithdrawnReasonDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdvisoryBoardDecisionId") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Details") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AdvisoryBoardDecisionId"); + + b.ToTable("AdvisoryBoardDecisionWithdrawnReason", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.ProjectNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .HasColumnType("nvarchar(max)"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectNotes", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.FormAMatProjectAggregate.FormAMatProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationReference") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("ProposedTrustName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReferenceNumber") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("FormAMatProject", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.OpeningDateHistoryAggregate.OpeningDateHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangedAt") + .HasColumnType("datetime2"); + + b.Property("ChangedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("NewDate") + .HasColumnType("datetime2"); + + b.Property("OldDate") + .HasColumnType("datetime2"); + + b.Property("ReasonsChanged") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OpeningDateHistories", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApplicationSharePointId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("datetime2") + .HasColumnName("CreatedOn"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("FormAMatProjectId") + .HasColumnType("int") + .HasColumnName("FormAMatProjectId"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("ProjectGroupId") + .HasColumnType("int"); + + b.Property("SchoolSharePointId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Project", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.SchoolImprovementPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArrangedBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ArrangedByOther") + .HasColumnType("nvarchar(max)"); + + b.Property("ConfidenceLevel") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ExpectedEndDate") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedEndDateOther") + .HasColumnType("datetime2"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("PlanComments") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("ProvidedBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("SchoolImprovementPlans", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate.ProjectGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("ReferenceNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("TrustReference") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ProjectGroups", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.IntendedTransferBenefit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("SelectedBenefit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TransferProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TransferProjectId"); + + b.ToTable("IntendedTransferBenefit", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.TransferProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 10003000L); + + b.Property("AnyRisks") + .HasColumnType("bit"); + + b.Property("AssignedUserEmailAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("AssignedUserFullName") + .HasColumnType("nvarchar(max)"); + + b.Property("AssignedUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Author") + .HasColumnType("nvarchar(max)"); + + b.Property("BenefitsSectionIsCompleted") + .HasColumnType("bit"); + + b.Property("ComplexLandAndBuildingFurtherSpecification") + .HasColumnType("nvarchar(max)"); + + b.Property("ComplexLandAndBuildingShouldBeConsidered") + .HasColumnType("bit"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DiocesanConsent") + .HasColumnType("nvarchar(max)"); + + b.Property("EqualitiesImpactAssessmentConsidered") + .HasColumnType("bit"); + + b.Property("FeatureSectionIsCompleted") + .HasColumnType("bit"); + + b.Property("FinanceAndDebtFurtherSpecification") + .HasColumnType("nvarchar(max)"); + + b.Property("FinanceAndDebtShouldBeConsidered") + .HasColumnType("bit"); + + b.Property("HasHtbDate") + .HasColumnType("bit"); + + b.Property("HasTargetDateForTransfer") + .HasColumnType("bit"); + + b.Property("HasTransferFirstDiscussedDate") + .HasColumnType("bit"); + + b.Property("HighProfileFurtherSpecification") + .HasColumnType("nvarchar(max)"); + + b.Property("HighProfileShouldBeConsidered") + .HasColumnType("bit"); + + b.Property("HtbDate") + .HasColumnType("datetime2"); + + b.Property("IncomingTrustAgreement") + .HasColumnType("nvarchar(max)"); + + b.Property("IsFormAMat") + .HasColumnType("bit"); + + b.Property("LastModifiedOn") + .HasColumnType("datetime2"); + + b.Property("LegalRequirementsSectionIsCompleted") + .HasColumnType("bit"); + + b.Property("OtherBenefitValue") + .HasColumnType("nvarchar(max)"); + + b.Property("OtherRisksFurtherSpecification") + .HasMaxLength(20000) + .HasColumnType("nvarchar(max)"); + + b.Property("OtherRisksShouldBeConsidered") + .HasColumnType("bit"); + + b.Property("OtherTransferTypeDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("OutgoingTrustConsent") + .HasColumnType("nvarchar(max)"); + + b.Property("OutgoingTrustName") + .HasColumnType("nvarchar(max)"); + + b.Property("OutgoingTrustUkprn") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PreviousAdvisoryBoardDate") + .HasColumnType("datetime2"); + + b.Property("ProjectRationale") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectReference") + .HasColumnType("nvarchar(max)"); + + b.Property("RationaleSectionIsCompleted") + .HasColumnType("bit"); + + b.Property("RddOrEsfaIntervention") + .HasColumnType("bit"); + + b.Property("RddOrEsfaInterventionDetail") + .HasColumnType("nvarchar(max)"); + + b.Property("Recommendation") + .HasColumnType("nvarchar(max)"); + + b.Property("SpecificReasonsForTransfer") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("nvarchar(max)"); + + b.Property("TargetDateForTransfer") + .HasColumnType("datetime2"); + + b.Property("TransferDatesSectionIsCompleted") + .HasColumnType("bit"); + + b.Property("TransferFirstDiscussed") + .HasColumnType("datetime2"); + + b.Property("TrustSponsorRationale") + .HasColumnType("nvarchar(max)"); + + b.Property("TypeOfTransfer") + .HasColumnType("nvarchar(max)"); + + b.Property("Urn") + .HasColumnType("int"); + + b.Property("WhoInitiatedTheTransfer") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TransferProject", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.TransferringAcademy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DistanceFromAcademyToTrustHq") + .HasColumnType("nvarchar(max)"); + + b.Property("DistanceFromAcademyToTrustHqDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("FinancialDeficit") + .HasColumnType("nvarchar(max)"); + + b.Property("IncomingTrustName") + .HasColumnType("nvarchar(max)"); + + b.Property("IncomingTrustUkprn") + .HasColumnType("nvarchar(max)"); + + b.Property("KeyStage2PerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)"); + + b.Property("KeyStage4PerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)"); + + b.Property("KeyStage5PerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)"); + + b.Property("LatestOfstedReportAdditionalInformation") + .HasColumnType("nvarchar(max)"); + + b.Property("LocalAuthority") + .HasColumnType("nvarchar(max)"); + + b.Property("MPNameAndParty") + .HasColumnType("nvarchar(max)"); + + b.Property("OutgoingAcademyUkprn") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PFIScheme") + .HasColumnType("nvarchar(max)"); + + b.Property("PFISchemeDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAdmissionNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PupilNumbersAdditionalInformation") + .HasColumnType("nvarchar(max)"); + + b.Property("Region") + .HasColumnType("nvarchar(max)"); + + b.Property("TransferProjectId") + .HasColumnType("int"); + + b.Property("ViabilityIssues") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TransferProjectId"); + + b.ToTable("TransferringAcademy", "academisation"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.FormTrust", "FormTrust") + .WithOne() + .HasForeignKey("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", "FormTrustId"); + + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.JoinTrust", "JoinTrust") + .WithOne() + .HasForeignKey("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", "JoinTrustId"); + + b.Navigation("FormTrust"); + + b.Navigation("JoinTrust"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Contributor", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", null) + .WithMany("Contributors") + .HasForeignKey("ConversionApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.ContributorDetails", "Details", b1 => + { + b1.Property("ContributorId") + .HasColumnType("int"); + + b1.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("EmailAddress"); + + b1.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("FirstName"); + + b1.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("LastName"); + + b1.Property("OtherRoleName") + .HasColumnType("nvarchar(max)") + .HasColumnName("OtherRoleName"); + + b1.Property("Role") + .HasColumnType("int") + .HasColumnName("Role"); + + b1.HasKey("ContributorId"); + + b1.ToTable("ConversionApplicationContributor", "academisation"); + + b1.WithOwner() + .HasForeignKey("ContributorId"); + }); + + b.Navigation("Details") + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.Lease", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.School", null) + .WithMany("Leases") + .HasForeignKey("ApplicationSchoolId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.Loan", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.School", null) + .WithMany("Loans") + .HasForeignKey("ApplicationSchoolId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.School", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", null) + .WithMany("Schools") + .HasForeignKey("ConversionApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.SchoolDetails", "Details", b1 => + { + b1.Property("SchoolId") + .HasColumnType("int"); + + b1.Property("ApplicationJoinTrustReason") + .HasColumnType("nvarchar(max)") + .HasColumnName("JoinTrustReason"); + + b1.Property("ApproverContactEmail") + .HasColumnType("nvarchar(max)") + .HasColumnName("ApproverContactEmail"); + + b1.Property("ApproverContactName") + .HasColumnType("nvarchar(max)") + .HasColumnName("ApproverContactName"); + + b1.Property("CapacityAssumptions") + .HasColumnType("nvarchar(max)") + .HasColumnName("CapacityAssumptions"); + + b1.Property("CapacityPublishedAdmissionsNumber") + .HasColumnType("int") + .HasColumnName("CapacityPublishedAdmissionsNumber"); + + b1.Property("ConfirmPaySupportGrantToSchool") + .HasColumnType("bit") + .HasColumnName("ConfirmPaySupportGrantToSchool"); + + b1.Property("ContactChairEmail") + .HasColumnType("nvarchar(max)") + .HasColumnName("ContactChairEmail"); + + b1.Property("ContactChairName") + .HasColumnType("nvarchar(max)") + .HasColumnName("ContactChairName"); + + b1.Property("ContactHeadEmail") + .HasColumnType("nvarchar(max)") + .HasColumnName("ContactHeadEmail"); + + b1.Property("ContactHeadName") + .HasColumnType("nvarchar(max)") + .HasColumnName("ContactHeadName"); + + b1.Property("ContactRole") + .HasColumnType("nvarchar(max)") + .HasColumnName("ContactRole"); + + b1.Property("ConversionChangeNamePlanned") + .HasColumnType("bit") + .HasColumnName("ConversionChangeNamePlanned"); + + b1.Property("ConversionTargetDate") + .HasColumnType("datetime2") + .HasColumnName("ConversionTargetDate"); + + b1.Property("ConversionTargetDateExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("ConversionTargetDateExplained"); + + b1.Property("ConversionTargetDateSpecified") + .HasColumnType("bit") + .HasColumnName("ConversionTargetDateSpecified"); + + b1.Property("DeclarationBodyAgree") + .HasColumnType("bit") + .HasColumnName("DeclarationBodyAgree"); + + b1.Property("DeclarationIAmTheChairOrHeadteacher") + .HasColumnType("bit") + .HasColumnName("DeclarationIAmTheChairOrHeadteacher"); + + b1.Property("DeclarationSignedByName") + .HasColumnType("nvarchar(max)") + .HasColumnName("DeclarationSignedByName"); + + b1.Property("FinanceOngoingInvestigations") + .HasColumnType("bit") + .HasColumnName("FinanceOngoingInvestigations"); + + b1.Property("FinancialInvestigationsExplain") + .HasColumnType("nvarchar(max)") + .HasColumnName("FinancialInvestigationsExplain"); + + b1.Property("FinancialInvestigationsTrustAware") + .HasColumnType("bit") + .HasColumnName("FinancialInvestigationsTrustAware"); + + b1.Property("MainContactOtherEmail") + .HasColumnType("nvarchar(max)") + .HasColumnName("MainContactOtherEmail"); + + b1.Property("MainContactOtherName") + .HasColumnType("nvarchar(max)") + .HasColumnName("MainContactOtherName"); + + b1.Property("MainContactOtherRole") + .HasColumnType("nvarchar(max)") + .HasColumnName("MainContactOtherRole"); + + b1.Property("ProjectedPupilNumbersYear1") + .HasColumnType("int") + .HasColumnName("ProjectedPupilNumbersYear1"); + + b1.Property("ProjectedPupilNumbersYear2") + .HasColumnType("int") + .HasColumnName("ProjectedPupilNumbersYear2"); + + b1.Property("ProjectedPupilNumbersYear3") + .HasColumnType("int") + .HasColumnName("ProjectedPupilNumbersYear3"); + + b1.Property("ProposedNewSchoolName") + .HasColumnType("nvarchar(max)") + .HasColumnName("ProposedNewSchoolName"); + + b1.Property("SchoolConversionReasonsForJoining") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolConversionReasonsForJoining"); + + b1.Property("SchoolHasConsultedStakeholders") + .HasColumnType("bit") + .HasColumnName("SchoolHasConsultedStakeholders"); + + b1.Property("SchoolName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolName"); + + b1.Property("SchoolPlanToConsultStakeholders") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolPlanToConsultStakeholders"); + + b1.Property("SchoolSupportGrantFundsPaidTo") + .HasColumnType("int") + .HasColumnName("SupportGrantFundsPaidTo"); + + b1.Property("Urn") + .HasColumnType("int") + .HasColumnName("Urn"); + + b1.HasKey("SchoolId"); + + b1.ToTable("ApplicationSchool", "academisation"); + + b1.WithOwner() + .HasForeignKey("SchoolId"); + + b1.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.LandAndBuildings", "LandAndBuildings", b2 => + { + b2.Property("SchoolDetailsSchoolId") + .HasColumnType("int"); + + b2.Property("FacilitiesShared") + .HasColumnType("bit") + .HasColumnName("FacilitiesShared"); + + b2.Property("FacilitiesSharedExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("FacilitiesSharedExplained"); + + b2.Property("Grants") + .HasColumnType("bit") + .HasColumnName("Grants"); + + b2.Property("GrantsAwardingBodies") + .HasColumnType("nvarchar(max)") + .HasColumnName("GrantsAwardingBodies"); + + b2.Property("OwnerExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("OwnerExplained"); + + b2.Property("PartOfBuildingSchoolsForFutureProgramme") + .HasColumnType("bit") + .HasColumnName("PartOfBuildingSchoolsForFutureProgramme"); + + b2.Property("PartOfPfiScheme") + .HasColumnType("bit") + .HasColumnName("PartOfPfiScheme"); + + b2.Property("PartOfPfiSchemeType") + .HasColumnType("nvarchar(max)") + .HasColumnName("PartOfPfiSchemeType"); + + b2.Property("PartOfPrioritySchoolsBuildingProgramme") + .HasColumnType("bit") + .HasColumnName("PartOfPrioritySchoolsBuildingProgramme"); + + b2.Property("WorksPlanned") + .HasColumnType("bit") + .HasColumnName("WorksPlanned"); + + b2.Property("WorksPlannedDate") + .HasColumnType("datetime2") + .HasColumnName("WorksPlannedDate"); + + b2.Property("WorksPlannedExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("WorksPlannedExplained"); + + b2.HasKey("SchoolDetailsSchoolId"); + + b2.ToTable("ApplicationSchool", "academisation"); + + b2.WithOwner() + .HasForeignKey("SchoolDetailsSchoolId"); + }); + + b1.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.FinancialYear", "CurrentFinancialYear", b2 => + { + b2.Property("SchoolDetailsSchoolId") + .HasColumnType("int"); + + b2.Property("CapitalCarryForward") + .HasColumnType("decimal(18,2)") + .HasColumnName("CurrentFinancialYearCapitalCarryForward"); + + b2.Property("CapitalCarryForwardExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("CurrentFinancialYearCapitalCarryForwardExplained"); + + b2.Property("CapitalCarryForwardFileLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("CurrentFinancialYearCapitalCarryForwardFileLink"); + + b2.Property("CapitalCarryForwardStatus") + .HasColumnType("int") + .HasColumnName("CurrentFinancialYearCapitalCarryForwardStatus"); + + b2.Property("FinancialYearEndDate") + .HasColumnType("datetime2") + .HasColumnName("CurrentFinancialYearEndDate"); + + b2.Property("Revenue") + .HasColumnType("decimal(18,2)") + .HasColumnName("CurrentFinancialYearRevenue"); + + b2.Property("RevenueStatus") + .HasColumnType("int") + .HasColumnName("CurrentFinancialYearRevenueStatus"); + + b2.Property("RevenueStatusExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("CurrentFinancialYearRevenueStatusExplained"); + + b2.Property("RevenueStatusFileLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("CurrentFinancialYearRevenueStatusFileLink"); + + b2.HasKey("SchoolDetailsSchoolId"); + + b2.ToTable("ApplicationSchool", "academisation"); + + b2.WithOwner() + .HasForeignKey("SchoolDetailsSchoolId"); + }); + + b1.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.FinancialYear", "NextFinancialYear", b2 => + { + b2.Property("SchoolDetailsSchoolId") + .HasColumnType("int"); + + b2.Property("CapitalCarryForward") + .HasColumnType("decimal(18,2)") + .HasColumnName("NextFinancialYearCapitalCarryForward"); + + b2.Property("CapitalCarryForwardExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("NextFinancialYearCapitalCarryForwardExplained"); + + b2.Property("CapitalCarryForwardFileLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("NextFinancialYearCapitalCarryForwardFileLink"); + + b2.Property("CapitalCarryForwardStatus") + .HasColumnType("int") + .HasColumnName("NextFinancialYearCapitalCarryForwardStatus"); + + b2.Property("FinancialYearEndDate") + .HasColumnType("datetime2") + .HasColumnName("NextFinancialYearEndDate"); + + b2.Property("Revenue") + .HasColumnType("decimal(18,2)") + .HasColumnName("NextFinancialYearRevenue"); + + b2.Property("RevenueStatus") + .HasColumnType("int") + .HasColumnName("NextFinancialYearRevenueStatus"); + + b2.Property("RevenueStatusExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("NextFinancialYearRevenueStatusExplained"); + + b2.Property("RevenueStatusFileLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("NextFinancialYearRevenueStatusFileLink"); + + b2.HasKey("SchoolDetailsSchoolId"); + + b2.ToTable("ApplicationSchool", "academisation"); + + b2.WithOwner() + .HasForeignKey("SchoolDetailsSchoolId"); + }); + + b1.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.FinancialYear", "PreviousFinancialYear", b2 => + { + b2.Property("SchoolDetailsSchoolId") + .HasColumnType("int"); + + b2.Property("CapitalCarryForward") + .HasColumnType("decimal(18,2)") + .HasColumnName("PreviousFinancialYearCapitalCarryForward"); + + b2.Property("CapitalCarryForwardExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("PreviousFinancialYearCapitalCarryForwardExplained"); + + b2.Property("CapitalCarryForwardFileLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("PreviousFinancialYearCapitalCarryForwardFileLink"); + + b2.Property("CapitalCarryForwardStatus") + .HasColumnType("int") + .HasColumnName("PreviousFinancialYearCapitalCarryForwardStatus"); + + b2.Property("FinancialYearEndDate") + .HasColumnType("datetime2") + .HasColumnName("PreviousFinancialYearEndDate"); + + b2.Property("Revenue") + .HasColumnType("decimal(18,2)") + .HasColumnName("PreviousFinancialYearRevenue"); + + b2.Property("RevenueStatus") + .HasColumnType("int") + .HasColumnName("PreviousFinancialYearRevenueStatus"); + + b2.Property("RevenueStatusExplained") + .HasColumnType("nvarchar(max)") + .HasColumnName("PreviousFinancialYearRevenueStatusExplained"); + + b2.Property("RevenueStatusFileLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("PreviousFinancialYearRevenueStatusFileLink"); + + b2.HasKey("SchoolDetailsSchoolId"); + + b2.ToTable("ApplicationSchool", "academisation"); + + b2.WithOwner() + .HasForeignKey("SchoolDetailsSchoolId"); + }); + + b1.Navigation("CurrentFinancialYear"); + + b1.Navigation("LandAndBuildings"); + + b1.Navigation("NextFinancialYear"); + + b1.Navigation("PreviousFinancialYear"); + }); + + b.Navigation("Details") + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.FormTrust", b => + { + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ApplicationAggregate.FormTrustDetails", "TrustDetails", b1 => + { + b1.Property("FormTrustId") + .HasColumnType("int"); + + b1.Property("FormTrustGrowthPlansYesNo") + .HasColumnType("bit") + .HasColumnName("FormTrustGrowthPlansYesNo"); + + b1.Property("FormTrustImprovementApprovedSponsor") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustImprovementApprovedSponsor"); + + b1.Property("FormTrustImprovementStrategy") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustImprovementStrategy"); + + b1.Property("FormTrustImprovementSupport") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustImprovementSupport"); + + b1.Property("FormTrustOpeningDate") + .HasColumnType("datetime2") + .HasColumnName("FormTrustOpeningDate"); + + b1.Property("FormTrustPlanForGrowth") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustPlanForGrowth"); + + b1.Property("FormTrustPlansForNoGrowth") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustPlansForNoGrowth"); + + b1.Property("FormTrustProposedNameOfTrust") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustProposedNameOfTrust"); + + b1.Property("FormTrustReasonApprovaltoConvertasSAT") + .HasColumnType("bit") + .HasColumnName("FormTrustReasonApprovaltoConvertasSAT"); + + b1.Property("FormTrustReasonApprovedPerson") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustReasonApprovedPerson"); + + b1.Property("FormTrustReasonForming") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustReasonForming"); + + b1.Property("FormTrustReasonFreedom") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustReasonFreedom"); + + b1.Property("FormTrustReasonGeoAreas") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustReasonGeoAreas"); + + b1.Property("FormTrustReasonImproveTeaching") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustReasonImproveTeaching"); + + b1.Property("FormTrustReasonVision") + .HasColumnType("nvarchar(max)") + .HasColumnName("FormTrustReasonVision"); + + b1.Property("TrustApproverEmail") + .HasColumnType("nvarchar(max)") + .HasColumnName("TrustApproverEmail"); + + b1.Property("TrustApproverName") + .HasColumnType("nvarchar(max)") + .HasColumnName("TrustApproverName"); + + b1.HasKey("FormTrustId"); + + b1.ToTable("ApplicationFormTrust", "academisation"); + + b1.WithOwner() + .HasForeignKey("FormTrustId"); + }); + + b.Navigation("TrustDetails") + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.TrustKeyPerson", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.FormTrust", null) + .WithMany("KeyPeople") + .HasForeignKey("ApplicationFormTrustId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.TrustKeyPersonRole", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.TrustKeyPerson", null) + .WithMany("Roles") + .HasForeignKey("ApplicationFormTrustKeyPersonRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", b => + { + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDecisionDetails", "AdvisoryBoardDecisionDetails", b1 => + { + b1.Property("ConversionAdvisoryBoardDecisionId") + .HasColumnType("int"); + + b1.Property("AcademyOrderDate") + .HasColumnType("datetime2") + .HasColumnName("AcademyOrderDate"); + + b1.Property("AdvisoryBoardDecisionDate") + .HasColumnType("datetime2") + .HasColumnName("AdvisoryBoardDecisionDate"); + + b1.Property("ApprovedConditionsDetails") + .HasColumnType("nvarchar(max)") + .HasColumnName("ApprovedConditionsDetails"); + + b1.Property("ApprovedConditionsSet") + .HasColumnType("bit") + .HasColumnName("ApprovedConditionsSet"); + + b1.Property("ConversionProjectId") + .HasColumnType("int") + .HasColumnName("ConversionProjectId"); + + b1.Property("Decision") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("Decision"); + + b1.Property("DecisionMadeBy") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("DecisionMadeBy"); + + b1.Property("DecisionMakerName") + .HasColumnType("nvarchar(max)") + .HasColumnName("DecisionMakerName"); + + b1.Property("TransferProjectId") + .HasColumnType("int") + .HasColumnName("TransferProjectId"); + + b1.HasKey("ConversionAdvisoryBoardDecisionId"); + + b1.ToTable("ConversionAdvisoryBoardDecision", "academisation"); + + b1.WithOwner() + .HasForeignKey("ConversionAdvisoryBoardDecisionId"); + }); + + b.Navigation("AdvisoryBoardDecisionDetails") + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDAORevokedReasonDetails", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", null) + .WithMany("DaoRevokedReasons") + .HasForeignKey("AdvisoryBoardDecisionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDeclinedReasonDetails", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", null) + .WithMany("DeclinedReasons") + .HasForeignKey("AdvisoryBoardDecisionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardDeferredReasonDetails", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", null) + .WithMany("DeferredReasons") + .HasForeignKey("AdvisoryBoardDecisionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ConversionAdvisoryBoardDecisionAggregate.AdvisoryBoardWithdrawnReasonDetails", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", null) + .WithMany("WithdrawnReasons") + .HasForeignKey("AdvisoryBoardDecisionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.ProjectNote", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ProjectAggregate.Project", null) + .WithMany("Notes") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.FormAMatProjectAggregate.FormAMatProject", b => + { + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.User", "AssignedUser", b1 => + { + b1.Property("FormAMatProjectId") + .HasColumnType("int"); + + b1.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("AssignedUserEmailAddress"); + + b1.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("AssignedUserFullName"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("AssignedUserId"); + + b1.HasKey("FormAMatProjectId"); + + b1.ToTable("FormAMatProject", "academisation"); + + b1.WithOwner() + .HasForeignKey("FormAMatProjectId"); + }); + + b.Navigation("AssignedUser"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.Project", b => + { + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.ProjectDetails", "Details", b1 => + { + b1.Property("ProjectId") + .HasColumnType("int"); + + b1.Property("AcademyTypeAndRoute") + .HasColumnType("nvarchar(max)") + .HasColumnName("AcademyTypeAndRoute"); + + b1.Property("ActualPupilNumbers") + .HasColumnType("int") + .HasColumnName("ActualPupilNumbers"); + + b1.Property("AgeRange") + .HasColumnType("nvarchar(max)") + .HasColumnName("AgeRange"); + + b1.Property("AnnexBFormReceived") + .HasColumnType("bit") + .HasColumnName("AnnexBFormReceived"); + + b1.Property("AnnexBFormUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("AnnexBFormUrl"); + + b1.Property("ApplicationReceivedDate") + .HasColumnType("datetime2") + .HasColumnName("ApplicationReceivedDate"); + + b1.Property("ApplicationReferenceNumber") + .HasColumnType("nvarchar(max)") + .HasColumnName("ApplicationReferenceNumber"); + + b1.Property("AssignedDate") + .HasColumnType("datetime2") + .HasColumnName("AssignedDate"); + + b1.Property("Author") + .HasColumnType("nvarchar(max)") + .HasColumnName("Author"); + + b1.Property("BaselineDate") + .HasColumnType("datetime2") + .HasColumnName("BaselineDate"); + + b1.Property("Capacity") + .HasColumnType("int") + .HasColumnName("Capacity"); + + b1.Property("CapitalCarryForwardAtEndMarchCurrentYear") + .HasColumnType("decimal(18,2)") + .HasColumnName("CapitalCarryForwardAtEndMarchCurrentYear"); + + b1.Property("CapitalCarryForwardAtEndMarchNextYear") + .HasColumnType("decimal(18,2)") + .HasColumnName("CapitalCarryForwardAtEndMarchNextYear"); + + b1.Property("ClearedBy") + .HasColumnType("nvarchar(max)") + .HasColumnName("ClearedBy"); + + b1.Property("Consultation") + .HasColumnType("nvarchar(max)") + .HasColumnName("Consultation"); + + b1.Property("ConversionSupportGrantAmount") + .HasColumnType("decimal(18,2)") + .HasColumnName("ConversionSupportGrantAmount"); + + b1.Property("ConversionSupportGrantAmountChanged") + .HasColumnType("bit") + .HasColumnName("ConversionSupportGrantAmountChanged"); + + b1.Property("ConversionSupportGrantChangeReason") + .HasColumnType("nvarchar(max)") + .HasColumnName("ConversionSupportGrantChangeReason"); + + b1.Property("ConversionSupportGrantEnvironmentalImprovementGrant") + .HasColumnType("nvarchar(max)") + .HasColumnName("ConversionSupportGrantEnvironmentalImprovementGrant"); + + b1.Property("ConversionSupportGrantNumberOfSites") + .HasColumnType("nvarchar(max)"); + + b1.Property("ConversionSupportGrantType") + .HasColumnType("nvarchar(max)") + .HasColumnName("ConversionSupportGrantType"); + + b1.Property("DaoPackSentDate") + .HasColumnType("datetime2") + .HasColumnName("DaoPackSentDate"); + + b1.Property("DiocesanConsent") + .HasColumnType("nvarchar(max)") + .HasColumnName("DiocesanConsent"); + + b1.Property("DiocesanTrust") + .HasColumnType("nvarchar(max)") + .HasColumnName("DiocesanTrust"); + + b1.Property("DistanceFromSchoolToTrustHeadquarters") + .HasColumnType("decimal(18,2)") + .HasColumnName("DistanceFromSchoolToTrustHeadquarters"); + + b1.Property("DistanceFromSchoolToTrustHeadquartersAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("DistanceFromSchoolToTrustHeadquartersAdditionalInformation"); + + b1.Property("EducationalAttendanceAdditionalInformation") + .HasColumnType("nvarchar(max)"); + + b1.Property("EndOfCurrentFinancialYear") + .HasColumnType("datetime2") + .HasColumnName("EndOfCurrentFinancialYear"); + + b1.Property("EndOfNextFinancialYear") + .HasColumnType("datetime2") + .HasColumnName("EndOfNextFinancialYear"); + + b1.Property("ExternalApplicationFormSaved") + .HasColumnType("bit") + .HasColumnName("ExternalApplicationFormSaved"); + + b1.Property("ExternalApplicationFormUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExternalApplicationFormUrl"); + + b1.Property("FinancialDeficit") + .HasColumnType("nvarchar(max)") + .HasColumnName("FinancialDeficit"); + + b1.Property("Form7Received") + .HasColumnType("nvarchar(max)") + .HasColumnName("Form7Received"); + + b1.Property("Form7ReceivedDate") + .HasColumnType("datetime2") + .HasColumnName("Form7ReceivedDate"); + + b1.Property("FoundationConsent") + .HasColumnType("nvarchar(max)") + .HasColumnName("FoundationConsent"); + + b1.Property("GoverningBodyResolution") + .HasColumnType("nvarchar(max)") + .HasColumnName("GoverningBodyResolution"); + + b1.Property("HeadTeacherBoardDate") + .HasColumnType("datetime2") + .HasColumnName("HeadTeacherBoardDate"); + + b1.Property("IfdPipelineId") + .HasColumnType("int") + .HasColumnName("IfdPipelineId"); + + b1.Property("IsFormAMat") + .HasColumnType("bit") + .HasColumnName("IsFormAMat"); + + b1.Property("KeyStage2PerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("KeyStage2PerformanceAdditionalInformation"); + + b1.Property("KeyStage4PerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("KeyStage4PerformanceAdditionalInformation"); + + b1.Property("KeyStage5PerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("KeyStage5PerformanceAdditionalInformation"); + + b1.Property("LegalRequirementsSectionComplete") + .HasColumnType("bit") + .HasColumnName("LegalRequirementsSectionComplete"); + + b1.Property("LocalAuthority") + .HasColumnType("nvarchar(max)") + .HasColumnName("LocalAuthority"); + + b1.Property("LocalAuthorityInformationTemplateComments") + .HasColumnType("nvarchar(max)") + .HasColumnName("LocalAuthorityInformationTemplateComments"); + + b1.Property("LocalAuthorityInformationTemplateLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("LocalAuthorityInformationTemplateLink"); + + b1.Property("LocalAuthorityInformationTemplateReturnedDate") + .HasColumnType("datetime2") + .HasColumnName("LocalAuthorityInformationTemplateReturnedDate"); + + b1.Property("LocalAuthorityInformationTemplateSectionComplete") + .HasColumnType("bit") + .HasColumnName("LocalAuthorityInformationTemplateSectionComplete"); + + b1.Property("LocalAuthorityInformationTemplateSentDate") + .HasColumnType("datetime2") + .HasColumnName("LocalAuthorityInformationTemplateSentDate"); + + b1.Property("MemberOfParliamentNameAndParty") + .HasColumnType("nvarchar(max)") + .HasColumnName("MemberOfParliamentNameAndParty"); + + b1.Property("NameOfTrust") + .HasColumnType("nvarchar(max)") + .HasColumnName("NameOfTrust"); + + b1.Property("NumberOfAlternativeProvisionPlaces") + .HasColumnType("int"); + + b1.Property("NumberOfFundedResidentialPlaces") + .HasColumnType("decimal(18,2)"); + + b1.Property("NumberOfMedicalPlaces") + .HasColumnType("int"); + + b1.Property("NumberOfPlacesFundedFor") + .HasColumnType("decimal(18,2)"); + + b1.Property("NumberOfPost16Places") + .HasColumnType("int"); + + b1.Property("NumberOfResidentialPlaces") + .HasColumnType("decimal(18,2)"); + + b1.Property("NumberOfSENUnitPlaces") + .HasColumnType("int"); + + b1.Property("PartOfPfiScheme") + .HasColumnType("nvarchar(max)") + .HasColumnName("PartOfPfiScheme"); + + b1.Property("PercentageFreeSchoolMeals") + .HasColumnType("decimal(18,2)") + .HasColumnName("PercentageFreeSchoolMeals"); + + b1.Property("PercentageOfGoodOrOutstandingSchoolsInTheDiocesanTrust") + .HasColumnType("decimal(18,2)") + .HasColumnName("PercentageOfGoodOrOutstandingSchoolsInTheDiocesanTrust"); + + b1.Property("PfiSchemeDetails") + .HasColumnType("nvarchar(max)") + .HasColumnName("PfiSchemeDetails"); + + b1.Property("PreviousHeadTeacherBoardDate") + .HasColumnType("datetime2") + .HasColumnName("PreviousHeadTeacherBoardDate"); + + b1.Property("PreviousHeadTeacherBoardDateQuestion") + .HasColumnType("nvarchar(max)") + .HasColumnName("PreviousHeadTeacherBoardDateQuestion"); + + b1.Property("PreviousHeadTeacherBoardLink") + .HasColumnType("nvarchar(max)") + .HasColumnName("PreviousHeadTeacherBoardLink"); + + b1.Property("ProjectDatesSectionComplete") + .HasColumnType("bit") + .HasColumnName("ProjectDatesSectionComplete"); + + b1.Property("ProjectStatus") + .HasColumnType("nvarchar(max)") + .HasColumnName("ProjectStatus"); + + b1.Property("ProjectedRevenueBalanceAtEndMarchNextYear") + .HasColumnType("decimal(18,2)") + .HasColumnName("ProjectedRevenueBalanceAtEndMarchNextYear"); + + b1.Property("ProposedConversionDate") + .HasColumnType("datetime2") + .HasColumnName("ProposedAcademyOpeningDate"); + + b1.Property("PublishedAdmissionNumber") + .HasColumnType("nvarchar(max)") + .HasColumnName("PublishedAdmissionNumber"); + + b1.Property("PupilsAttendingGroupMedicalAndHealthNeeds") + .HasColumnType("bit"); + + b1.Property("PupilsAttendingGroupPermanentlyExcluded") + .HasColumnType("bit"); + + b1.Property("PupilsAttendingGroupTeenageMums") + .HasColumnType("bit"); + + b1.Property("RationaleForProject") + .HasColumnType("nvarchar(max)") + .HasColumnName("RationaleForProject"); + + b1.Property("RationaleForTrust") + .HasColumnType("nvarchar(max)") + .HasColumnName("RationaleForTrust"); + + b1.Property("RationaleSectionComplete") + .HasColumnType("bit") + .HasColumnName("RationaleSectionComplete"); + + b1.Property("RecommendationForProject") + .HasColumnType("nvarchar(max)") + .HasColumnName("RecommendationForProject"); + + b1.Property("Region") + .HasColumnType("nvarchar(max)") + .HasColumnName("Region"); + + b1.Property("RevenueCarryForwardAtEndMarchCurrentYear") + .HasColumnType("decimal(18,2)") + .HasColumnName("RevenueCarryForwardAtEndMarchCurrentYear"); + + b1.Property("RisksAndIssues") + .HasColumnType("nvarchar(max)") + .HasColumnName("RisksAndIssues"); + + b1.Property("RisksAndIssuesSectionComplete") + .HasColumnType("bit") + .HasColumnName("RisksAndIssuesSectionComplete"); + + b1.Property("SchoolAndTrustInformationSectionComplete") + .HasColumnType("bit") + .HasColumnName("SchoolAndTrustInformationSectionComplete"); + + b1.Property("SchoolBudgetInformationAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolBudgetInformationAdditionalInformation"); + + b1.Property("SchoolBudgetInformationSectionComplete") + .HasColumnType("bit") + .HasColumnName("SchoolBudgetInformationSectionComplete"); + + b1.Property("SchoolName") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolName"); + + b1.Property("SchoolOverviewSectionComplete") + .HasColumnType("bit") + .HasColumnName("SchoolOverviewSectionComplete"); + + b1.Property("SchoolPerformanceAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolPerformanceAdditionalInformation"); + + b1.Property("SchoolPhase") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolPhase"); + + b1.Property("SchoolPupilForecastsAdditionalInformation") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolPupilForecastsAdditionalInformation"); + + b1.Property("SchoolType") + .HasColumnType("nvarchar(max)") + .HasColumnName("SchoolType"); + + b1.Property("SponsorName") + .HasColumnType("nvarchar(max)") + .HasColumnName("SponsorName"); + + b1.Property("SponsorReferenceNumber") + .HasColumnType("nvarchar(max)") + .HasColumnName("SponsorReferenceNumber"); + + b1.Property("TrustReferenceNumber") + .HasColumnType("nvarchar(max)") + .HasColumnName("TrustReferenceNumber"); + + b1.Property("Urn") + .HasColumnType("int") + .HasColumnName("Urn"); + + b1.Property("Version") + .HasColumnType("nvarchar(max)") + .HasColumnName("Version"); + + b1.Property("ViabilityIssues") + .HasColumnType("nvarchar(max)") + .HasColumnName("ViabilityIssues"); + + b1.Property("YearOneProjectedCapacity") + .HasColumnType("int") + .HasColumnName("YearOneProjectedCapacity"); + + b1.Property("YearOneProjectedPupilNumbers") + .HasColumnType("int") + .HasColumnName("YearOneProjectedPupilNumbers"); + + b1.Property("YearThreeProjectedCapacity") + .HasColumnType("int") + .HasColumnName("YearThreeProjectedCapacity"); + + b1.Property("YearThreeProjectedPupilNumbers") + .HasColumnType("int") + .HasColumnName("YearThreeProjectedPupilNumbers"); + + b1.Property("YearTwoProjectedCapacity") + .HasColumnType("int") + .HasColumnName("YearTwoProjectedCapacity"); + + b1.Property("YearTwoProjectedPupilNumbers") + .HasColumnType("int") + .HasColumnName("YearTwoProjectedPupilNumbers"); + + b1.HasKey("ProjectId"); + + b1.ToTable("Project", "academisation"); + + b1.WithOwner() + .HasForeignKey("ProjectId"); + + b1.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.User", "AssignedUser", b2 => + { + b2.Property("ProjectDetailsProjectId") + .HasColumnType("int"); + + b2.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("AssignedUserEmailAddress"); + + b2.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("AssignedUserFullName"); + + b2.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("AssignedUserId"); + + b2.HasKey("ProjectDetailsProjectId"); + + b2.ToTable("Project", "academisation"); + + b2.WithOwner() + .HasForeignKey("ProjectDetailsProjectId"); + }); + + b1.Navigation("AssignedUser"); + }); + + b.Navigation("Details") + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.SchoolImprovementPlan", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.ProjectAggregate.Project", null) + .WithMany("SchoolImprovementPlans") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate.ProjectGroup", b => + { + b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.User", "AssignedUser", b1 => + { + b1.Property("ProjectGroupId") + .HasColumnType("int"); + + b1.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("AssignedUserEmailAddress"); + + b1.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("AssignedUserFullName"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("AssignedUserId"); + + b1.HasKey("ProjectGroupId"); + + b1.ToTable("ProjectGroups", "academisation"); + + b1.WithOwner() + .HasForeignKey("ProjectGroupId"); + }); + + b.Navigation("AssignedUser"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.IntendedTransferBenefit", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.TransferProject", null) + .WithMany("IntendedTransferBenefits") + .HasForeignKey("TransferProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.TransferringAcademy", b => + { + b.HasOne("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.TransferProject", null) + .WithMany("TransferringAcademies") + .HasForeignKey("TransferProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Application", b => + { + b.Navigation("Contributors"); + + b.Navigation("Schools"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Schools.School", b => + { + b.Navigation("Leases"); + + b.Navigation("Loans"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.FormTrust", b => + { + b.Navigation("KeyPeople"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ApplicationAggregate.Trusts.TrustKeyPerson", b => + { + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ConversionAdvisoryBoardDecisionAggregate.ConversionAdvisoryBoardDecision", b => + { + b.Navigation("DaoRevokedReasons"); + + b.Navigation("DeclinedReasons"); + + b.Navigation("DeferredReasons"); + + b.Navigation("WithdrawnReasons"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.Project", b => + { + b.Navigation("Notes"); + + b.Navigation("SchoolImprovementPlans"); + }); + + modelBuilder.Entity("Dfe.Academies.Academisation.Domain.TransferProjectAggregate.TransferProject", b => + { + b.Navigation("IntendedTransferBenefits"); + + b.Navigation("TransferringAcademies"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.cs b/Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.cs new file mode 100644 index 000000000..d21bbad6a --- /dev/null +++ b/Dfe.Academies.Academisation.Data/Migrations/20240726132737_Removed-project-group-FK-Ref.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Dfe.Academies.Academisation.Data.Migrations +{ + /// + public partial class RemovedprojectgroupFKRef : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Project_ProjectGroups_ProjectGroupId", + schema: "academisation", + table: "Project"); + + migrationBuilder.DropIndex( + name: "IX_Project_ProjectGroupId", + schema: "academisation", + table: "Project"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Project_ProjectGroupId", + schema: "academisation", + table: "Project", + column: "ProjectGroupId"); + + migrationBuilder.AddForeignKey( + name: "FK_Project_ProjectGroups_ProjectGroupId", + schema: "academisation", + table: "Project", + column: "ProjectGroupId", + principalSchema: "academisation", + principalTable: "ProjectGroups", + principalColumn: "Id"); + } + } +} diff --git a/Dfe.Academies.Academisation.Data/Migrations/AcademisationContextModelSnapshot.cs b/Dfe.Academies.Academisation.Data/Migrations/AcademisationContextModelSnapshot.cs index 303a8199f..9fdf30bdd 100644 --- a/Dfe.Academies.Academisation.Data/Migrations/AcademisationContextModelSnapshot.cs +++ b/Dfe.Academies.Academisation.Data/Migrations/AcademisationContextModelSnapshot.cs @@ -696,8 +696,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("ProjectGroupId"); - b.ToTable("Project", "academisation"); }); @@ -1737,10 +1735,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.Project", b => { - b.HasOne("Dfe.Academies.Academisation.Domain.ProjectGroupsAggregate.ProjectGroup", "Group") - .WithMany() - .HasForeignKey("ProjectGroupId"); - b.OwnsOne("Dfe.Academies.Academisation.Domain.Core.ProjectAggregate.ProjectDetails", "Details", b1 => { b1.Property("ProjectId") @@ -2181,8 +2175,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Details") .IsRequired(); - - b.Navigation("Group"); }); modelBuilder.Entity("Dfe.Academies.Academisation.Domain.ProjectAggregate.SchoolImprovementPlan", b => From 623f89b0827f7c1f42497acea1eabf1658fe9b2b Mon Sep 17 00:00:00 2001 From: "SHAKIR, Muhammad" Date: Fri, 26 Jul 2024 14:35:43 +0100 Subject: [PATCH 15/15] added missing file --- Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs index 81d0c7115..3dac50c0f 100644 --- a/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs +++ b/Dfe.Academies.Academisation.Domain/ProjectAggregate/Project.cs @@ -36,7 +36,6 @@ private Project(ProjectDetails projectDetails) public int? FormAMatProjectId { get; private set; } public int? ProjectGroupId { get; private set; } - public ProjectGroup Group { get; private set; } public DateTime? DeletedAt { get; private set; } ///