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();
        }
Exemple #6
0
        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
                );
        }