diff --git a/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequest.cs b/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequest.cs index a92bc4a4d5..01fd5a57b8 100644 --- a/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequest.cs +++ b/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequest.cs @@ -17,5 +17,6 @@ public class TransferRequest public string ApprovedOrRejectedByUserName { get; set; } public string ApprovedOrRejectedByUserEmail { get; set; } public DateTime? ApprovedOrRejectedOn { get; set; } + public int FundingCap { get; set; } } } diff --git a/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequestSummary.cs b/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequestSummary.cs index e5e6c7a463..b2c25c971c 100644 --- a/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequestSummary.cs +++ b/src/SFA.DAS.Commitments.Api.Types/Commitment/TransferRequestSummary.cs @@ -15,5 +15,6 @@ public class TransferRequestSummary public DateTime? ApprovedOrRejectedOn { get; set; } public TransferType TransferType { get; set; } public DateTime CreatedOn { get; set; } + public int FundingCap { get; set; } } } diff --git a/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequest.cs b/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequest.cs index dcb1ca8783..e5d01e53e2 100644 --- a/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequest.cs +++ b/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequest.cs @@ -49,7 +49,7 @@ public void ThenMappingTheSourceObjectReturnsTheApiObjectValuesCorrectly() result.TrainingList.Count.Should().Be(_courses.Count); result.TrainingList[0].ApprenticeshipCount.Should().Be(_courses[0].ApprenticeshipCount); result.TrainingList[0].CourseTitle.Should().Be(_courses[0].CourseTitle); - + result.FundingCap.Should().Be(_source.FundingCap); } [Test] diff --git a/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequestSummary.cs b/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequestSummary.cs index 36be2a4335..f894b28e6f 100644 --- a/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequestSummary.cs +++ b/src/SFA.DAS.Commitments.Api.UnitTests/Orchestrators/Mappers/WhenMappingATransferRequestSummary.cs @@ -63,6 +63,7 @@ public void ThenMappingToNewObjectMatches() result.ApprovedOrRejectedByUserEmail.Should().Be(_source[0].ApprovedOrRejectedByUserEmail); result.ApprovedOrRejectedOn.Should().Be(_source[0].ApprovedOrRejectedOn); result.CreatedOn.Should().Be(_source[0].CreatedOn); + result.FundingCap.Should().Be(_source[0].FundingCap); } } } diff --git a/src/SFA.DAS.Commitments.Api/Orchestrators/Mappers/TransferRequestMapper.cs b/src/SFA.DAS.Commitments.Api/Orchestrators/Mappers/TransferRequestMapper.cs index 3d36ff5e3d..9adc5c6fb0 100644 --- a/src/SFA.DAS.Commitments.Api/Orchestrators/Mappers/TransferRequestMapper.cs +++ b/src/SFA.DAS.Commitments.Api/Orchestrators/Mappers/TransferRequestMapper.cs @@ -26,6 +26,7 @@ public TransferRequestSummary MapFrom(Domain.Entities.TransferRequestSummary sou HashedCohortRef = _hashingService.HashValue(source.CommitmentId), HashedSendingEmployerAccountId = _hashingService.HashValue(source.SendingEmployerAccountId), TransferCost = source.TransferCost, + FundingCap = source.FundingCap, Status = (TransferApprovalStatus) source.Status, ApprovedOrRejectedByUserName = source.ApprovedOrRejectedByUserName, ApprovedOrRejectedByUserEmail = source.ApprovedOrRejectedByUserEmail, @@ -54,11 +55,12 @@ public TransferRequest MapFrom(Domain.Entities.TransferRequest source) TransferSenderName = source.TransferSenderName, LegalEntityName = source.LegalEntityName, TransferCost = source.TransferCost, - TrainingList = JsonConvert.DeserializeObject>(source.TrainingCourses), + TrainingList = JsonConvert.DeserializeObject>(source.TrainingCourses), Status = (TransferApprovalStatus)source.Status, ApprovedOrRejectedByUserName = source.ApprovedOrRejectedByUserName, ApprovedOrRejectedByUserEmail = source.ApprovedOrRejectedByUserEmail, - ApprovedOrRejectedOn = source.ApprovedOrRejectedOn + ApprovedOrRejectedOn = source.ApprovedOrRejectedOn, + FundingCap = source.FundingCap }; } } diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/ApproveTransferRequest/WhenApprovingATransferRequest.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/ApproveTransferRequest/WhenApprovingATransferRequest.cs index 721b436bc7..8884adf993 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/ApproveTransferRequest/WhenApprovingATransferRequest.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/ApproveTransferRequest/WhenApprovingATransferRequest.cs @@ -10,6 +10,7 @@ using SFA.DAS.Commitments.Application.Commands.ApproveTransferRequest; using SFA.DAS.Commitments.Application.Commands.SetPaymentOrder; using SFA.DAS.Commitments.Application.Exceptions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Application.Interfaces.ApprenticeshipEvents; using SFA.DAS.Commitments.Application.Rules; using SFA.DAS.Commitments.Domain.Data; @@ -66,7 +67,8 @@ public void SetUp() _sut = new ApproveTransferRequestCommandHandler(_validator, _commitmentRepository.Object, _apprenticeshipRepository.Object, _overlapRules.Object, _currentDateTime.Object, _apprenticeshipEventsList.Object, _apprenticeshipEventsPublisher.Object, _mediator.Object, - _messagePublisher.Object, _historyRepository.Object, Mock.Of()); + _messagePublisher.Object, _historyRepository.Object, Mock.Of(), + Mock.Of()); } [Test] diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ApproveCohortTestBase.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ApproveCohortTestBase.cs index 8308c69b3a..698e5ff029 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ApproveCohortTestBase.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ApproveCohortTestBase.cs @@ -6,6 +6,7 @@ using Moq; using NUnit.Framework; using SFA.DAS.Commitments.Application.Exceptions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Application.Interfaces.ApprenticeshipEvents; using SFA.DAS.Commitments.Application.Rules; using SFA.DAS.Commitments.Domain; @@ -30,6 +31,7 @@ public abstract class ApproveCohortTestBase where T : IAsyncRequest protected Mock ApprenticeshipEventsPublisher; protected Mock Mediator; protected Mock MessagePublisher; + protected Mock ApprenticeshipInfoService; protected AsyncRequestHandler Target; protected T Command; protected Commitment Commitment; diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohort.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohort.cs index c09be5b312..db135fac80 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohort.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohort.cs @@ -8,6 +8,7 @@ using SFA.DAS.Commitments.Application.Commands.CohortApproval.EmployerApproveCohort; using SFA.DAS.Commitments.Application.Commands.SetPaymentOrder; using SFA.DAS.Commitments.Application.Exceptions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Domain; using SFA.DAS.Commitments.Domain.Entities; using SFA.DAS.Commitments.Domain.Entities.History; @@ -29,7 +30,18 @@ public void SetUp() CommitmentRepository.Setup(x => x.GetCommitmentById(Command.CommitmentId)).ReturnsAsync(Commitment); SetupSuccessfulOverlapCheck(); - Target = new EmployerApproveCohortCommandHandler(Validator, CommitmentRepository.Object, ApprenticeshipRepository.Object, OverlapRules.Object, CurrentDateTime.Object, HistoryRepository.Object, ApprenticeshipEventsList.Object, ApprenticeshipEventsPublisher.Object, Mediator.Object, MessagePublisher.Object, Mock.Of()); + Target = new EmployerApproveCohortCommandHandler(Validator, + CommitmentRepository.Object, + ApprenticeshipRepository.Object, + OverlapRules.Object, + CurrentDateTime.Object, + HistoryRepository.Object, + ApprenticeshipEventsList.Object, + ApprenticeshipEventsPublisher.Object, + Mediator.Object, + MessagePublisher.Object, + Mock.Of(), + Mock.Of()); } [Test] diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohortWhichHasATransferSender.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohortWhichHasATransferSender.cs index 90c6d2a719..fa71a44d48 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohortWhichHasATransferSender.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/EmployerApproveCohort/WhenAnEmployerApprovesACohortWhichHasATransferSender.cs @@ -5,8 +5,10 @@ using NUnit.Framework; using SFA.DAS.Commitments.Application.Commands.CohortApproval.EmployerApproveCohort; using SFA.DAS.Commitments.Application.Commands.SetPaymentOrder; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Domain; using SFA.DAS.Commitments.Domain.Entities; +using SFA.DAS.Commitments.Domain.Entities.TrainingProgramme; using SFA.DAS.Commitments.Domain.Interfaces; using SFA.DAS.Commitments.Events; using SFA.DAS.Messaging.Interfaces; @@ -27,7 +29,7 @@ public void SetUp() SetUpCommonMocks(); Commitment = CreateCommitment(Command.CommitmentId, Command.Caller.Id, 234587, 1000, "Nice Company"); CommitmentRepository.Setup(x => x.GetCommitmentById(Command.CommitmentId)).ReturnsAsync(Commitment); - CommitmentRepository.Setup(x => x.StartTransferRequestApproval(It.IsAny(), It.IsAny(), + CommitmentRepository.Setup(x => x.StartTransferRequestApproval(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).ReturnsAsync(_transferRequestId); Commitment.Apprenticeships.ForEach(x => x.AgreementStatus = AgreementStatus.ProviderAgreed); @@ -36,7 +38,28 @@ public void SetUp() _messagePublisher = new Mock(); - Target = new EmployerApproveCohortCommandHandler(Validator, CommitmentRepository.Object, ApprenticeshipRepository.Object, OverlapRules.Object, CurrentDateTime.Object, HistoryRepository.Object, ApprenticeshipEventsList.Object, ApprenticeshipEventsPublisher.Object, Mediator.Object, _messagePublisher.Object, Mock.Of()); + ApprenticeshipInfoService = new Mock(); + ApprenticeshipInfoService.Setup(x => x.GetTrainingProgram(It.IsAny())) + .ReturnsAsync(new Standard + { + FundingPeriods = new List + { + new FundingPeriod {FundingCap = 1000} + } + }); + + Target = new EmployerApproveCohortCommandHandler(Validator, + CommitmentRepository.Object, + ApprenticeshipRepository.Object, + OverlapRules.Object, + CurrentDateTime.Object, + HistoryRepository.Object, + ApprenticeshipEventsList.Object, + ApprenticeshipEventsPublisher.Object, + Mediator.Object, + _messagePublisher.Object, + Mock.Of(), + ApprenticeshipInfoService.Object); } [Test] @@ -47,8 +70,7 @@ public async Task ThenIfTheProviderHasAlreadyApprovedAMessageIsPublishedToTransf _messagePublisher.Verify(x => x.PublishAsync(It.Is(y => y.TransferRequestId == _transferRequestId && y.ReceivingEmployerAccountId == Commitment.EmployerAccountId && - y.CommitmentId == Commitment.Id && y.SendingEmployerAccountId == Commitment.TransferSenderId && - y.TransferCost == Commitment.Apprenticeships.Sum(a => a.Cost ?? 0))), Times.Once); + y.CommitmentId == Commitment.Id && y.SendingEmployerAccountId == Commitment.TransferSenderId)), Times.Once); } [Test] @@ -80,15 +102,12 @@ public async Task ThenIfTheProviderHasAlreadyApprovedDoNotSetAStartDateForTheApp [Test] public async Task ThenEnsureTheStartATransferRequestInRepositoryIsCalled() { - var expectedTotal = (decimal)Commitment.Apprenticeships.Sum(i => i.Cost); - await Target.Handle(Command); CommitmentRepository.Verify(x => x.StartTransferRequestApproval(Commitment.Id, - expectedTotal, It.Is>(p => + It.IsAny(), It.IsAny(), It.Is>(p => p.Count == 1 && p[0].ApprenticeshipCount == 2 && p[0].CourseTitle == Commitment.Apprenticeships[0].TrainingName))); - } [Test] @@ -104,6 +123,5 @@ public async Task ThenIfTheProviderHasAlreadyApprovedThenEventsEmittedShouldIndi null ), Times.Exactly(Commitment.Apprenticeships.Count)); } - } } diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohort.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohort.cs index 1dfbae8aa2..32a924f06e 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohort.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohort.cs @@ -8,6 +8,7 @@ using SFA.DAS.Commitments.Application.Commands.CohortApproval.ProiderApproveCohort; using SFA.DAS.Commitments.Application.Commands.SetPaymentOrder; using SFA.DAS.Commitments.Application.Exceptions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Domain; using SFA.DAS.Commitments.Domain.Entities; using SFA.DAS.Commitments.Domain.Entities.History; @@ -32,7 +33,18 @@ public void SetUp() CommitmentRepository.Setup(x => x.GetCommitmentById(Command.CommitmentId)).ReturnsAsync(Commitment); SetupSuccessfulOverlapCheck(); - Target = new ProviderApproveCohortCommandHandler(Validator, CommitmentRepository.Object, ApprenticeshipRepository.Object, OverlapRules.Object, CurrentDateTime.Object, HistoryRepository.Object, ApprenticeshipEventsList.Object, ApprenticeshipEventsPublisher.Object, Mediator.Object, MessagePublisher.Object, Mock.Of()); + Target = new ProviderApproveCohortCommandHandler(Validator, + CommitmentRepository.Object, + ApprenticeshipRepository.Object, + OverlapRules.Object, + CurrentDateTime.Object, + HistoryRepository.Object, + ApprenticeshipEventsList.Object, + ApprenticeshipEventsPublisher.Object, + Mediator.Object, + MessagePublisher.Object, + Mock.Of(), + Mock.Of()); } [Test] diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohortWhichHasATransferSender.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohortWhichHasATransferSender.cs index 26a26d5d09..18f85382aa 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohortWhichHasATransferSender.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/CohortApproval/ProviderApproveCohort/WhenAnProviderApprovesACohortWhichHasATransferSender.cs @@ -6,10 +6,13 @@ using NUnit.Framework; using SFA.DAS.Commitments.Application.Commands.CohortApproval.ProiderApproveCohort; using SFA.DAS.Commitments.Application.Commands.SetPaymentOrder; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Domain; using SFA.DAS.Commitments.Domain.Entities; +using SFA.DAS.Commitments.Domain.Entities.TrainingProgramme; using SFA.DAS.Commitments.Domain.Interfaces; using SFA.DAS.Commitments.Events; +using SFA.DAS.Commitments.Infrastructure.Services; namespace SFA.DAS.Commitments.Application.UnitTests.Commands.CohortApproval.ProviderApproveCohort { @@ -28,26 +31,45 @@ public void SetUp() Commitment = CreateCommitment(Command.CommitmentId, 11234, Command.Caller.Id, 1000, "Nice Company"); Commitment.EditStatus = EditStatus.ProviderOnly; Commitment.Apprenticeships.ForEach(x => x.AgreementStatus = AgreementStatus.EmployerAgreed); - CommitmentRepository.Setup(x => x.StartTransferRequestApproval(It.IsAny(), It.IsAny(), + CommitmentRepository.Setup(x => x.StartTransferRequestApproval(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).ReturnsAsync(_transferRequestId); CommitmentRepository.Setup(x => x.GetCommitmentById(Command.CommitmentId)).ReturnsAsync(Commitment); SetupSuccessfulOverlapCheck(); - Target = new ProviderApproveCohortCommandHandler(Validator, CommitmentRepository.Object, ApprenticeshipRepository.Object, OverlapRules.Object, CurrentDateTime.Object, HistoryRepository.Object, ApprenticeshipEventsList.Object, ApprenticeshipEventsPublisher.Object, Mediator.Object, MessagePublisher.Object, Mock.Of()); + ApprenticeshipInfoService = new Mock(); + ApprenticeshipInfoService.Setup(x => x.GetTrainingProgram(It.IsAny())) + .ReturnsAsync(new Standard + { + FundingPeriods = new List + { + new FundingPeriod {FundingCap = 1000} + } + }); + + Target = new ProviderApproveCohortCommandHandler(Validator, + CommitmentRepository.Object, + ApprenticeshipRepository.Object, + OverlapRules.Object, + CurrentDateTime.Object, + HistoryRepository.Object, + ApprenticeshipEventsList.Object, + ApprenticeshipEventsPublisher.Object, + Mediator.Object, + MessagePublisher.Object, + Mock.Of(), + ApprenticeshipInfoService.Object); } [Test] public async Task ThenIfTheEmployerHasAlreadyApprovedAMessageIsPublishedToTransferSender() { - await Target.Handle(Command); MessagePublisher.Verify(x => x.PublishAsync(It.Is(y => y.TransferRequestId == _transferRequestId && y.ReceivingEmployerAccountId == Commitment.EmployerAccountId && - y.CommitmentId == Commitment.Id && y.SendingEmployerAccountId == Commitment.TransferSenderId && - y.TransferCost == Commitment.Apprenticeships.Sum(a => a.Cost ?? 0))), Times.Once); + y.CommitmentId == Commitment.Id && y.SendingEmployerAccountId == Commitment.TransferSenderId)), Times.Once); } [Test] @@ -81,12 +103,10 @@ public async Task ThenIfTheEmployerHasAlreadyApprovedDoNotSetAStartDateForTheApp [Test] public async Task ThenEnsureTheStartATransferRequestInRepositoryIsCalled() { - var expectedTotal = (decimal)Commitment.Apprenticeships.Sum(i => i.Cost); - await Target.Handle(Command); CommitmentRepository.Verify(x => x.StartTransferRequestApproval(Commitment.Id, - expectedTotal, It.Is>(p => + It.IsAny(), It.IsAny(), It.Is>(p => p.Count == 1 && p[0].ApprenticeshipCount == 2 && p[0].CourseTitle == Commitment.Apprenticeships[0].TrainingName))); diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/RejectTransferRequest/WhenRejectingATransferRequest.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/RejectTransferRequest/WhenRejectingATransferRequest.cs index 19fab198bb..d1de127dc6 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/Commands/RejectTransferRequest/WhenRejectingATransferRequest.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Commands/RejectTransferRequest/WhenRejectingATransferRequest.cs @@ -9,6 +9,7 @@ using NUnit.Framework; using SFA.DAS.Commitments.Application.Commands.RejectTransferRequest; using SFA.DAS.Commitments.Application.Exceptions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Application.Interfaces.ApprenticeshipEvents; using SFA.DAS.Commitments.Application.Rules; using SFA.DAS.Commitments.Domain.Data; @@ -71,7 +72,8 @@ public void SetUp() _sut = new RejectTransferRequestCommandHandler(_validator, _commitmentRepository.Object, _apprenticeshipRepository.Object, _overlapRules.Object, _currentDateTime.Object, _apprenticeshipEventsList.Object, _apprenticeshipEventsPublisher.Object, _mediator.Object, - _messagePublisher.Object, _historyRepository.Object, Mock.Of()); + _messagePublisher.Object, _historyRepository.Object, Mock.Of(), + Mock.Of()); } [Test] diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/DateTimeExtensions/WhenGettingFirstOfMonth.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/DateTimeExtensions/WhenGettingFirstOfMonth.cs new file mode 100644 index 0000000000..e3b108d623 --- /dev/null +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/DateTimeExtensions/WhenGettingFirstOfMonth.cs @@ -0,0 +1,17 @@ +using System; +using NUnit.Framework; +using SFA.DAS.Commitments.Application.Extensions; + +namespace SFA.DAS.Commitments.Application.UnitTests.Extensions.DateTimeExtensions +{ + [TestFixture] + public class WhenGettingFirstOfMonth + { + [TestCase("2018-06-15", "2018-06-01 00:00:00")] + [TestCase("2018-06-01 18:35:14", "2018-06-01 00:00:00")] + public void ThenTheFirstDayOfTheMonthIsReturned(DateTime value, DateTime expectResult) + { + Assert.AreEqual(expectResult, value.FirstOfMonth()); + } + } +} diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingFundingCap.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingFundingCap.cs new file mode 100644 index 0000000000..5e871c07a6 --- /dev/null +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingFundingCap.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using Moq; +using NUnit.Framework; +using SFA.DAS.Commitments.Application.Extensions; +using SFA.DAS.Commitments.Domain.Entities.TrainingProgramme; + +namespace SFA.DAS.Commitments.Application.UnitTests.Extensions.ITrainingProgrammeExtensions +{ + [TestFixture] + public class WhenDeterminingFundingCap + { + private Mock _course; + + [SetUp] + public void Arrange() + { + _course = new Mock(); + _course.Setup(x => x.EffectiveFrom).Returns(new DateTime(2018, 03, 01)); + _course.Setup(x => x.EffectiveTo).Returns(new DateTime(2019, 03, 31)); + _course.Setup(x => x.FundingPeriods).Returns(new List + { + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,03,01), + EffectiveTo = new DateTime(2018,07,31), + FundingCap = 5000 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,08,01), + EffectiveTo = null, + FundingCap = 2000 + } + }); + } + + [TestCase("2018-05-15", 5000, Description = "Within first funding band")] + [TestCase("2018-09-15", 2000, Description = "Within second funding band")] + [TestCase("2018-01-01", 0, Description = "Before course start")] + [TestCase("2019-06-01", 0, Description = "After course end")] + public void ThenTheApplicableFundingPeriodIsUsed(DateTime effectiveDate, int expectCap) + { + //Act + var result = _course.Object.FundingCapOn(effectiveDate); + + //Assert + Assert.AreEqual(expectCap, result); + } + + [TestCase("2020-01-01", 0, Description = "Before funding band")] + [TestCase("2020-02-01", 1, Description = "Before funding band but in same month")] + [TestCase("2020-02-20", 1, Description = "Within open-ended funding band first day in same month")] + [TestCase("2020-02-21", 1, Description = "Within open-ended funding band in same month")] + [TestCase("2020-03-01", 1, Description = "Within open-ended funding band")] + public void AndOnlyFundingPeriodHasEffectiveFromNotFirstOfMonthAndEffectiveToOpenEndedThenTheApplicableFundingPeriodIsUsed(DateTime effectiveDate, int expectCap) + { + var courseAndFundingBandStart = new DateTime(2020, 2, 20); + var courseAndFundingBandEnd = (DateTime?)null; + + _course.Setup(x => x.EffectiveFrom).Returns(courseAndFundingBandStart); + _course.Setup(x => x.EffectiveTo).Returns(courseAndFundingBandEnd); + + _course.Setup(x => x.FundingPeriods).Returns(new List + { + new FundingPeriod + { + EffectiveFrom = courseAndFundingBandStart, + EffectiveTo = courseAndFundingBandEnd, + FundingCap = 1 + } + }); + + //Act + var result = _course.Object.FundingCapOn(effectiveDate); + + //Assert + Assert.AreEqual(expectCap, result); + } + + [TestCase("2018-07-01", 1, Description = "Within first open-start funding band")] + [TestCase("2018-07-31", 1, Description = "At end of first open-start funding band")] + [TestCase("2018-08-01", 2, Description = "Start of second funding band")] + [TestCase("2020-01-01", 2, Description = "Within second open-ended funding band")] + public void AndFirstFundingPeriodHasNUllEffectiveFromThenTheApplicableFundingPeriodIsUsed(DateTime effectiveDate, int expectCap) + { + var courseAndFundingBandStart = (DateTime?)null; + var courseAndFundingBandEnd = (DateTime?)null; + + _course.Setup(x => x.EffectiveFrom).Returns(courseAndFundingBandStart); + _course.Setup(x => x.EffectiveTo).Returns(courseAndFundingBandEnd); + + _course.Setup(x => x.FundingPeriods).Returns(new List + { + new FundingPeriod + { + EffectiveFrom = courseAndFundingBandStart, + EffectiveTo = new DateTime(2018,07,31), + FundingCap = 1 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,08,01), + EffectiveTo = courseAndFundingBandEnd, + FundingCap = 2 + } + }); + + //Act + var result = _course.Object.FundingCapOn(effectiveDate); + + //Assert + Assert.AreEqual(expectCap, result); + } + + [TestCase("2018-07-31", 0, Description = "Before first funding band")] + [TestCase("2018-08-01", 1, Description = "Before first funding band but withing same month as first funding band")] + [TestCase("2018-08-15", 1, Description = "Within (only-day of) first funding band")] + [TestCase("2018-08-16", 2, Description = "Within (only-day of) second funding band")] + [TestCase("2018-08-17", 3, Description = "At start of third funding band")] + [TestCase("2018-08-26", 0, Description = "Beyond end of last funding band")] + public void AndMultipleBandsInMonthThenTheApplicableFundingPeriodIsUsed(DateTime effectiveDate, int expectCap) + { + var courseAndFundingBandStart = new DateTime(2018, 08, 15); + var courseAndFundingBandEnd = new DateTime(2018, 08, 25); + + _course.Setup(x => x.EffectiveFrom).Returns(courseAndFundingBandStart); + _course.Setup(x => x.EffectiveTo).Returns(courseAndFundingBandEnd); + + _course.Setup(x => x.FundingPeriods).Returns(new List + { + new FundingPeriod + { + EffectiveFrom = courseAndFundingBandStart, + EffectiveTo = new DateTime(2018,08,15), + FundingCap = 1 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,08,16), + EffectiveTo = new DateTime(2018,08,16), + FundingCap = 2 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,08,17), + EffectiveTo = courseAndFundingBandEnd, + FundingCap = 3 + } + }); + + //Act + var result = _course.Object.FundingCapOn(effectiveDate); + + //Assert + Assert.AreEqual(expectCap, result); + } + + [TestCase("2018-08-01", 1, Description = "Within first open-start funding band")] + [TestCase("2018-08-15", 1, Description = "At end of first open-start funding band")] + [TestCase("2018-08-16", 2, Description = "At start of second funding band (in same month as first)")] + [TestCase("2018-08-21", 3, Description = "Start of third funding band")] + [TestCase("2020-01-01", 3, Description = "Within third open-ended funding band")] + public void AndFirstFundingPeriodHasNullEffectiveFromAndMultipleBandsInMonthThenTheApplicableFundingPeriodIsUsed(DateTime effectiveDate, int expectCap) + { + var courseAndFundingBandStart = (DateTime?)null; + var courseAndFundingBandEnd = (DateTime?)null; + + _course.Setup(x => x.EffectiveFrom).Returns(courseAndFundingBandStart); + _course.Setup(x => x.EffectiveTo).Returns(courseAndFundingBandEnd); + + _course.Setup(x => x.FundingPeriods).Returns(new List + { + new FundingPeriod + { + EffectiveFrom = courseAndFundingBandStart, + EffectiveTo = new DateTime(2018,08,15), + FundingCap = 1 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,08,16), + EffectiveTo = new DateTime(2018,08,20), + FundingCap = 2 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018,08,21), + EffectiveTo = courseAndFundingBandEnd, + FundingCap = 3 + } + }); + + //Act + var result = _course.Object.FundingCapOn(effectiveDate); + + //Assert + Assert.AreEqual(expectCap, result); + } + + [Test] + public void IfThereAreNoFundingPeriodsThenCapShouldBeZero() + { + //Arrange + _course.Setup(x => x.FundingPeriods).Returns(new List()); + + //Act + var result = _course.Object.FundingCapOn(new DateTime(2018, 05, 15)); + + //Assert + Assert.AreEqual(0, result); + } + + [Test] + public void FundingPeriodsAreEffectiveUntilTheEndOfTheDay() + { + //Act + var result = _course.Object.FundingCapOn(new DateTime(2018, 7, 31, 23, 59, 59)); + + //Assert + Assert.AreEqual(5000, result); + } + } +} diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingTheStatusOfACourseOnSpecificDate.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingTheStatusOfACourseOnSpecificDate.cs new file mode 100644 index 0000000000..168df1da53 --- /dev/null +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingTheStatusOfACourseOnSpecificDate.cs @@ -0,0 +1,40 @@ +using System; +using Moq; +using NUnit.Framework; +using SFA.DAS.Commitments.Application.Extensions; +using SFA.DAS.Commitments.Domain.Entities.TrainingProgramme; + +namespace SFA.DAS.Commitments.Application.UnitTests.Extensions.ITrainingProgrammeExtensions +{ + [TestFixture] + public class WhenDeterminingTheStatusOfACourseOnSpecificDate + { + [TestCase("2016-01-01", "2016-12-01", "2016-06-01", TrainingProgrammeStatus.Active, Description = "Within date range")] + [TestCase("2016-01-01", "2016-12-01", "2016-01-01", TrainingProgrammeStatus.Active, Description = "Within date range (on start day)")] + [TestCase("2016-01-01", "2016-12-01", "2016-12-01", TrainingProgrammeStatus.Active, Description = "Within date range (on last day)")] + [TestCase("2016-01-15", "2016-12-15", "2016-01-01", TrainingProgrammeStatus.Active, Description = "Within date range - ignoring start day")] + //[TestCase("2016-01-15", "2016-12-15", "2016-12-30", TrainingProgrammeStatus.Active, Description = "Within date range - ignoring end day")] + [TestCase("2016-01-15", "2016-12-15", "2016-12-30", TrainingProgrammeStatus.Expired, Description = "Past date range (but in same month as courseEnd")] + [TestCase(null, "2016-12-01", "2016-06-01", TrainingProgrammeStatus.Active, Description = "Within date range with no defined course start date")] + [TestCase("2016-01-01", null, "2016-06-01", TrainingProgrammeStatus.Active, Description = "Withing date range, with no defined course end date")] + [TestCase(null, null, "2016-06-01", TrainingProgrammeStatus.Active, Description = "Within date range, with no defined course effective dates")] + [TestCase("2016-01-01", "2016-12-01", "2015-06-01", TrainingProgrammeStatus.Pending, Description = "Outside (before) date range")] + [TestCase("2016-01-01", "2016-12-01", "2015-12-31", TrainingProgrammeStatus.Pending, Description = "Outside (immediately before) date range")] + [TestCase("2016-01-01", "2016-12-01", "2017-06-01", TrainingProgrammeStatus.Expired, Description = "Outside (after) date range")] + [TestCase("2016-01-01", "2016-12-01", "2017-01-01", TrainingProgrammeStatus.Expired, Description = "Outside (immediately after) date range")] + [TestCase(null, "2016-12-01", "2017-06-01", TrainingProgrammeStatus.Expired, Description = "Outside (after) date range with no defined course start date")] + public void ThenTheCourseEffectiveDatesAreUsedToDetermineTheStatus(DateTime? courseStart, DateTime? courseEnd, DateTime effectiveDate, TrainingProgrammeStatus expectStatus) + { + //Arrange + var course = new Mock(); + course.SetupGet(x => x.EffectiveFrom).Returns(courseStart); + course.SetupGet(x => x.EffectiveTo).Returns(courseEnd); + + //Act + var result = course.Object.GetStatusOn(effectiveDate); + + //Assert + Assert.AreEqual(expectStatus, result); + } + } +} diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingWhetherACourseIsActive.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingWhetherACourseIsActive.cs new file mode 100644 index 0000000000..ad4d7688dc --- /dev/null +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Extensions/ITrainingProgrammeExtensions/WhenDeterminingWhetherACourseIsActive.cs @@ -0,0 +1,35 @@ +using System; +using Moq; +using NUnit.Framework; +using SFA.DAS.Commitments.Application.Extensions; +using SFA.DAS.Commitments.Domain.Entities.TrainingProgramme; + +namespace SFA.DAS.Commitments.Application.UnitTests.Extensions.ITrainingProgrammeExtensions +{ + [TestFixture] + public class WhenDeterminingWhetherACourseIsActive + { + [TestCase("2016-01-01", "2016-12-01", "2016-06-01", true, Description = "Within date range")] + [TestCase("2016-01-15", "2016-12-15", "2016-01-01", true, Description = "Within date range - ignoring start day")] + //[TestCase("2016-01-15", "2016-12-15", "2016-12-30", true, Description = "Within date range - ignoring end day")] + [TestCase("2016-01-15", "2016-12-15", "2016-12-30", false, Description = "After date range (but in same month as courseEnd")] + [TestCase(null, "2016-12-01", "2016-06-01", true, Description = "Within date range with no defined course start date")] + [TestCase("2016-01-01", null, "2016-06-01", true, Description = "Withing date range, with no defined course end date")] + [TestCase(null, null, "2016-06-01", true, Description = "Within date range, with no defined course effective dates")] + [TestCase("2016-01-01", "2016-12-01", "2015-06-01", false, Description = "Outside (before) date range")] + [TestCase("2016-01-01", "2016-12-01", "2017-06-01", false, Description = "Outside (after) date range")] + public void ThenIfWithinCourseEffectiveRangeThenIsActive(DateTime? courseStart, DateTime? courseEnd, DateTime effectiveDate, bool expectIsActive) + { + //Arrange + var course = new Mock(); + course.SetupGet(x => x.EffectiveFrom).Returns(courseStart); + course.SetupGet(x => x.EffectiveTo).Returns(courseEnd); + + //Act + var result = course.Object.IsActiveOn(effectiveDate); + + //Assert + Assert.AreEqual(expectIsActive, result); + } + } +} diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/SFA.DAS.Commitments.Application.UnitTests.csproj b/src/SFA.DAS.Commitments.Application.UnitTests/SFA.DAS.Commitments.Application.UnitTests.csproj index 381bf5f0c6..0e8d28d3ba 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/SFA.DAS.Commitments.Application.UnitTests.csproj +++ b/src/SFA.DAS.Commitments.Application.UnitTests/SFA.DAS.Commitments.Application.UnitTests.csproj @@ -206,6 +206,10 @@ + + + + @@ -246,6 +250,7 @@ + diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/Services/CohortApprovalService/WhenCreatingTransferRequest.cs b/src/SFA.DAS.Commitments.Application.UnitTests/Services/CohortApprovalService/WhenCreatingTransferRequest.cs new file mode 100644 index 0000000000..7c8264ea5e --- /dev/null +++ b/src/SFA.DAS.Commitments.Application.UnitTests/Services/CohortApprovalService/WhenCreatingTransferRequest.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MediatR; +using Moq; +using NUnit.Framework; +using SFA.DAS.Commitments.Application.Interfaces; +using SFA.DAS.Commitments.Application.Interfaces.ApprenticeshipEvents; +using SFA.DAS.Commitments.Application.Rules; +using SFA.DAS.Commitments.Domain.Data; +using SFA.DAS.Commitments.Domain.Entities; +using SFA.DAS.Commitments.Domain.Entities.TrainingProgramme; +using SFA.DAS.Commitments.Domain.Interfaces; +using SFA.DAS.Messaging.Interfaces; + +namespace SFA.DAS.Commitments.Application.UnitTests.Services.CohortApprovalService +{ + [TestFixture] + public class WhenCreatingTransferRequest + { + private Application.Services.CohortApprovalService _cohortApprovalService; + private Mock _commitmentRepository; + private Mock _apprenticeshipInfoService; + private FundingPeriod _fundingPeriod; + + private Commitment _commitment; + + [SetUp] + public void Arrange() + { + _commitmentRepository = new Mock(); + _commitmentRepository.Setup(x => + x.StartTransferRequestApproval( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>())) + .ReturnsAsync(1); + + _fundingPeriod = new FundingPeriod {FundingCap = 2000}; + + _apprenticeshipInfoService = new Mock(); + _apprenticeshipInfoService.Setup(x => x.GetTrainingProgram(It.IsAny())) + .ReturnsAsync(new Standard + { + FundingPeriods = new List + { + _fundingPeriod + } + }); + + _cohortApprovalService = new Application.Services.CohortApprovalService( + Mock.Of(), + Mock.Of(), + Mock.Of(), + _commitmentRepository.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + _apprenticeshipInfoService.Object + ); + + _commitment = new Commitment + { + TransferSenderId = 1, + Apprenticeships = new List + { + new Apprenticeship + { + TrainingCode = "TEST1", + StartDate = new DateTime(2018,9,1), + Cost = 1000 + }, + new Apprenticeship + { + TrainingCode = "TEST2", + StartDate = new DateTime(2018,9,1), + Cost = 2000 + } + } + }; + } + + [TestCase(1000, 2000, 5000, 3000, Description = "All costs under cap")] + [TestCase(1000, 2000, 2000, 3000, Description = "Costs equal to and below cap")] + [TestCase(1000, 2000, 1000, 2000, Description = "One cost above cap - capping is applied")] + [TestCase(3000, 5000, 2000, 4000, Description = "All costs above cap - capping is applied")] + public async Task ThenTheTotalCostShouldBeCalculatedCorrectly(decimal cost1, decimal cost2, int cap, decimal expectedTotal) + { + //Arrange + _commitment = new Commitment + { + TransferSenderId = 1, + Apprenticeships = new List + { + new Apprenticeship + { + StartDate = new DateTime(2018,9,1), + Cost = cost1 + }, + new Apprenticeship + { + StartDate = new DateTime(2018,9,1), + Cost = cost2 + } + } + }; + + _fundingPeriod.FundingCap = cap; + + //Act + await _cohortApprovalService.CreateTransferRequest( + TestHelper.Clone(_commitment), Mock.Of()); + + //test code sums all apprenticeship costs + _commitmentRepository.Verify(x => x.StartTransferRequestApproval( + It.IsAny(), + It.Is(cost => cost == expectedTotal), + It.IsAny(), + It.IsAny>() + )); + } + + [Test] + public async Task ThenTheTotalFundingCapShouldBeCalculatedCorrectly() + { + //Arrange + _apprenticeshipInfoService.Setup(x => x.GetTrainingProgram(It.Is(t => t == "TEST1"))) + .ReturnsAsync(new Standard + { + FundingPeriods = new List + { + new FundingPeriod + { + FundingCap = 2000 + } + } + }); + + _apprenticeshipInfoService.Setup(x => x.GetTrainingProgram(It.Is(t => t == "TEST2"))) + .ReturnsAsync(new Standard + { + FundingPeriods = new List + { + new FundingPeriod + { + EffectiveFrom = new DateTime(2018, 1, 1), + EffectiveTo = new DateTime(2018,6,30), + FundingCap = 3000 + }, + new FundingPeriod + { + EffectiveFrom = new DateTime(2018, 7, 1), + FundingCap = 4000 + } + } + }); + + _commitment = new Commitment + { + TransferSenderId = 1, + Apprenticeships = new List + { + new Apprenticeship + { + TrainingCode = "TEST1", + StartDate = new DateTime(2018,9,1), + Cost = 0 + }, + new Apprenticeship + { + TrainingCode = "TEST2", + StartDate = new DateTime(2018,1,1), + Cost = 0 + }, + new Apprenticeship + { + TrainingCode = "TEST2", + StartDate = new DateTime(2018,9,1), + Cost = 0 + } + } + }; + + //Act + await _cohortApprovalService.CreateTransferRequest( + TestHelper.Clone(_commitment), Mock.Of()); + + //Assert + _commitmentRepository.Verify(x => x.StartTransferRequestApproval( + It.IsAny(), + It.IsAny(), + It.Is(cap => cap == 9000), + It.IsAny>() + )); + } + } +} diff --git a/src/SFA.DAS.Commitments.Application.UnitTests/TestHelper.cs b/src/SFA.DAS.Commitments.Application.UnitTests/TestHelper.cs index f38da99208..562b7ddf0d 100644 --- a/src/SFA.DAS.Commitments.Application.UnitTests/TestHelper.cs +++ b/src/SFA.DAS.Commitments.Application.UnitTests/TestHelper.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Reflection; using KellermanSoftware.CompareNetObjects; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace SFA.DAS.Commitments.Application.UnitTests { @@ -8,8 +10,13 @@ public static class TestHelper { public static T Clone(T source) { - var serialized = JsonConvert.SerializeObject(source); - return JsonConvert.DeserializeObject(serialized); + var settings = new JsonSerializerSettings + { + ContractResolver = new JsonIgnoreAttributeIgnorerContractResolver() + }; + + var serialized = JsonConvert.SerializeObject(source, settings); + return JsonConvert.DeserializeObject(serialized, settings); } public static bool EnumerablesAreEqual(IEnumerable expected, IEnumerable actual) @@ -18,4 +25,14 @@ public static bool EnumerablesAreEqual(IEnumerable expected, IEnumerable validator, ICommitmentRepository commitmentRepository, IApprenticeshipRepository apprenticeshipRepository, IApprenticeshipOverlapRules overlapRules, ICurrentDateTime currentDateTime, IHistoryRepository historyRepository, IApprenticeshipEventsList apprenticeshipEventsList, IApprenticeshipEventsPublisher apprenticeshipEventsPublisher, IMediator mediator, IMessagePublisher messagePublisher, ICommitmentsLogger logger) + public ProviderApproveCohortCommandHandler(AbstractValidator validator, + ICommitmentRepository commitmentRepository, + IApprenticeshipRepository apprenticeshipRepository, + IApprenticeshipOverlapRules overlapRules, + ICurrentDateTime currentDateTime, + IHistoryRepository historyRepository, + IApprenticeshipEventsList apprenticeshipEventsList, + IApprenticeshipEventsPublisher apprenticeshipEventsPublisher, + IMediator mediator, + IMessagePublisher messagePublisher, + ICommitmentsLogger logger, + IApprenticeshipInfoService apprenticeshipInfoService) { _validator = validator; _commitmentRepository = commitmentRepository; _messagePublisher = messagePublisher; _logger = logger; _historyService = new HistoryService(historyRepository); - _cohortApprovalService = new CohortApprovalService(apprenticeshipRepository, overlapRules, currentDateTime, commitmentRepository, apprenticeshipEventsList, apprenticeshipEventsPublisher, mediator, _logger); + _cohortApprovalService = new CohortApprovalService(apprenticeshipRepository, overlapRules, currentDateTime, commitmentRepository, apprenticeshipEventsList, apprenticeshipEventsPublisher, mediator, _logger, apprenticeshipInfoService); } protected override async Task HandleCore(ProviderApproveCohortCommand message) @@ -52,13 +64,7 @@ protected override async Task HandleCore(ProviderApproveCohortCommand message) { if (commitment.HasTransferSenderAssigned) { - var transferRequestId = await _commitmentRepository.StartTransferRequestApproval(commitment.Id, - _cohortApprovalService.CurrentCostOfCohort(commitment), - _cohortApprovalService.TrainingCourseSummaries(commitment)); - - await _cohortApprovalService.PublishCommitmentRequiresApprovalByTransferSenderEventMessage(_messagePublisher, commitment, transferRequestId); - - commitment.TransferApprovalStatus = TransferApprovalStatus.Pending; + await _cohortApprovalService.CreateTransferRequest(commitment, _messagePublisher); } } else diff --git a/src/SFA.DAS.Commitments.Application/Commands/RejectTransferRequest/RejectTransferRequestCommandHandler.cs b/src/SFA.DAS.Commitments.Application/Commands/RejectTransferRequest/RejectTransferRequestCommandHandler.cs index fbc842e7b1..df1fddb292 100644 --- a/src/SFA.DAS.Commitments.Application/Commands/RejectTransferRequest/RejectTransferRequestCommandHandler.cs +++ b/src/SFA.DAS.Commitments.Application/Commands/RejectTransferRequest/RejectTransferRequestCommandHandler.cs @@ -3,6 +3,7 @@ using FluentValidation; using MediatR; using SFA.DAS.Commitments.Application.Exceptions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Application.Interfaces.ApprenticeshipEvents; using SFA.DAS.Commitments.Application.Rules; using SFA.DAS.Commitments.Application.Services; @@ -35,7 +36,8 @@ public RejectTransferRequestCommandHandler(AbstractValidator GetStatusOn(x.EffectiveFrom, x.EffectiveTo, date) == TrainingProgrammeStatus.Active); + + return applicableFundingPeriod?.FundingCap ?? 0; + } + + /// + /// we make use of the same logic to determine ActiveOn and FundingBandOn so that if the programme is active, it should fall within a funding band + /// + private static TrainingProgrammeStatus GetStatusOn(DateTime? effectiveFrom, DateTime? effectiveTo, DateTime date) + { + var dateOnly = date.Date; + + if (effectiveFrom.HasValue && effectiveFrom.Value.FirstOfMonth() > dateOnly) + return TrainingProgrammeStatus.Pending; + + if (!effectiveTo.HasValue || effectiveTo.Value >= dateOnly) + return TrainingProgrammeStatus.Active; + + return TrainingProgrammeStatus.Expired; + } + } +} diff --git a/src/SFA.DAS.Commitments.Application/Properties/AssemblyInfo.cs b/src/SFA.DAS.Commitments.Application/Properties/AssemblyInfo.cs index 90e71a2393..870b4485ee 100644 --- a/src/SFA.DAS.Commitments.Application/Properties/AssemblyInfo.cs +++ b/src/SFA.DAS.Commitments.Application/Properties/AssemblyInfo.cs @@ -14,6 +14,9 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +//Allow internal services to be unit tested +[assembly: InternalsVisibleTo("SFA.DAS.Commitments.Application.UnitTests")] + // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. diff --git a/src/SFA.DAS.Commitments.Application/SFA.DAS.Commitments.Application.csproj b/src/SFA.DAS.Commitments.Application/SFA.DAS.Commitments.Application.csproj index ef761d20b0..d9ad3273b6 100644 --- a/src/SFA.DAS.Commitments.Application/SFA.DAS.Commitments.Application.csproj +++ b/src/SFA.DAS.Commitments.Application/SFA.DAS.Commitments.Application.csproj @@ -175,6 +175,8 @@ + + diff --git a/src/SFA.DAS.Commitments.Application/Services/CohortApprovalService.cs b/src/SFA.DAS.Commitments.Application/Services/CohortApprovalService.cs index 2577e6246c..2543556e90 100644 --- a/src/SFA.DAS.Commitments.Application/Services/CohortApprovalService.cs +++ b/src/SFA.DAS.Commitments.Application/Services/CohortApprovalService.cs @@ -6,6 +6,8 @@ using MediatR; using Newtonsoft.Json; using SFA.DAS.Commitments.Application.Commands.SetPaymentOrder; +using SFA.DAS.Commitments.Application.Extensions; +using SFA.DAS.Commitments.Application.Interfaces; using SFA.DAS.Commitments.Application.Interfaces.ApprenticeshipEvents; using SFA.DAS.Commitments.Application.Rules; using SFA.DAS.Commitments.Domain; @@ -26,6 +28,7 @@ internal class CohortApprovalService private readonly IMediator _mediator; private readonly OverlappingApprenticeshipService _overlappingApprenticeshipService; private readonly ApprenticeshipEventsService _apprenticeshipEventsService; + private readonly IApprenticeshipInfoService _apprenticeshipInfoService; private readonly ICommitmentsLogger _logger; internal CohortApprovalService(IApprenticeshipRepository apprenticeshipRepository, @@ -35,13 +38,15 @@ internal CohortApprovalService(IApprenticeshipRepository apprenticeshipRepositor IApprenticeshipEventsList apprenticeshipEventsList, IApprenticeshipEventsPublisher apprenticeshipEventsPublisher, IMediator mediator, - ICommitmentsLogger logger) + ICommitmentsLogger logger, + IApprenticeshipInfoService apprenticeshipInfoService) { _apprenticeshipRepository = apprenticeshipRepository; _currentDateTime = currentDateTime; _commitmentRepository = commitmentRepository; _mediator = mediator; _logger = logger; + _apprenticeshipInfoService = apprenticeshipInfoService; _overlappingApprenticeshipService = new OverlappingApprenticeshipService(apprenticeshipRepository, overlapRules); _apprenticeshipEventsService = new ApprenticeshipEventsService(apprenticeshipEventsList, apprenticeshipEventsPublisher, @@ -133,12 +138,30 @@ internal async Task ReorderPayments(long employerAccountId) await _mediator.SendAsync(new SetPaymentOrderCommand { AccountId = employerAccountId }); } - internal decimal CurrentCostOfCohort(Commitment commitment) + //not sure why we can't dependency inject the message publisher + internal async Task CreateTransferRequest(Commitment commitment, IMessagePublisher messagePublisher) { - return commitment.Apprenticeships.Sum(x => x.Cost ?? 0); + decimal totalCost = 0; + var totalFundingCap = 0; + + foreach (var apprenticeship in commitment.Apprenticeships) + { + var course = await _apprenticeshipInfoService.GetTrainingProgram(apprenticeship.TrainingCode); + var cap = course.FundingCapOn(apprenticeship.StartDate.Value); + totalFundingCap += cap; + totalCost += apprenticeship.Cost.Value < cap ? apprenticeship.Cost.Value : cap; + } + + var trainingCourseSummaries = TrainingCourseSummaries(commitment); + + var transferRequestId = await _commitmentRepository.StartTransferRequestApproval(commitment.Id, totalCost, totalFundingCap, trainingCourseSummaries); + + await PublishCommitmentRequiresApprovalByTransferSenderEventMessage(messagePublisher, commitment, transferRequestId, totalCost); + + commitment.TransferApprovalStatus = TransferApprovalStatus.Pending; } - internal List TrainingCourseSummaries(Commitment commitment) + private List TrainingCourseSummaries(Commitment commitment) { var apprenticeships = commitment.Apprenticeships ?? new List(); @@ -152,13 +175,11 @@ internal List TrainingCourseSummaries(Commitment commitme return grouped.ToList(); } - internal Task PublishCommitmentRequiresApprovalByTransferSenderEventMessage(IMessagePublisher messagePublisher, Commitment commitment, long transferRequestId) + private async Task PublishCommitmentRequiresApprovalByTransferSenderEventMessage(IMessagePublisher messagePublisher, Commitment commitment, long transferRequestId, decimal totalCost) { - decimal totalCost = CurrentCostOfCohort(commitment); - var senderMessage = new CohortApprovalByTransferSenderRequested(transferRequestId, commitment.EmployerAccountId, commitment.Id, commitment.TransferSenderId.Value, totalCost); - return messagePublisher.PublishAsync(senderMessage); + await messagePublisher.PublishAsync(senderMessage); } internal async Task CreatePriceHistory(Commitment commitment) diff --git a/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequest.sql b/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequest.sql index bde885b697..50e2bb1adb 100644 --- a/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequest.sql +++ b/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequest.sql @@ -12,6 +12,7 @@ SELECT ,C.TransferSenderName ,C.LegalEntityName ,TR.[Cost] AS TransferCost + ,TR.FundingCap as FundingCap ,TR.TrainingCourses ,TR.[Status] ,TR.TransferApprovalActionedByEmployerName AS ApprovedOrRejectedByUserName diff --git a/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForReceiver.sql b/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForReceiver.sql index d7716504bf..f9e817514d 100644 --- a/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForReceiver.sql +++ b/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForReceiver.sql @@ -10,6 +10,7 @@ SELECT ,CommitmentId ,SendingEmployerAccountId ,TransferCost + ,FundingCap ,[Status] ,ApprovedOrRejectedByUserName ,ApprovedOrRejectedByUserEmail diff --git a/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForSender.sql b/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForSender.sql index 998a690531..740d50da8c 100644 --- a/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForSender.sql +++ b/src/SFA.DAS.Commitments.Database/StoredProcedures/GetTransferRequestsForSender.sql @@ -10,6 +10,7 @@ SELECT ,CommitmentId ,SendingEmployerAccountId ,TransferCost + ,FundingCap ,[Status] ,ApprovedOrRejectedByUserName ,ApprovedOrRejectedByUserEmail diff --git a/src/SFA.DAS.Commitments.Database/StoredProcedures/StartATransferRequest.sql b/src/SFA.DAS.Commitments.Database/StoredProcedures/StartATransferRequest.sql index 6be8e7c3db..2b1dd62480 100644 --- a/src/SFA.DAS.Commitments.Database/StoredProcedures/StartATransferRequest.sql +++ b/src/SFA.DAS.Commitments.Database/StoredProcedures/StartATransferRequest.sql @@ -2,13 +2,14 @@ @commitmentid BIGINT, @cost MONEY, @trainingCourses NVARCHAR(MAX), + @fundingCap MONEY = null, @transferRequestId BIGINT = null OUT AS BEGIN DECLARE @OldApprovalStatus AS TINYINT = NULL - INSERT INTO [dbo].[TransferRequest] (CommitmentId, Cost, TrainingCourses, [Status]) VALUES (@commitmentid, @cost, @trainingCourses, 0) + INSERT INTO [dbo].[TransferRequest] (CommitmentId, Cost, FundingCap, TrainingCourses, [Status]) VALUES (@commitmentid, @cost, @fundingCap, @trainingCourses, 0) SELECT @transferRequestId = SCOPE_IDENTITY() diff --git a/src/SFA.DAS.Commitments.Database/Tables/TransferRequest.sql b/src/SFA.DAS.Commitments.Database/Tables/TransferRequest.sql index 559f968b4b..bb029cc311 100644 --- a/src/SFA.DAS.Commitments.Database/Tables/TransferRequest.sql +++ b/src/SFA.DAS.Commitments.Database/Tables/TransferRequest.sql @@ -10,6 +10,7 @@ [TransferApprovalActionedOn] DATETIME2, [CreatedOn] DATETIME2 NOT NULL DEFAULT GETDATE(), + [FundingCap] MONEY NULL, CONSTRAINT [FK_TransferRequest_Commitment] FOREIGN KEY ([CommitmentId]) REFERENCES [Commitment]([Id]) ) diff --git a/src/SFA.DAS.Commitments.Database/Views/TransferRequestSummary.sql b/src/SFA.DAS.Commitments.Database/Views/TransferRequestSummary.sql index 6485f51545..ad732d1790 100644 --- a/src/SFA.DAS.Commitments.Database/Views/TransferRequestSummary.sql +++ b/src/SFA.DAS.Commitments.Database/Views/TransferRequestSummary.sql @@ -9,6 +9,7 @@ SELECT ,TR.[CommitmentId] ,C.TransferSenderId AS SendingEmployerAccountId ,TR.[Cost] AS TransferCost + ,TR.FundingCap as FundingCap ,TR.[Status] ,TR.TransferApprovalActionedByEmployerName AS ApprovedOrRejectedByUserName ,TR.TransferApprovalActionedByEmployerEmail AS ApprovedOrRejectedByUserEmail diff --git a/src/SFA.DAS.Commitments.Domain/Data/ICommitmentRepository.cs b/src/SFA.DAS.Commitments.Domain/Data/ICommitmentRepository.cs index fdcfa800e6..fc20c3cd24 100644 --- a/src/SFA.DAS.Commitments.Domain/Data/ICommitmentRepository.cs +++ b/src/SFA.DAS.Commitments.Domain/Data/ICommitmentRepository.cs @@ -21,7 +21,7 @@ public interface ICommitmentRepository Task> GetTransferRequestsForSender(long transferSenderAccountId); Task> GetPendingTransferRequests(); Task> GetTransferRequestsForReceiver(long transferReceiverAccountId); - Task StartTransferRequestApproval(long commitmentId, decimal cost, List trainingCourses); + Task StartTransferRequestApproval(long commitmentId, decimal cost, int fundingCap, List trainingCourses); Task ResetEditStatusToEmployer(long commitmentId); Task CreateRelationship(Relationship relationship); Task GetRelationship(long employerAccountId, long providerId, string legalEntityCode); diff --git a/src/SFA.DAS.Commitments.Domain/Entities/TrainingProgramme/TrainingProgrammeStatus.cs b/src/SFA.DAS.Commitments.Domain/Entities/TrainingProgramme/TrainingProgrammeStatus.cs new file mode 100644 index 0000000000..2d6b94eea3 --- /dev/null +++ b/src/SFA.DAS.Commitments.Domain/Entities/TrainingProgramme/TrainingProgrammeStatus.cs @@ -0,0 +1,7 @@ +namespace SFA.DAS.Commitments.Domain.Entities.TrainingProgramme +{ + public enum TrainingProgrammeStatus + { + Pending, Active, Expired + } +} diff --git a/src/SFA.DAS.Commitments.Domain/Entities/TransferRequest.cs b/src/SFA.DAS.Commitments.Domain/Entities/TransferRequest.cs index ef427f82c5..e7b93ba4b8 100644 --- a/src/SFA.DAS.Commitments.Domain/Entities/TransferRequest.cs +++ b/src/SFA.DAS.Commitments.Domain/Entities/TransferRequest.cs @@ -11,6 +11,7 @@ public class TransferRequest public string TransferSenderName { get; set; } public string LegalEntityName { get; set; } public decimal TransferCost { get; set; } + public int FundingCap { get; set; } public string TrainingCourses { get; set; } public TransferApprovalStatus Status { get; set; } public string ApprovedOrRejectedByUserName { get; set; } diff --git a/src/SFA.DAS.Commitments.Domain/Entities/TransferRequestSummary.cs b/src/SFA.DAS.Commitments.Domain/Entities/TransferRequestSummary.cs index 9e5ced5730..eeb55c288b 100644 --- a/src/SFA.DAS.Commitments.Domain/Entities/TransferRequestSummary.cs +++ b/src/SFA.DAS.Commitments.Domain/Entities/TransferRequestSummary.cs @@ -11,6 +11,7 @@ public class TransferRequestSummary public long CommitmentId { get; set; } public long SendingEmployerAccountId { get; set; } public decimal TransferCost { get; set; } + public int FundingCap { get; set; } public TransferApprovalStatus Status { get; set; } public string ApprovedOrRejectedByUserName { get; set; } public string ApprovedOrRejectedByUserEmail { get; set; } diff --git a/src/SFA.DAS.Commitments.Domain/SFA.DAS.Commitments.Domain.csproj b/src/SFA.DAS.Commitments.Domain/SFA.DAS.Commitments.Domain.csproj index 169e3f1bf8..1dfeba3917 100644 --- a/src/SFA.DAS.Commitments.Domain/SFA.DAS.Commitments.Domain.csproj +++ b/src/SFA.DAS.Commitments.Domain/SFA.DAS.Commitments.Domain.csproj @@ -109,6 +109,7 @@ + diff --git a/src/SFA.DAS.Commitments.Infrastructure/Data/CommitmentRepository.cs b/src/SFA.DAS.Commitments.Infrastructure/Data/CommitmentRepository.cs index 4dfba4f888..20c1f74147 100644 --- a/src/SFA.DAS.Commitments.Infrastructure/Data/CommitmentRepository.cs +++ b/src/SFA.DAS.Commitments.Infrastructure/Data/CommitmentRepository.cs @@ -224,7 +224,7 @@ await WithConnection(async connection => } } - public async Task StartTransferRequestApproval(long commitmentId, decimal cost, List trainingCourses) + public async Task StartTransferRequestApproval(long commitmentId, decimal cost, int fundingCap, List trainingCourses) { _logger.Debug($"Starting a Transfer Request Approval", commitmentId: commitmentId); try @@ -234,6 +234,7 @@ public async Task StartTransferRequestApproval(long commitmentId, decimal parameters.Add("@commitmentId", commitmentId, DbType.Int64); parameters.Add("@cost", cost, DbType.Decimal); parameters.Add("@trainingCourses", JsonConvert.SerializeObject(trainingCourses), DbType.String); + parameters.Add("@fundingCap", fundingCap, DbType.Decimal); parameters.Add("@transferRequestId", transferRequestId, DbType.Int64, ParameterDirection.Output); //todo: await WithTransaction(async (connection, transaction) => ??