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}");
            if (await duplicateEarningEventService.IsDuplicate(identifiedRemovedLearningAim, cancellationToken))
            {
                logger.LogWarning($"Duplicate Identified Removed Learning Aim found for learner with JobId: {identifiedRemovedLearningAim.JobId}, " +
                                  $"Learner Ref Number: {identifiedRemovedLearningAim.Learner.ReferenceNumber}, Aim: {identifiedRemovedLearningAim.LearningAim.Reference}");
                return(new List <PeriodisedRequiredPaymentEvent>().AsReadOnly());
            }

            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());
        }
        public async Task <List <DataLockEvent> > GetPaymentEvents(ApprenticeshipContractType1EarningEvent earningEvent, CancellationToken cancellationToken)
        {
            var dataLockEvents = new List <DataLockEvent>();

            if (await duplicateEarningEventService.IsDuplicate(earningEvent, cancellationToken)
                .ConfigureAwait(false))
            {
                return(dataLockEvents);
            }

            var learnerMatchResult = await learnerMatcher.MatchLearner(earningEvent.Ukprn, earningEvent.Learner.Uln).ConfigureAwait(false);

            if (learnerMatchResult.DataLockErrorCode.HasValue)
            {
                dataLockEvents = CreateDataLockEvents(earningEvent, learnerMatchResult.DataLockErrorCode.Value);
                return(dataLockEvents);
            }

            var apprenticeshipsForUln = learnerMatchResult.Apprenticeships;
            var onProgrammeEarning    = GetOnProgrammeEarnings(earningEvent, apprenticeshipsForUln);
            var incentiveEarnings     = GetIncentiveEarnings(earningEvent, apprenticeshipsForUln);

            if (onProgrammeEarning.validOnProgEarnings.Any())
            {
                var payableEarningEvent = mapper.Map <PayableEarningEvent>(earningEvent);
                payableEarningEvent.OnProgrammeEarnings = onProgrammeEarning.validOnProgEarnings;
                payableEarningEvent.IncentiveEarnings   = incentiveEarnings.validIncentiveEarnings;
                dataLockEvents.Add(payableEarningEvent);
            }

            if (onProgrammeEarning.invalidOnProgEarnings.Any())
            {
                var earningFailedDataLockEvent = mapper.Map <EarningFailedDataLockMatching>(earningEvent);
                earningFailedDataLockEvent.OnProgrammeEarnings = onProgrammeEarning.invalidOnProgEarnings;
                earningFailedDataLockEvent.IncentiveEarnings   = incentiveEarnings.invalidIncentiveEarning;
                dataLockEvents.Add(earningFailedDataLockEvent);
            }

            return(dataLockEvents);
        }
Пример #3
0
        public async Task <ReadOnlyCollection <PeriodisedRequiredPaymentEvent> > HandleEarningEvent(TEarningEvent earningEvent, IDataCache <PaymentHistoryEntity[]> paymentHistoryCache, CancellationToken cancellationToken)
        {
            if (earningEvent == null)
            {
                throw new ArgumentNullException(nameof(earningEvent));
            }

            try
            {
                var result = new List <PeriodisedRequiredPaymentEvent>();
                if (await duplicateEarningEventService.IsDuplicate(earningEvent, cancellationToken)
                    .ConfigureAwait(false))
                {
                    return(result.AsReadOnly());
                }
                var cachedPayments = await paymentHistoryCache.TryGet(CacheKeys.PaymentHistoryKey, cancellationToken);

                var academicYearPayments = cachedPayments.HasValue
                    ? cachedPayments.Value
                                           .Where(p => p.LearnAimReference.Equals(earningEvent.LearningAim.Reference, StringComparison.OrdinalIgnoreCase))
                                           .Select(p => mapper.Map <PaymentHistoryEntity, Payment>(p))
                                           .ToList()
                    : new List <Payment>();

                foreach (var(period, type) in GetPeriods(earningEvent))
                {
                    if (period.Period > earningEvent.CollectionPeriod.Period) // cut off future periods
                    {
                        continue;
                    }

                    if (period.Amount != 0 && !period.SfaContributionPercentage.HasValue)
                    {
                        throw new InvalidOperationException("Non-zero amount with no Sfa Contribution");
                    }

                    var payments = academicYearPayments.Where(payment => payment.DeliveryPeriod == period.Period &&
                                                              payment.TransactionType == type)
                                   .ToList();

                    List <RequiredPayment> requiredPayments;
                    var holdBackCompletionPayments = false;

                    if (NegativeEarningWillResultInARefund(period, payments))
                    {
                        requiredPayments = negativeEarningService
                                           .ProcessNegativeEarning(period.Amount, academicYearPayments, period.Period, period.PriceEpisodeIdentifier);
                    }
                    else
                    {
                        var earning = new Earning
                        {
                            Amount = period.Amount,
                            SfaContributionPercentage = period.SfaContributionPercentage,
                            EarningType            = GetEarningType(type),
                            PriceEpisodeIdentifier = period.PriceEpisodeIdentifier,
                            AccountId = period.AccountId,
                            TransferSenderAccountId = period.TransferSenderAccountId
                        };

                        requiredPayments = requiredPaymentProcessor.GetRequiredPayments(earning, payments);
                        if (requiredPayments.Count > 0)
                        {
                            holdBackCompletionPayments = await HoldBackCompletionPayments(earningEvent, earning, type, cancellationToken).ConfigureAwait(false);
                        }
                    }

                    if (requiredPayments.GroupBy(x => x.SfaContributionPercentage)
                        .All(x => x.Sum(y => y.Amount) == 0))
                    {
                        continue;
                    }

                    foreach (var requiredPayment in requiredPayments)
                    {
                        var requiredPaymentEvent = CreateRequiredPaymentEvent(requiredPayment.EarningType, type, holdBackCompletionPayments);
                        mapper.Map(period, requiredPaymentEvent);
                        mapper.Map(earningEvent, requiredPaymentEvent);
                        mapper.Map(requiredPayment, requiredPaymentEvent);
                        AddRefundCommitmentDetails(requiredPayment, requiredPaymentEvent);

                        var priceEpisodeIdentifier = requiredPaymentEvent.PriceEpisodeIdentifier;

                        if (earningEvent.PriceEpisodes != null && earningEvent.PriceEpisodes.Any())
                        {
                            var priceEpisode = earningEvent.PriceEpisodes.Count == 1
                                ? earningEvent.PriceEpisodes.FirstOrDefault()
                                : earningEvent.PriceEpisodes?.SingleOrDefault(x => x.Identifier == priceEpisodeIdentifier);

                            mapper.Map(priceEpisode, requiredPaymentEvent);

                            if (requiredPaymentEvent.LearningAim != null)
                            {
                                mapper.Map(priceEpisode, requiredPaymentEvent.LearningAim);
                            }
                        }

                        result.Add(requiredPaymentEvent);
                    }
                }
                return(result.AsReadOnly());
            }
            catch (Exception e)
            {
                paymentLogger.LogError($"Error while Handling EarningEvent for {earningEvent.Ukprn} ", e);
                throw;
            }
        }