public async Task <ReadOnlyCollection <FundingSourcePaymentEvent> > UnableToFundTransfer( ProcessUnableToFundTransferFundingSourcePayment message) { try { using (var operation = telemetry.StartOperation("LevyFundedService.UnableToFundTransfer", message.EventId.ToString())) { var stopwatch = Stopwatch.StartNew(); paymentLogger.LogDebug( $"Handling UnableToFundTransfer for {Id}, Job: {message.JobId}, UKPRN: {message.Ukprn}, Receiver Account: {message.AccountId}, Sender Account: {message.TransferSenderAccountId}"); var fundingSourcePayments = await transferFundingSourceEventGenerationService .ProcessReceiverTransferPayment(message).ConfigureAwait(false); paymentLogger.LogInfo( $"Finished handling required payment for {Id}, Job: {message.JobId}, UKPRN: {message.Ukprn}, Account: {message.AccountId}"); telemetry.TrackDuration("LevyFundedService.UnableToFundTransfer", stopwatch, message); telemetry.StopOperation(operation); return(fundingSourcePayments); } } catch (Exception e) { paymentLogger.LogError($"Error handling unable to fund transfer. Error: {e.Message}", e); throw; } }
public async Task ShouldProcessPaymentIfMonthEndProcessed() { // arrange var keys = new List <string> { "1" }; var unableToFundEvent = new ProcessUnableToFundTransferFundingSourcePayment { EventId = Guid.NewGuid(), Learner = new Learner(), AccountId = 666, AmountDue = 100 }; var balance = 100m; var transferAllowance = 50; var levyPayment = new LevyPayment { AmountDue = 55, Type = FundingSourceType.Levy }; var employerCoInvestedPayment = new EmployerCoInvestedPayment { AmountDue = 44, Type = FundingSourceType.CoInvestedEmployer }; var sfaCoInvestedPayment = new SfaCoInvestedPayment { AmountDue = 33, Type = FundingSourceType.CoInvestedSfa }; var allPayments = new FundingSourcePayment[] { levyPayment, employerCoInvestedPayment, sfaCoInvestedPayment }; levyBalanceServiceMock.Setup(s => s.Initialise(balance, transferAllowance)) .Verifiable(); processorMock.Setup(p => p.Process(It.IsAny <RequiredPayment>())) .Returns(() => allPayments) .Verifiable(); monthEndCacheMock.Setup(c => c.TryGet(It.Is <string>(key => key.Equals(CacheKeys.MonthEndCacheKey)), It.IsAny <CancellationToken>())) .ReturnsAsync(() => new ConditionalValue <bool>(true, true)) .Verifiable(); levyAccountCacheMock.Setup(c => c.TryGet(CacheKeys.LevyBalanceKey, It.IsAny <CancellationToken>())) .ReturnsAsync(() => new ConditionalValue <LevyAccountModel>(true, new LevyAccountModel { AccountId = 666, Balance = balance, TransferAllowance = transferAllowance })) .Verifiable(); levyAccountCacheMock.Setup(c => c.AddOrReplace(CacheKeys.LevyBalanceKey, It.Is <LevyAccountModel>(model => model.AccountId == 666), It.IsAny <CancellationToken>())) .Returns(Task.CompletedTask) .Verifiable(); // act var fundingSourcePayments = await service.ProcessReceiverTransferPayment(unableToFundEvent); // assert fundingSourcePayments.Should().HaveCount(3); fundingSourcePayments[0].Should().BeOfType <LevyFundingSourcePaymentEvent>(); fundingSourcePayments[1].Should().BeOfType <EmployerCoInvestedFundingSourcePaymentEvent>(); fundingSourcePayments[2].Should().BeOfType <SfaCoInvestedFundingSourcePaymentEvent>(); fundingSourcePayments[0].AmountDue.Should().Be(55); fundingSourcePayments[1].AmountDue.Should().Be(44); fundingSourcePayments[2].AmountDue.Should().Be(33); }
public async Task ShouldAddPaymentToCacheIfMonthEndNotStarted() { // arrange const long expectedUln = 200; const long expectedUkprn = 100; var expectedEventId = Guid.NewGuid(); var keys = new List <RequiredPaymentSortKeyModel> { new RequiredPaymentSortKeyModel { Ukprn = expectedUkprn, Uln = 1, Id = Guid.NewGuid().ToString(), AgreedOnDate = DateTime.Today }, new RequiredPaymentSortKeyModel { Ukprn = expectedUkprn, Uln = 2, Id = Guid.NewGuid().ToString(), AgreedOnDate = DateTime.Today } }; var unableToFundEvent = new ProcessUnableToFundTransferFundingSourcePayment { Ukprn = expectedUkprn, EventId = expectedEventId, AccountId = 1, AmountDue = 100, StartDate = DateTime.Today, Learner = new Learner { Uln = expectedUln }, }; eventCacheMock .Setup(c => c.AddOrReplace(expectedEventId.ToString(), It.Is <CalculatedRequiredLevyAmount>(levyEvent => levyEvent.AmountDue == unableToFundEvent.AmountDue && levyEvent.EventId == expectedEventId), CancellationToken.None)) .Returns(Task.CompletedTask) .Verifiable(); requiredPaymentSortKeysCacheMock .Setup(c => c.TryGet(CacheKeys.RequiredPaymentKeyListKey, CancellationToken.None)) .ReturnsAsync(() => new ConditionalValue <List <RequiredPaymentSortKeyModel> >(true, keys)) .Verifiable(); requiredPaymentSortKeysCacheMock .Setup(c => c.AddOrReplace(CacheKeys.RequiredPaymentKeyListKey, It.Is <List <RequiredPaymentSortKeyModel> >(list => list.Count == 3 && list[2].Uln == expectedUln && list[2].Ukprn == expectedUkprn), CancellationToken.None)) .Returns(Task.CompletedTask) .Verifiable(); monthEndCacheMock.Setup(c => c.TryGet(It.Is <string>(key => key.Equals(CacheKeys.MonthEndCacheKey)), It.IsAny <CancellationToken>())) .ReturnsAsync(() => new ConditionalValue <bool>(false, false)) .Verifiable(); // act await service.ProcessReceiverTransferPayment(unableToFundEvent); // assert }
public async Task <ReadOnlyCollection <FundingSourcePaymentEvent> > ProcessReceiverTransferPayment(ProcessUnableToFundTransferFundingSourcePayment message) { if (!message.AccountId.HasValue) { throw new InvalidOperationException($"Invalid ProcessUnableToFundTransferFundingSourcePayment event. No account id populated on message. Event id: {message.EventId}"); } paymentLogger.LogDebug($"Converting the unable to fund transfer payment to a levy payment. Event id: {message.EventId}, account id: {message.AccountId}, job id: {message.JobId}"); var requiredPayment = mapper.Map <CalculatedRequiredLevyAmount>(message); paymentLogger.LogVerbose("Mapped ProcessUnableToFundTransferFundingSourcePayment to CalculatedRequiredLevyAmount"); var payments = new List <FundingSourcePaymentEvent>(); var monthEndStartedForThisAccount = await monthEndCache.TryGet(CacheKeys.MonthEndStartedForThisAccountCacheKey); if (!monthEndStartedForThisAccount.HasValue || !monthEndStartedForThisAccount.Value) { paymentLogger.LogDebug("Month end has not been started yet so adding the payment to the cache."); await AddRequiredPayment(requiredPayment); } else { var levyAccountCacheItem = await levyAccountCache.TryGet(CacheKeys.LevyBalanceKey, CancellationToken.None) .ConfigureAwait(false); if (!levyAccountCacheItem.HasValue) { throw new InvalidOperationException($"The last levy account balance has not been stored in the reliable for account: {message.AccountId}"); } levyBalanceService.Initialise(levyAccountCacheItem.Value.Balance, levyAccountCacheItem.Value.TransferAllowance); paymentLogger.LogDebug("Service has finished month end processing so now generating the payments for the ProcessUnableToFundTransferFundingSourcePayment event."); payments.AddRange(fundingSourcePaymentEventBuilder.BuildFundingSourcePaymentsForRequiredPayment(requiredPayment, message.AccountId.Value, message.JobId)); var remainingBalance = mapper.Map <LevyAccountModel>(levyAccountCacheItem.Value); remainingBalance.Balance = levyBalanceService.RemainingBalance; remainingBalance.TransferAllowance = levyBalanceService.RemainingTransferAllowance; await levyAccountCache.AddOrReplace(CacheKeys.LevyBalanceKey, remainingBalance); } paymentLogger.LogInfo($"Finished processing the ProcessUnableToFundTransferFundingSourcePayment. Event id: {message.EventId}, account id: {message.AccountId}, job id: {message.JobId}"); return(payments.AsReadOnly()); }
public void Setup() { var requiredPaymentEventEventId = Guid.NewGuid(); var earningEventId = Guid.NewGuid(); var utcNow = DateTime.UtcNow; unableToFundTransferFundingSourcePayment = new ProcessUnableToFundTransferFundingSourcePayment { EventId = requiredPaymentEventEventId, EarningEventId = earningEventId, EventTime = utcNow, RequiredPaymentEventId = requiredPaymentEventEventId, AmountDue = 1000.00m, CollectionPeriod = CollectionPeriodFactory.CreateFromAcademicYearAndPeriod(1819, 1), DeliveryPeriod = 1, JobId = 1, Learner = new Learner { ReferenceNumber = "001", Uln = 1234567890 }, LearningAim = new LearningAim { FrameworkCode = 403 }, PriceEpisodeIdentifier = "1819-P01", SfaContributionPercentage = 0.9m, Ukprn = 10000, AccountId = 1000000, IlrSubmissionDateTime = DateTime.Today, ContractType = ContractType.Act1, ApprenticeshipEmployerType = ApprenticeshipEmployerType.Levy, ApprenticeshipId = 12, CompletionAmount = 10, CompletionStatus = 1, InstalmentAmount = 10, StartDate = utcNow, TransactionType = TransactionType.Balancing, ActualEndDate = utcNow, FundingSourceType = FundingSourceType.Levy, IlrFileName = "Test", LearningStartDate = utcNow, NumberOfInstalments = 1, PlannedEndDate = utcNow, ApprenticeshipPriceEpisodeId = 1, TransferSenderAccountId = 10, ReportingAimFundingLineType = "Test" }; expectedEvent = new CalculatedRequiredLevyAmount { EventId = requiredPaymentEventEventId, EarningEventId = earningEventId, EventTime = utcNow, AmountDue = 1000.00m, CollectionPeriod = CollectionPeriodFactory.CreateFromAcademicYearAndPeriod(1819, 1), DeliveryPeriod = 1, JobId = 1, Learner = new Learner { ReferenceNumber = "001", Uln = 1234567890 }, LearningAim = new LearningAim { FrameworkCode = 403 }, PriceEpisodeIdentifier = "1819-P01", SfaContributionPercentage = 0.9m, Ukprn = 10000, AccountId = 1000000, IlrSubmissionDateTime = DateTime.Today, ContractType = ContractType.Act1, ApprenticeshipEmployerType = ApprenticeshipEmployerType.Levy, ApprenticeshipId = 12, CompletionAmount = 10, CompletionStatus = 1, InstalmentAmount = 10, StartDate = utcNow, ActualEndDate = utcNow, IlrFileName = "Test", LearningStartDate = utcNow, NumberOfInstalments = 1, PlannedEndDate = utcNow, ApprenticeshipPriceEpisodeId = 1, OnProgrammeEarningType = OnProgrammeEarningType.Balancing, TransferSenderAccountId = 10, ReportingAimFundingLineType = "Test" }; mapperConfiguration = AutoMapperConfigurationFactory.CreateMappingConfig(); autoMapper = mapperConfiguration.CreateMapper(); }
public void SetUp() { ukprn = 3383742; accountId = 78934234; message = new ProcessUnableToFundTransferFundingSourcePayment { AccountId = accountId, Ukprn = ukprn }; calculatedRequiredLevyAmount = new CalculatedRequiredLevyAmount { Ukprn = ukprn }; levyAccountModel = new LevyAccountModel { AccountId = accountId, Balance = 3000, TransferAllowance = 2000 }; mappedLevyAccountModel = new LevyAccountModel { AccountId = accountId, Balance = 3000, TransferAllowance = 2000 }; fundingSourcePaymentEvents = new List <FundingSourcePaymentEvent> { new LevyFundingSourcePaymentEvent { AccountId = accountId, Ukprn = ukprn, AmountDue = 1000 } }; paymentLogger = new Mock <IPaymentLogger>(); mapper = new Mock <IMapper>(); monthEndCache = new Mock <IDataCache <bool> >(); levyAccountCache = new Mock <IDataCache <LevyAccountModel> >(); levyBalanceService = new Mock <ILevyBalanceService>(); fundingSourcePaymentEventBuilder = new Mock <IFundingSourcePaymentEventBuilder>(); levyTransactionBatchStorageService = new Mock <ILevyTransactionBatchStorageService>(); mapper.Setup(x => x.Map <CalculatedRequiredLevyAmount>(message)) .Returns(calculatedRequiredLevyAmount); mapper.Setup(x => x.Map <LevyAccountModel>(levyAccountModel)) .Returns(mappedLevyAccountModel); levyAccountCache.Setup(x => x.TryGet(CacheKeys.LevyBalanceKey, It.IsAny <CancellationToken>())) .ReturnsAsync(new ConditionalValue <LevyAccountModel>(true, levyAccountModel)); fundingSourcePaymentEventBuilder.Setup(x => x.BuildFundingSourcePaymentsForRequiredPayment( It.IsAny <CalculatedRequiredLevyAmount>(), accountId, It.IsAny <long>())) .Returns(fundingSourcePaymentEvents); service = new TransferFundingSourceEventGenerationService( paymentLogger.Object, mapper.Object, monthEndCache.Object, levyAccountCache.Object, levyBalanceService.Object, fundingSourcePaymentEventBuilder.Object, levyTransactionBatchStorageService.Object ); }