diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ApprenticeshipApprovalsController.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ApprenticeshipApprovalsController.cs new file mode 100644 index 000000000..8e97b80d3 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api/Controllers/ApprenticeshipApprovalsController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Authorization; +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; + +namespace SFA.DAS.CommitmentsV2.Api.Controllers; + +[ApiController] +[Authorize] +[Route("api/apprenticeships/{ApprenticeshipId:long}/approvals/{ApprovalRequestId:Guid}")] +public class ApprenticeshipApprovalsController(IMediator mediator) : ControllerBase +{ + [HttpGet] + public async Task GetApprenticeshipApproval(long apprenticeshipId, Guid approvalRequestId) + { + var result = await mediator.Send(new GetApprenticeshipApprovalQuery(apprenticeshipId, approvalRequestId)); + + if(result == null) + { + return NotFound(); + } + + return Ok(result); + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalHandlerTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalHandlerTests.cs new file mode 100644 index 000000000..1fdfd7e57 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalHandlerTests.cs @@ -0,0 +1,217 @@ +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; +using SFA.DAS.CommitmentsV2.Data; +using SFA.DAS.CommitmentsV2.Models; +using SFA.DAS.CommitmentsV2.Types; + +namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Queries.GetApprenticeshipApproval; + +[TestFixture] +public class GetApprenticeshipApprovalHandlerTests +{ + private GetApprenticeshipApprovalHandlerTestsFixture _fixture; + + [SetUp] + public void Arrange() + { + _fixture = new GetApprenticeshipApprovalHandlerTestsFixture(); + } + + [Test] + public async Task Handle_ThenShouldReturn_CoreValues() + { + var result = await _fixture.Handle(); + + result.Should().NotBeNull(); + result.ApprenticeshipId.Should().Be(_fixture.ApprenticeshipId); + result.ApprovalRequestId.Should().Be(_fixture.ApprovalRequestId); + result.AccountLegalEntityId.Should().Be(_fixture.AccountLegalEntityId); + result.AccountLegalEntityName.Should().Be(_fixture.AccountLegalEntity.Name); + result.AccountId.Should().Be(_fixture.AccountLegalEntity.AccountId); + result.Name.Should().Be($"{_fixture.Apprenticeship.FirstName} {_fixture.Apprenticeship.LastName}"); + result.ULN.Should().Be(_fixture.Apprenticeship.Uln); + result.StartDate.Should().Be(_fixture.Apprenticeship.StartDate); + result.CourseCode.Should().Be(_fixture.Apprenticeship.CourseCode); + result.CourseName.Should().Be(_fixture.Apprenticeship.CourseName); + result.ProviderName.Should().Be(_fixture.Provider.Name); + result.UKPRN.Should().Be(_fixture.Provider.UkPrn); + result.ApprovalRequestStatus.Should().Be(_fixture.ApprovalRequest.Status); + result.Items.Should().HaveCount(_fixture.ApprovalFieldRequests.Count); + } + + [Test] + public async Task Handle_ForContinuationApprenticeship_ThenShouldReturn_OriginalStartDate() + { + _fixture.Apprenticeship.ContinuationOfId = 123; + _fixture.Apprenticeship.OriginalStartDate = DateTime.Now.AddYears(-2); + var result = await _fixture.Handle(); + + result.Should().NotBeNull(); + result.StartDate.Should().Be(_fixture.Apprenticeship.OriginalStartDate); + } + + [Test] + public async Task Handle_ThenShouldReturn_MappedItems() + { + var result = await _fixture.Handle(); + + result.Should().NotBeNull(); + result.Items.Should().BeEquivalentTo(_fixture.ApprovalFieldRequests + .Select(x=> new GetApprenticeshipApprovalQueryResult.ChangeItem { FieldName = x.Field, NewValue = x.New, OldValue = x.Old, EffectiveFromDate = x.EffectiveFromDate } )); + } + + [Test] + public async Task WhenApprovalRequestNotFound_ThenShouldReturn_Null() + { + _fixture.Request = new GetApprenticeshipApprovalQuery(_fixture.ApprenticeshipId, Guid.NewGuid()); + var result = await _fixture.Handle(); + + result.Should().BeNull(); + } + + [Test] + public async Task WhenApprenticeshipIdDoesNotMatch_ThenShouldReturn_Null() + { + _fixture.Request = new GetApprenticeshipApprovalQuery(_fixture.ApprenticeshipId + 1, _fixture.ApprovalRequestId); + var result = await _fixture.Handle(); + + result.Should().BeNull(); + } + + public class GetApprenticeshipApprovalHandlerTestsFixture + { + public long ApprenticeshipId { get; private set; } + public Guid ApprovalRequestId { get; private set; } = Guid.NewGuid(); + public long AccountLegalEntityId { get; private set; } + public ApprovalRequest ApprovalRequest { get; private set; } + public List ApprovalFieldRequests { get; private set; } + public Apprenticeship Apprenticeship { get; private set; } + public Cohort Cohort { get; private set; } + public Provider Provider { get; private set; } + public AccountLegalEntity AccountLegalEntity { get; private set; } + public Course Course { get; private set; } + + public GetApprenticeshipApprovalQuery Request; + public GetApprenticeshipApprovalQueryResult Result; + + private readonly GetApprenticeshipApprovalQueryHandler _handler; + private readonly ProviderCommitmentsDbContext _db; + private Fixture _autoFixture; + + public GetApprenticeshipApprovalHandlerTestsFixture() + { + _db = new ProviderCommitmentsDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString(), b => b.EnableNullChecks(false)).Options); + SeedData(); + Request = new GetApprenticeshipApprovalQuery(ApprenticeshipId, ApprovalRequestId); + + + _handler = new GetApprenticeshipApprovalQueryHandler(new Lazy(() => _db)); + } + + public async Task Handle() + { + Result = await _handler.Handle(Request, new CancellationToken()); + return Result; + } + + private GetApprenticeshipApprovalHandlerTestsFixture SeedData() + { + _autoFixture = new Fixture(); + _autoFixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + ApprenticeshipId = _autoFixture.Create(); + + Provider = new Provider + { + UkPrn = _autoFixture.Create(), + Name = _autoFixture.Create() + }; + + var account = new Account(1, "", "", "", DateTime.UtcNow); + + AccountLegalEntity = new AccountLegalEntity(account, + AccountLegalEntityId, + 0, + "", + publicHashedId: _autoFixture.Create(), + _autoFixture.Create(), + OrganisationType.PublicBodies, + "", + DateTime.UtcNow); + + Cohort = new Cohort + { + Id = _autoFixture.CreateMany().Last(), + AccountLegalEntity = AccountLegalEntity, + EmployerAccountId = _autoFixture.Create(), + ProviderId = Provider.UkPrn, + Provider = Provider, + ApprenticeshipEmployerTypeOnApproval = ApprenticeshipEmployerType.Levy + }; + + var courseCode = _autoFixture.Create(); + + Apprenticeship = new Apprenticeship + { + Id = ApprenticeshipId, + CommitmentId = Cohort.Id, + Cohort = Cohort, + AgreedOn = _autoFixture.Create(), + CourseCode = courseCode, + StandardUId = "ST0001_1.0", + TrainingCourseVersion = "1.0", + CourseName = _autoFixture.Create(), + FirstName = _autoFixture.Create(), + LastName = _autoFixture.Create(), + DateOfBirth = _autoFixture.Create(), + StartDate = _autoFixture.Create(), + EndDate = _autoFixture.Create(), + Uln = _autoFixture.Create(), + PaymentStatus = _autoFixture.Create(), + EmployerRef = _autoFixture.Create(), + MadeRedundant = _autoFixture.Create(), + FlexibleEmployment = _autoFixture.Create(), + PriorLearning = _autoFixture.Create(), + TrainingTotalHours = _autoFixture.Create(), + EmployerHasEditedCost = _autoFixture.Create(), + StopDate = _autoFixture.Create(), + WithdrawnReasonCode = _autoFixture.Create(), + PaymentFreezeDate = DateTime.UtcNow.Date.AddDays(-7), + FreezePaymentsReason = FreezePaymentsReason.LearnerOnBreak, + }; + + switch (Apprenticeship.PaymentStatus) + { + case PaymentStatus.Withdrawn: + Apprenticeship.StopDate = _autoFixture.Create(); + break; + case PaymentStatus.Paused: + Apprenticeship.PauseDate = _autoFixture.Create(); + break; + case PaymentStatus.Completed: + Apprenticeship.CompletionDate = _autoFixture.Create(); + break; + } + + _db.Apprenticeships.Add(Apprenticeship); + + Course = _autoFixture.Build().With(c => c.LarsCode, courseCode).Create(); + _db.Courses.Add(Course); + + ApprovalFieldRequests = _autoFixture.Build() + .With(afr => afr.ApprovalRequestId, ApprovalRequestId) + .CreateMany(3).ToList(); + + ApprovalRequest = _autoFixture.Build() + .With(ar => ar.Id, ApprovalRequestId) + .With(ar => ar.ApprenticeshipId, ApprenticeshipId) + .With(ar => ar.Status, CocApprovalResultStatus.Pending) + .With(ar => ar.Items, ApprovalFieldRequests) + .Create(); + _db.ApprovalRequests.Add(ApprovalRequest); + + _db.SaveChanges(); + + return this; + } + } +} diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalValidationTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalValidationTests.cs new file mode 100644 index 000000000..37f000637 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalValidationTests.cs @@ -0,0 +1,34 @@ +using SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; + +namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Queries.GetApprenticeshipApproval; + +[TestFixture] +public class GetApprenticeshipApprovalValidationTests +{ + [TestCase(-1, false)] + [TestCase(0, false)] + [TestCase(1, true)] + public void Validate_WithSpecifiedAppprenticeshipId(int apprenticeshipId, bool expectedIsValid) + { + var validator = new GetApprenticeshipApprovalQueryValidator(); + var validationResults = validator.Validate(new GetApprenticeshipApprovalQuery(apprenticeshipId, Guid.NewGuid())); + validationResults.IsValid.Should().Be(expectedIsValid); + } + + [Test] + public void Validate_WithNonEmptyApprovalRequestId_Then_Should_Pass() + { + var validator = new GetApprenticeshipApprovalQueryValidator(); + var validationResults = validator.Validate(new GetApprenticeshipApprovalQuery(111, Guid.NewGuid())); + validationResults.IsValid.Should().Be(true); + } + + [Test] + public void Validate_WithEmptyApprovalRequestId_Then_Should_Fail() + { + var validator = new GetApprenticeshipApprovalQueryValidator(); + var validationResults = validator.Validate(new GetApprenticeshipApprovalQuery(111, Guid.Empty)); + validationResults.IsValid.Should().Be(false); + } + +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipUpdate/GetApprenticeshipUpdateValidationTests.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipUpdate/GetApprenticeshipUpdateValidationTests.cs index 72a4236e4..dfd352c59 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipUpdate/GetApprenticeshipUpdateValidationTests.cs +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2.UnitTests/Application/Queries/GetApprenticeshipUpdate/GetApprenticeshipUpdateValidationTests.cs @@ -1,30 +1,29 @@ using SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipUpdate; using SFA.DAS.CommitmentsV2.Types; -namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Queries.GetApprenticeshipUpdate +namespace SFA.DAS.CommitmentsV2.UnitTests.Application.Queries.GetApprenticeshipUpdate; + +[TestFixture] +public class GetApprenticeshipUpdateValidationTests { - [TestFixture] - public class GetApprenticeshipUpdateValidationTests + [TestCase(-1, false)] + [TestCase(0, false)] + [TestCase(1, true)] + public void Validate_WithSpecifiedAppprenticeshipId(int apprenticeshipId, bool expectedIsValid) { - [TestCase(-1, false)] - [TestCase(0, false)] - [TestCase(1, true)] - public void Validate_WithSpecifiedAppprenticeshipId(int apprenticeshipId, bool expectedIsValid) - { - var validator = new GetApprenticeshipUpdateQueryValidator(); - var validationResults = validator.Validate(new GetApprenticeshipUpdateQuery(apprenticeshipId, null)); - Assert.That(validationResults.IsValid, Is.EqualTo(expectedIsValid)); - } + var validator = new GetApprenticeshipUpdateQueryValidator(); + var validationResults = validator.Validate(new GetApprenticeshipUpdateQuery(apprenticeshipId, null)); + Assert.That(validationResults.IsValid, Is.EqualTo(expectedIsValid)); + } - [TestCase(null, true)] - [TestCase(ApprenticeshipUpdateStatus.Approved, true)] - [TestCase(ApprenticeshipUpdateStatus.Deleted, true)] - [TestCase(ApprenticeshipUpdateStatus.Superceded, true)] - public void DontValidate_ApprenticeshipUpdateStatus(ApprenticeshipUpdateStatus? status, bool expectedIsValid) - { - var validator = new GetApprenticeshipUpdateQueryValidator(); - var validationResults = validator.Validate(new GetApprenticeshipUpdateQuery(1, status)); - Assert.That(validationResults.IsValid, Is.EqualTo(expectedIsValid)); - } + [TestCase(null, true)] + [TestCase(ApprenticeshipUpdateStatus.Approved, true)] + [TestCase(ApprenticeshipUpdateStatus.Deleted, true)] + [TestCase(ApprenticeshipUpdateStatus.Superceded, true)] + public void DontValidate_ApprenticeshipUpdateStatus(ApprenticeshipUpdateStatus? status, bool expectedIsValid) + { + var validator = new GetApprenticeshipUpdateQueryValidator(); + var validationResults = validator.Validate(new GetApprenticeshipUpdateQuery(1, status)); + Assert.That(validationResults.IsValid, Is.EqualTo(expectedIsValid)); } -} +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQuery.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQuery.cs new file mode 100644 index 000000000..9f08d52db --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQuery.cs @@ -0,0 +1,14 @@ +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; + +public class GetApprenticeshipApprovalQuery : IRequest +{ + public long ApprenticeshipId { get; } + + public Guid ApprovalRequestId { get; } + + public GetApprenticeshipApprovalQuery(long apprenticeshipId, Guid approvalRequestId) + { + ApprenticeshipId = apprenticeshipId; + ApprovalRequestId = approvalRequestId; + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryHandler.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryHandler.cs new file mode 100644 index 000000000..fca5fe30d --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryHandler.cs @@ -0,0 +1,52 @@ +using SFA.DAS.CommitmentsV2.Data; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; + +public class GetApprenticeshipApprovalQueryHandler(Lazy dbContext) : IRequestHandler +{ + public async Task Handle(GetApprenticeshipApprovalQuery request, CancellationToken cancellationToken) + { + var approvalRequest = await dbContext.Value.ApprovalRequests + .Include(x => x.Items) + .FirstOrDefaultAsync(x => x.Id == request.ApprovalRequestId, cancellationToken); + + if (approvalRequest == null) + { + return null; + } + + if (approvalRequest.ApprenticeshipId != request.ApprenticeshipId) + { + return null; + } + + var apprenticeship = await dbContext.Value.Apprenticeships + .Include(x => x.Cohort).ThenInclude(x => x.Provider) + .Include(a => a.Cohort).ThenInclude(c => c.AccountLegalEntity) + .FirstOrDefaultAsync(x => x.Id == request.ApprenticeshipId, cancellationToken); + + return new GetApprenticeshipApprovalQueryResult + { + ApprenticeshipId = apprenticeship.Id, + Name = $"{apprenticeship.FirstName} {apprenticeship.LastName}", + ULN = apprenticeship.Uln, + UKPRN = apprenticeship.Cohort.Provider.UkPrn, + ProviderName = apprenticeship.Cohort.Provider.Name, + AccountLegalEntityId = apprenticeship.Cohort.AccountLegalEntity.Id, + AccountLegalEntityName = apprenticeship.Cohort.AccountLegalEntity.Name, + AccountId = apprenticeship.Cohort.AccountLegalEntity.AccountId, + StartDate = apprenticeship.OriginalStartDate ?? apprenticeship.StartDate, + CourseCode = apprenticeship.CourseCode, + CourseName = apprenticeship.CourseName, + ApprovalRequestStatus = approvalRequest.Status, + ApprovalRequestId = approvalRequest.Id, + Items = approvalRequest.Items.Select(i => new GetApprenticeshipApprovalQueryResult.ChangeItem + { + FieldName = i.Field, + OldValue = i.Old, + NewValue = i.New, + EffectiveFromDate = i.EffectiveFromDate + }).ToList() + }; + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryResult.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryResult.cs new file mode 100644 index 000000000..17cd66ac9 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryResult.cs @@ -0,0 +1,29 @@ +using SFA.DAS.CommitmentsV2.Models; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; + +public class GetApprenticeshipApprovalQueryResult +{ + public long ApprenticeshipId { get; set; } + public Guid ApprovalRequestId { get; set; } + public CocApprovalResultStatus? ApprovalRequestStatus { get; set; } + public virtual ICollection Items { get; set; } + public string Name { get; set; } + public string ULN { get; set; } + public DateTime? StartDate { get; set; } + public string CourseCode { get; set; } + public string CourseName { get; set; } + public string ProviderName { get; set; } + public long UKPRN { get; set; } + public string AccountLegalEntityName { get; set; } + public long AccountLegalEntityId { get; set; } + public long AccountId { get; set; } + + public class ChangeItem + { + public string FieldName { get; set; } + public string OldValue { get; set; } + public string NewValue { get; set; } + public DateTime? EffectiveFromDate { get; set; } + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryValidator.cs b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryValidator.cs new file mode 100644 index 000000000..f38b445c7 --- /dev/null +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/Application/Queries/GetApprenticeshipApproval/GetApprenticeshipApprovalQueryValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace SFA.DAS.CommitmentsV2.Application.Queries.GetApprenticeshipApproval; + +public class GetApprenticeshipApprovalQueryValidator : AbstractValidator +{ + public GetApprenticeshipApprovalQueryValidator() + { + RuleFor(q => q.ApprenticeshipId).GreaterThan(0); + RuleFor(q => q.ApprovalRequestId).NotEmpty(); + } +} \ No newline at end of file diff --git a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/SFA.DAS.CommitmentsV2.csproj b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/SFA.DAS.CommitmentsV2.csproj index 103889a95..2d377e4fb 100644 --- a/src/CommitmentsV2/SFA.DAS.CommitmentsV2/SFA.DAS.CommitmentsV2.csproj +++ b/src/CommitmentsV2/SFA.DAS.CommitmentsV2/SFA.DAS.CommitmentsV2.csproj @@ -65,10 +65,5 @@ - - - - -