private IList <PeriodisedRequiredPaymentEvent> CreateRefundPayments(IdentifiedRemovedLearningAim identifiedRemovedLearningAim, List <Payment> historicPayments, int transactionType, ConditionalValue <PaymentHistoryEntity[]> cacheItem)
        {
            var refundPaymentsAndPeriods = refundRemovedLearningAimService.RefundLearningAim(historicPayments);

            return(refundPaymentsAndPeriods
                   .Select(refund =>
            {
                logger.LogVerbose("Now mapping the required payment to a PeriodisedRequiredPaymentEvent.");

                var historicPayment = cacheItem.Value.FirstOrDefault(payment =>
                                                                     payment.PriceEpisodeIdentifier == refund.payment.PriceEpisodeIdentifier &&
                                                                     payment.DeliveryPeriod == refund.deliveryPeriod);

                if (historicPayment == null)
                {
                    throw new InvalidOperationException($"Cannot find historic payment with price episode identifier: {refund.payment.PriceEpisodeIdentifier} for period {refund.deliveryPeriod}.");
                }

                var requiredPaymentEvent = requiredPaymentEventFactory.Create(refund.payment.EarningType, transactionType);

                mapper.Map(refund.payment, requiredPaymentEvent);
                mapper.Map(historicPayment, requiredPaymentEvent);
                mapper.Map(identifiedRemovedLearningAim, requiredPaymentEvent);

                // funding line type is not part of removed aim, we need to use value from historic payment
                requiredPaymentEvent.LearningAim.FundingLineType = historicPayment.LearningAimFundingLineType;

                logger.LogDebug("Finished mapping");
                return requiredPaymentEvent;
            }).ToList());
        }
 private static bool AssertPaymentModel(PaymentModel actualPayment, IdentifiedRemovedLearningAim message, Guid originalEventId, DateTimeOffset originalEventTime)
 {
     return(actualPayment.Id == 0 &&
            actualPayment.Amount == -100 &&
            actualPayment.JobId == message.JobId &&
            actualPayment.ClawbackSourcePaymentEventId == originalEventId &&
            actualPayment.CollectionPeriod.Period == message.CollectionPeriod.Period &&
            actualPayment.CollectionPeriod.AcademicYear == message.CollectionPeriod.AcademicYear &&
            actualPayment.IlrSubmissionDateTime == message.IlrSubmissionDateTime &&
            actualPayment.EventId != originalEventId &&
            actualPayment.EventTime != originalEventTime &&
            actualPayment.RequiredPaymentEventId == Guid.Empty &&
            actualPayment.EarningEventId == Guid.Empty &&
            actualPayment.FundingSourceEventId == Guid.Empty);
 }
Exemplo n.º 3
0
        private static void ConvertToClawbackPayment(IdentifiedRemovedLearningAim message, PaymentModel clawbackPayment)
        {
            clawbackPayment.Id                    = 0;
            clawbackPayment.Amount               *= -1;
            clawbackPayment.JobId                 = message.JobId;
            clawbackPayment.CollectionPeriod      = message.CollectionPeriod.Clone();
            clawbackPayment.IlrSubmissionDateTime = message.IlrSubmissionDateTime;
            clawbackPayment.EventTime             = DateTimeOffset.UtcNow;

            clawbackPayment.RequiredPaymentEventId = Guid.Empty;
            clawbackPayment.EarningEventId         = Guid.Empty;
            clawbackPayment.FundingSourceEventId   = Guid.Empty;

            //NOTE: DO NOT CHANGE THE ORDER OF ASSIGNMENT BELLOW
            clawbackPayment.ClawbackSourcePaymentEventId = clawbackPayment.EventId;
            clawbackPayment.EventId = Guid.NewGuid();
        }
Exemplo n.º 4
0
        public void SetUp()
        {
            mocker  = AutoMock.GetLoose();
            history = new List <PaymentHistoryEntity>();
            var config = new MapperConfiguration(cfg => cfg.AddProfile <RequiredPaymentsProfile>());

            config.AssertConfigurationIsValid();
            var mapper = new Mapper(config);

            mocker.Provide <IMapper>(mapper);
            var logger = new Mock <IPaymentLogger>();

            duplicateEarningsServiceMock = mocker.Mock <IDuplicateEarningEventService>();
            mocker.Provide <IRefundRemovedLearningAimService>(new RefundRemovedLearningAimService());
            mocker.Provide <IPeriodisedRequiredPaymentEventFactory>(new PeriodisedRequiredPaymentEventFactory(logger.Object));

            identifiedLearner = new IdentifiedRemovedLearningAim
            {
                CollectionPeriod = new CollectionPeriod
                {
                    AcademicYear = 1819,
                    Period       = 1
                },
                EventId               = Guid.NewGuid(),
                EventTime             = DateTimeOffset.UtcNow,
                IlrSubmissionDateTime = DateTime.Now,
                JobId   = 1,
                Learner = new Learner
                {
                    ReferenceNumber = "learner-ref-123",
                },
                LearningAim = new LearningAim
                {
                    FrameworkCode   = 3,
                    FundingLineType = "funding line type",
                    PathwayCode     = 4,
                    ProgrammeType   = 5,
                    Reference       = "learning-ref-456",
                    StandardCode    = 6
                },
                Ukprn = 7
            };
        }
        private IList <PeriodisedRequiredPaymentEvent> CreateRefundPayments(IdentifiedRemovedLearningAim identifiedRemovedLearningAim, List <Payment> historicPaymentsByTransactionType, int transactionType, ConditionalValue <PaymentHistoryEntity[]> cacheItem)
        {
            var refundPaymentsAndPeriods = refundRemovedLearningAimService.RefundLearningAim(historicPaymentsByTransactionType);

            return(refundPaymentsAndPeriods
                   .Select(refund =>
            {
                logger.LogVerbose("Now mapping the required payment to a PeriodisedRequiredPaymentEvent.");

                var historicPayment = cacheItem.Value.FirstOrDefault(payment =>
                                                                     payment.PriceEpisodeIdentifier == refund.payment.PriceEpisodeIdentifier &&
                                                                     payment.DeliveryPeriod == refund.deliveryPeriod &&
                                                                     payment.TransactionType == transactionType);

                if (historicPayment == null)
                {
                    throw new InvalidOperationException($"Cannot find historic payment with price episode identifier: {refund.payment.PriceEpisodeIdentifier} for period {refund.deliveryPeriod}.");
                }

                var requiredPaymentEvent = requiredPaymentEventFactory.Create(refund.payment.EarningType, transactionType);
                if (requiredPaymentEvent == null)
                {
                    // This shouldn't now happen as the transaction type in the history should match the one in the cache
                    logger.LogWarning(
                        $"Required payment event is null for EarningType: {refund.payment.EarningType} with TransactionType: {transactionType}");
                    return null;
                }

                mapper.Map(refund.payment, requiredPaymentEvent);
                mapper.Map(historicPayment, requiredPaymentEvent);
                mapper.Map(identifiedRemovedLearningAim, requiredPaymentEvent);

                // funding line type and Learner Uln are not part of removed aim, we need to use value from historic payment
                requiredPaymentEvent.LearningAim.FundingLineType = historicPayment.LearningAimFundingLineType;
                requiredPaymentEvent.Learner.Uln = historicPayment.LearnerUln;

                logger.LogDebug("Finished mapping");
                return requiredPaymentEvent;
            })
                   .Where(x => x != null)
                   .ToList());
        }
Exemplo n.º 6
0
        public async Task HandleRemovedLearner()
        {
            var payment = Context.Get <PaymentModel>(HistoricPayment);

            var removedLearnerEvent = new IdentifiedRemovedLearningAim
            {
                JobId            = TestSession.GenerateId(),
                CollectionPeriod = new CollectionPeriod {
                    AcademicYear = 1920, Period = 5
                },
                IlrSubmissionDateTime = DateTime.Now,
                Ukprn        = payment.Ukprn,
                ContractType = ContractType.Act2,
                LearningAim  = new LearningAim
                {
                    FrameworkCode   = 1,
                    FundingLineType = "funding line type",
                    PathwayCode     = 2,
                    ProgrammeType   = 3,
                    Reference       = "ZPROG001",
                    StandardCode    = 4,
                    StartDate       = DateTime.Now.AddMonths(-2),
                },
                Learner = new Learner
                {
                    ReferenceNumber = "abc",
                }
            };

            Context.Add(IdentifiedRemovedLearningAim, removedLearnerEvent);
            Context.Add(JobIds, new List <long> {
                removedLearnerEvent.JobId
            });

            await MessageSession.Publish(removedLearnerEvent);
        }
        public void OneTimeSetUp()
        {
            var config = new MapperConfiguration(cfg => cfg.AddProfile <RequiredPaymentsProfile>());

            config.AssertConfigurationIsValid();
            mapper = new Mapper(config);

            message = new IdentifiedRemovedLearningAim
            {
                LearningAim = new LearningAim
                {
                    Reference       = "reference",
                    PathwayCode     = 1,
                    FrameworkCode   = 2,
                    ProgrammeType   = 3,
                    StandardCode    = 4,
                    FundingLineType = "fundingLineType",
                    SequenceNumber  = 5,
                    StartDate       = DateTime.Now,
                },
                Learner = new Learner
                {
                    ReferenceNumber = "LearnerRef",
                    Uln             = 123,
                },
                CollectionPeriod = new CollectionPeriod {
                    AcademicYear = 2021, Period = 4
                },
                Ukprn                 = 12345678,
                ContractType          = ContractType.Act1,
                EventId               = Guid.NewGuid(),
                EventTime             = DateTimeOffset.Now,
                IlrSubmissionDateTime = DateTime.Now,
                JobId                 = 456
            };
        }
Exemplo n.º 8
0
 public static void ShouldBeMappedTo(this PeriodisedRequiredPaymentEvent paymentEvent, IdentifiedRemovedLearningAim identifiedLearningAim)
 {
     paymentEvent.EarningEventId.Should().Be(Guid.Empty);
     paymentEvent.EventId.Should().NotBe(Guid.Empty);
     paymentEvent.CollectionPeriod.AcademicYear.Should().Be(identifiedLearningAim.CollectionPeriod.AcademicYear);
     paymentEvent.CollectionPeriod.Period.Should().Be(identifiedLearningAim.CollectionPeriod.Period);
     paymentEvent.IlrSubmissionDateTime.Should().Be(identifiedLearningAim.IlrSubmissionDateTime);
     paymentEvent.JobId.Should().Be(identifiedLearningAim.JobId);
     paymentEvent.Learner.ReferenceNumber.Should().Be(identifiedLearningAim.Learner.ReferenceNumber);
     paymentEvent.LearningAim.Reference.Should().Be(identifiedLearningAim.LearningAim.Reference);
     paymentEvent.LearningAim.FrameworkCode.Should().Be(identifiedLearningAim.LearningAim.FrameworkCode);
     paymentEvent.LearningAim.FundingLineType.Should().Be(identifiedLearningAim.LearningAim.FundingLineType);
     paymentEvent.LearningAim.PathwayCode.Should().Be(identifiedLearningAim.LearningAim.PathwayCode);
     paymentEvent.LearningAim.ProgrammeType.Should().Be(identifiedLearningAim.LearningAim.ProgrammeType);
     paymentEvent.LearningAim.StandardCode.Should().Be(identifiedLearningAim.LearningAim.StandardCode);
 }
        public void SetUp()
        {
            mocker = AutoMock.GetLoose();
            var config = new MapperConfiguration(cfg => cfg.AddProfile <RequiredPaymentsProfile>());

            config.AssertConfigurationIsValid();
            mapper = new Mapper(config);


            identifiedLearningAim = new IdentifiedRemovedLearningAim
            {
                CollectionPeriod = new CollectionPeriod
                {
                    AcademicYear = 1819,
                    Period       = 1
                },
                EventId               = Guid.NewGuid(),
                EventTime             = DateTimeOffset.UtcNow,
                IlrSubmissionDateTime = DateTime.Now,
                JobId   = 1,
                Learner = new Learner
                {
                    ReferenceNumber = "learner-ref-123",
                    Uln             = 2
                },
                LearningAim = new LearningAim
                {
                    FrameworkCode   = 3,
                    FundingLineType = "funding line type",
                    PathwayCode     = 4,
                    ProgrammeType   = 5,
                    Reference       = "learning-ref-456",
                    StandardCode    = 6
                },
                Ukprn = 7
            };

            historicPayment = new PaymentHistoryEntity
            {
                Amount = 10,
                SfaContributionPercentage = .9M,
                TransactionType           = (int)OnProgrammeEarningType.Learning,
                CollectionPeriod          = new CollectionPeriod
                {
                    AcademicYear = 1819,
                    Period       = 1
                },
                LearnAimReference      = "aim-ref-123",
                LearnerReferenceNumber = "learning-ref-456",
                PriceEpisodeIdentifier = "pe-1",
                DeliveryPeriod         = 1,
                Ukprn               = 7,
                ActualEndDate       = null,
                CompletionAmount    = 3000,
                CompletionStatus    = 1,
                ExternalId          = Guid.NewGuid(),
                FundingSource       = FundingSourceType.Levy,
                InstalmentAmount    = 1000,
                NumberOfInstalments = 12,
                PlannedEndDate      = DateTime.Today,
                StartDate           = DateTime.Today.AddMonths(-12),
            };
        }
Exemplo n.º 10
0
        public async Task <ReadOnlyCollection <PeriodisedRequiredPaymentEvent> > RefundRemovedLearningAim(IdentifiedRemovedLearningAim removedLearningAim, CancellationToken cancellationToken)
        {
            paymentLogger.LogDebug($"Handling identified removed learning aim for jobId:{removedLearningAim.JobId} with apprenticeship key based on {logSafeApprenticeshipKeyString}");
            using (var operation = telemetry.StartOperation("RequiredPaymentsService.RefundRemovedLearningAim", removedLearningAim.EventId.ToString()))
            {
                var stopwatch = Stopwatch.StartNew();
                await ResetPaymentHistoryCacheIfDifferentCollectionPeriod(removedLearningAim.CollectionPeriod)
                .ConfigureAwait(false);

                await Initialise(removedLearningAim.CollectionPeriod.Period).ConfigureAwait(false);

                var requiredPaymentEvents = await refundRemovedLearningAimProcessor.RefundLearningAim(removedLearningAim, paymentHistoryCache, cancellationToken).ConfigureAwait(false);

                Log(requiredPaymentEvents);
                // removed aim would will not receive a command to clear cache
                await Reset().ConfigureAwait(false);

                telemetry.TrackDuration("RequiredPaymentsService.RefundRemovedLearningAim", stopwatch, removedLearningAim);
                telemetry.StopOperation(operation);
                return(requiredPaymentEvents);
            }
        }
        public async Task <ReadOnlyCollection <PeriodisedRequiredPaymentEvent> > RefundLearningAim(IdentifiedRemovedLearningAim identifiedRemovedLearningAim, IDataCache <PaymentHistoryEntity[]> paymentHistoryCache, CancellationToken cancellationToken)
        {
            logger.LogDebug($"Now processing request to generate refunds for learning aim: learner: {identifiedRemovedLearningAim.Learner.ReferenceNumber}, Aim: {identifiedRemovedLearningAim.LearningAim.Reference}");
            var cacheItem = await paymentHistoryCache.TryGet(CacheKeys.PaymentHistoryKey, cancellationToken)
                            .ConfigureAwait(false);

            if (!cacheItem.HasValue)
            {
                throw new InvalidOperationException("No payment history found in the cache.");
            }

            var historicPayments = cacheItem.Value.Select(mapper.Map <PaymentHistoryEntity, Payment>).ToList();

            logger.LogDebug($"Got {historicPayments.Count} historic payments. Now generating refunds per transaction type.");

            var requiredPaymentEvents = historicPayments.GroupBy(historicPayment => historicPayment.TransactionType)
                                        .SelectMany(group => CreateRefundPayments(identifiedRemovedLearningAim, group.ToList(), group.Key, cacheItem))
                                        .ToList();

            return(requiredPaymentEvents.AsReadOnly());
        }
Exemplo n.º 12
0
        public async Task <IList <CalculatedRequiredLevyAmount> > GenerateClawbackForRemovedLearnerAim(IdentifiedRemovedLearningAim message, CancellationToken cancellationToken)
        {
            var learnerPaymentHistory = await paymentClawbackRepository.GetReadOnlyLearnerPaymentHistory(
                message.Ukprn,
                message.ContractType,
                message.Learner.ReferenceNumber,
                message.LearningAim.Reference,
                message.LearningAim.FrameworkCode,
                message.LearningAim.PathwayCode,
                message.LearningAim.ProgrammeType,
                message.LearningAim.StandardCode,
                message.CollectionPeriod.AcademicYear,
                message.CollectionPeriod.Period,
                cancellationToken).ConfigureAwait(false);

            if (!learnerPaymentHistory.Any() || learnerPaymentHistory.Sum(p => p.Amount) == 0)
            {
                logger.LogInfo("no previous payments or sum of all previous payments is already zero so no action required" +
                               $"jobId:{message.JobId}, learnerRef:{message.Learner.ReferenceNumber}, frameworkCode:{message.LearningAim.FrameworkCode}, " +
                               $"pathwayCode:{message.LearningAim.PathwayCode}, programmeType:{message.LearningAim.ProgrammeType}, " +
                               $"standardCode:{message.LearningAim.StandardCode}, learningAimReference:{message.LearningAim.Reference}, " +
                               $"academicYear:{message.CollectionPeriod.AcademicYear}, contractType:{message.ContractType}");

                return(new List <CalculatedRequiredLevyAmount>());
            }

            var paymentToIgnore = learnerPaymentHistory.Join(learnerPaymentHistory,
                                                             payment => payment.EventId,
                                                             clawbackPayment => clawbackPayment.ClawbackSourcePaymentEventId,
                                                             (payment, clawbackPayment) => new[] { payment.EventId, clawbackPayment.EventId })
                                  .SelectMany(paymentId => paymentId);

            if (learnerPaymentHistory.Where(payment => paymentToIgnore.Contains(payment.EventId)).Sum(p => p.Amount) != 0)
            {
                logger.LogWarning("Previous Payment and Clawback do not Match, this clawback will result in over or under payment" +
                                  $"jobId:{message.JobId}, learnerRef:{message.Learner.ReferenceNumber}, frameworkCode:{message.LearningAim.FrameworkCode}, " +
                                  $"pathwayCode:{message.LearningAim.PathwayCode}, programmeType:{message.LearningAim.ProgrammeType}, " +
                                  $"standardCode:{message.LearningAim.StandardCode}, learningAimReference:{message.LearningAim.Reference}, " +
                                  $"academicYear:{message.CollectionPeriod.AcademicYear}, contractType:{message.ContractType}");
            }

            var paymentToClawback = learnerPaymentHistory
                                    .Where(payment => !paymentToIgnore.Contains(payment.EventId))
                                    .Select(payment =>
            {
                ConvertToClawbackPayment(message, payment);
                return(payment);
            }).ToList();

            return(await ProcessPaymentToClawback(paymentToClawback, cancellationToken));
        }
 private static void AssertCalculatedRequiredLevyAmountEvent(CalculatedRequiredLevyAmount listOfCalculatedRequiredLevyAmounts, PaymentModel historicalPayment, IdentifiedRemovedLearningAim message)
 {
     listOfCalculatedRequiredLevyAmounts.AgreementId.Should().Be(historicalPayment.AgreementId);
     listOfCalculatedRequiredLevyAmounts.AgreedOnDate.Should().Be(null);
     listOfCalculatedRequiredLevyAmounts.AccountId.Should().Be(historicalPayment.AccountId);
     listOfCalculatedRequiredLevyAmounts.ActualEndDate.Should().Be(historicalPayment.ActualEndDate);
     listOfCalculatedRequiredLevyAmounts.AmountDue.Should().Be(-100);
     listOfCalculatedRequiredLevyAmounts.ApprenticeshipEmployerType.Should().Be(historicalPayment.ApprenticeshipEmployerType);
     listOfCalculatedRequiredLevyAmounts.ApprenticeshipId.Should().Be(historicalPayment.ApprenticeshipId);
     listOfCalculatedRequiredLevyAmounts.ApprenticeshipPriceEpisodeId.Should().Be(historicalPayment.ApprenticeshipPriceEpisodeId);
     listOfCalculatedRequiredLevyAmounts.CollectionPeriod.Period.Should().Be(message.CollectionPeriod.Period);
     listOfCalculatedRequiredLevyAmounts.CollectionPeriod.AcademicYear.Should().Be(message.CollectionPeriod.AcademicYear);
     listOfCalculatedRequiredLevyAmounts.CompletionAmount.Should().Be(historicalPayment.CompletionAmount);
     listOfCalculatedRequiredLevyAmounts.CompletionStatus.Should().Be(historicalPayment.CompletionStatus);
     listOfCalculatedRequiredLevyAmounts.ContractType.Should().Be(historicalPayment.ContractType);
     listOfCalculatedRequiredLevyAmounts.DeliveryPeriod.Should().Be(historicalPayment.DeliveryPeriod);
     listOfCalculatedRequiredLevyAmounts.EarningEventId.Should().Be(Guid.Empty);
     listOfCalculatedRequiredLevyAmounts.EventTime.Should().BeCloseTo(DateTimeOffset.UtcNow, 100);
     listOfCalculatedRequiredLevyAmounts.IlrFileName.Should().Be(null);
     listOfCalculatedRequiredLevyAmounts.IlrSubmissionDateTime.Should().Be(message.IlrSubmissionDateTime);
     listOfCalculatedRequiredLevyAmounts.InstalmentAmount.Should().Be(historicalPayment.InstalmentAmount);
     listOfCalculatedRequiredLevyAmounts.JobId.Should().Be(message.JobId);
     listOfCalculatedRequiredLevyAmounts.Learner.ReferenceNumber.Should().Be(historicalPayment.LearnerReferenceNumber);
     listOfCalculatedRequiredLevyAmounts.Learner.Uln.Should().Be(historicalPayment.LearnerUln);
     listOfCalculatedRequiredLevyAmounts.LearningAim.FrameworkCode.Should().Be(historicalPayment.LearningAimFrameworkCode);
     listOfCalculatedRequiredLevyAmounts.LearningAim.FundingLineType.Should().Be(historicalPayment.LearningAimFundingLineType);
     listOfCalculatedRequiredLevyAmounts.LearningAim.PathwayCode.Should().Be(historicalPayment.LearningAimPathwayCode);
     listOfCalculatedRequiredLevyAmounts.LearningAim.ProgrammeType.Should().Be(historicalPayment.LearningAimProgrammeType);
     listOfCalculatedRequiredLevyAmounts.LearningAim.Reference.Should().Be(historicalPayment.LearningAimReference);
     listOfCalculatedRequiredLevyAmounts.LearningAim.SequenceNumber.Should().Be(0);
     listOfCalculatedRequiredLevyAmounts.LearningAim.StandardCode.Should().Be(historicalPayment.LearningAimStandardCode);
     listOfCalculatedRequiredLevyAmounts.LearningAim.StartDate.Should().Be(historicalPayment.StartDate);
     listOfCalculatedRequiredLevyAmounts.LearningStartDate.Should().Be(historicalPayment.LearningStartDate);
     listOfCalculatedRequiredLevyAmounts.NumberOfInstalments.Should().Be(historicalPayment.NumberOfInstalments);
     listOfCalculatedRequiredLevyAmounts.OnProgrammeEarningType.Should().Be((OnProgrammeEarningType)historicalPayment.TransactionType);
     listOfCalculatedRequiredLevyAmounts.PlannedEndDate.Should().Be(historicalPayment.PlannedEndDate);
     listOfCalculatedRequiredLevyAmounts.PriceEpisodeIdentifier.Should().Be(historicalPayment.PriceEpisodeIdentifier);
     listOfCalculatedRequiredLevyAmounts.Priority.Should().Be(0);
     listOfCalculatedRequiredLevyAmounts.ReportingAimFundingLineType.Should().Be(historicalPayment.ReportingAimFundingLineType);
     listOfCalculatedRequiredLevyAmounts.SfaContributionPercentage.Should().Be(historicalPayment.SfaContributionPercentage);
     listOfCalculatedRequiredLevyAmounts.StartDate.Should().Be(historicalPayment.StartDate);
     listOfCalculatedRequiredLevyAmounts.TransactionType.Should().Be(historicalPayment.TransactionType);
     listOfCalculatedRequiredLevyAmounts.TransferSenderAccountId.Should().Be(historicalPayment.TransferSenderAccountId);
     listOfCalculatedRequiredLevyAmounts.Ukprn.Should().Be(historicalPayment.Ukprn);
 }