public Task Begin()
 {
     logger.LogVerbose($"Creating state manager transaction.");
     ((ReliableStateManagerTransactionProvider)reliableStateManagerTransactionProvider).Current = stateManager.CreateTransaction();
     logger.LogDebug($"Creating state manager transaction.Transaction Id: {reliableStateManagerTransactionProvider.Current.TransactionId}");
     return(Task.CompletedTask);
 }
예제 #2
0
        public async Task ProcessedJobMessage(long jobId, Guid messageId, string messageName, List <GeneratedMessage> generatedMessages)
        {
            try
            {
                logger.LogVerbose($"Sending request to record successful processing of event. Job Id: {jobId}, Event: id: {messageId} ");
                var itemProcessedEvent = new RecordJobMessageProcessingStatus
                {
                    JobId             = jobId,
                    Id                = messageId,
                    MessageName       = messageName,
                    EndTime           = DateTimeOffset.UtcNow,
                    GeneratedMessages = generatedMessages ?? new List <GeneratedMessage>(),
                    Succeeded         = true,
                };

                var partitionedEndpointName = config.GetMonitoringEndpointName(jobId);
                await messageSession.Send(partitionedEndpointName, itemProcessedEvent).ConfigureAwait(false);

                logger.LogDebug($"Sent request to record successful processing of event. Job Id: {jobId}, Event: id: {messageId} ");
            }
            catch (Exception ex)
            {
                logger.LogWarning($"Failed to send the job status message. Job: {jobId}, Message: {messageId}, {messageName}, Error: {ex.Message}, {ex}");
            }
        }
예제 #3
0
        public async Task Reset()
        {
            paymentLogger.LogVerbose($"Resetting actor for id {Id}");
            await apprenticeships.ResetInitialiseFlag().ConfigureAwait(false);

            paymentLogger.LogInfo($"Reset actor for Id {Id}");
        }
예제 #4
0
        public async Task <int> Process(int batchSize, CancellationToken cancellationToken)
        {
            logger.LogVerbose("Processing batch.");
            var batch = await cache.GetPayments(batchSize, cancellationToken).ConfigureAwait(false);

            if (batch.Count < 1)
            {
                logger.LogVerbose("No records found to process.");
                return(0);
            }

            using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled))
            {
                try
                {
                    foreach (var item in batch)
                    {
                        logger.LogVerbose($"Saving {typeof(T).Name} to table: {item.ToString()}");
                        await bulkWriter.Write(item, cancellationToken).ConfigureAwait(false);
                    }

                    await bulkWriter.Flush(cancellationToken).ConfigureAwait(false);

                    scope.Complete();
                }
                catch (Exception e)
                {
                    logger.LogError($"Error performing bulk copy for model type: {typeof(T).Name}. Error: {e.Message}", e);
                    throw;
                }
            }

            return(batch.Count);
        }
예제 #5
0
        public async Task Handle(ApprenticeshipContractType1EarningEvent message, IMessageHandlerContext context)
        {
            if (message.Learner == null || message.Learner?.Uln == 0)
            {
                throw new InvalidOperationException("Invalid 'ApprenticeshipContractType1EarningEvent' received. Learner was null or Uln was 0.");
            }
            var uln        = message.Learner.Uln;
            var learnerRef = message.Learner.ReferenceNumber;

            logger.LogDebug($"Processing DataLockProxyProxyService event for learner with learner ref {learnerRef}");
            var actorId = new ActorId(uln.ToString());

            logger.LogVerbose($"Creating actor proxy for learner with learner ref {learnerRef}");
            var actor = proxyFactory.CreateActorProxy <IDataLockService>(new Uri("fabric:/SFA.DAS.Payments.DataLocks.ServiceFabric/DataLockServiceActorService"), actorId);

            logger.LogDebug($"Actor proxy created for learner with " +
                            $"JobId: {message.JobId} and LearnRefNumber: {learnerRef}");

            logger.LogVerbose($"Calling actor proxy to handle earning for learner with learner ref {learnerRef}");
            var dataLockEvents = await actor.HandleEarning(message, CancellationToken.None).ConfigureAwait(false);

            logger.LogDebug($"Earning handled for learner with learner ref {learnerRef}");

            if (dataLockEvents != null)
            {
                var summary = string.Join(", ", dataLockEvents.GroupBy(e => e.GetType().Name).Select(g => $"{g.Key}: {g.Count()}"));
                logger.LogVerbose($"Publishing data lock event for learner with learner ref {learnerRef}: {summary}");
                await Task.WhenAll(dataLockEvents.Select(context.Publish)).ConfigureAwait(false);

                logger.LogDebug($"Data lock event published for learner with learner ref {learnerRef}");
            }

            logger.LogInfo($"Successfully processed DataLockProxyProxyService event for Actor for learner {learnerRef}");
        }
        public async Task Handle(ReceivedProviderEarningsEvent message, CancellationToken cancellationToken)
        {
            logger.LogVerbose($"Handling ILR Submissions. Data: {message.ToJson()}");
            var currentIlr = await GetCurrentIlrSubmissionEvent(message.Ukprn, cancellationToken);

            var isNewIlrSubmission = validateIlrSubmission.IsNewIlrSubmission(new IlrSubmissionValidationRequest
            {
                IncomingPaymentUkprn          = message.Ukprn,
                IncomingPaymentSubmissionDate = message.IlrSubmissionDateTime,
                CurrentIlr = currentIlr
            });

            if (!isNewIlrSubmission)
            {
                logger.LogInfo($"Ignored same Ilr Submission Data for Ukprn {message.Ukprn} and Job Id {message.JobId} Submission already processed");
                return;
            }

            logger.LogInfo($"Updating current Ilr Submission Data for Ukprn {message.Ukprn} and Job Id {message.JobId}");

            await ilrSubmittedEventCache.Clear(message.Ukprn.ToString(), cancellationToken);

            await ilrSubmittedEventCache.Add(message.Ukprn.ToString(), message, cancellationToken);

            logger.LogDebug($"Successfully Updated current Ilr Submission Data for Ukprn {message.Ukprn} and Job Id {message.JobId}");

            await providerPaymentsRepository.DeleteOldMonthEndPayment(message.CollectionPeriod,
                                                                      message.Ukprn,
                                                                      message.IlrSubmissionDateTime,
                                                                      cancellationToken);

            logger.LogInfo($"Successfully Deleted Old Month End Payment for Ukprn {message.Ukprn} and Job Id {message.JobId}");
        }
예제 #7
0
        public async Task Handle(DataLockEvent message, IMessageHandlerContext context)
        {
            paymentLogger.LogVerbose($"Processing {message.GetType().Name} event for UKPRN {message.Ukprn}");
            await manageReceivedDataLockEvent.ProcessDataLockEvent(message);

            paymentLogger.LogVerbose($"Successfully Processed {message.GetType().Name} event for UKPRN {message.Ukprn}");
        }
        public async Task HandleSubmissionSucceeded(short academicYear, byte collectionPeriod, long ukprn, DateTime submissionTime, long jobId, CancellationToken cancellationToken)
        {
            logger.LogVerbose($"Handling Submission Succeeded. Data: Ukprn: {ukprn}, Academic Year: {academicYear}, Collection Period: {collectionPeriod}, Submission Time {submissionTime}");
            await providerPaymentsRepository.DeleteOldMonthEndPayment(new CollectionPeriod { AcademicYear = academicYear, Period = collectionPeriod },
                                                                      ukprn,
                                                                      submissionTime,
                                                                      cancellationToken);

            logger.LogInfo($"Successfully Deleted Old Month End Payment for Ukprn: {ukprn}, Academic Year: {academicYear}, Collection Period: {collectionPeriod}, Submission Time {submissionTime} and Job Id {jobId}");
        }
예제 #9
0
        private ProviderPaymentEvent MapToProviderPaymentEvent(PaymentModel payment, long monthEndJobId)
        {
            paymentLogger.LogVerbose($"Mapping payment id: {payment.Id}, funding source: {payment.FundingSource}");
            var providerPayment = paymentFactory.Create(payment.FundingSource);

            paymentLogger.LogVerbose($"Got {providerPayment.GetType().Name} payment message type. Now mapping to provider payment.");
            mapper.Map(payment, providerPayment);
            providerPayment.JobId = monthEndJobId;
            paymentLogger.LogVerbose($"Finished mapping payment. Id: {providerPayment.EventId}");
            return(providerPayment);
        }
예제 #10
0
        public async Task StartMonthEnd(long ukprn, short academicYear, byte collectionPeriod, long monthEndJobId)
        {
            logger.LogVerbose($"Recoding month end in the cache. Ukprn: {ukprn}, academic year: {academicYear}, collection period: {collectionPeriod}, Month End Job Id {monthEndJobId}");
            await monthEndCache.AddOrReplace(ukprn, academicYear, collectionPeriod, monthEndJobId).ConfigureAwait(false);

            logger.LogDebug($"Recoded month end in the cache. Ukprn: {ukprn}, academic year: {academicYear}, collection period: {collectionPeriod} , Month End Job Id {monthEndJobId}");

            logger.LogVerbose($"Flushing model cache. Ukprn: {ukprn}, academic year: {academicYear}, collection period: {collectionPeriod}, Month End Job Id {monthEndJobId}");
            await batchService.StorePayments(CancellationToken.None);

            logger.LogDebug($"Model cache flushed. Ukprn: {ukprn}, academic year: {academicYear}, collection period: {collectionPeriod} , Month End Job Id {monthEndJobId}");
        }
        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());
        }
        public async Task <bool> WaitForJobToFinish(long jobId, CancellationToken cancellationToken)
        {
            //TODO: Temp brittle solution to wait for jobs to finish
            logger.LogDebug($"Waiting for job {jobId} to finish.");
            var endTime = DateTime.Now.Add(config.TimeToWaitForJobToComplete);

            while (DateTime.Now < endTime)
            {
                cancellationToken.ThrowIfCancellationRequested();
                var job = await dataContext.GetJobByDcJobId(jobId).ConfigureAwait(false);

                if (job != null && (job.DataLocksCompletionTime != null ||
                                    job.Status != Monitoring.Jobs.Model.JobStatus.InProgress))
                {
                    logger.LogInfo($"DC Job {jobId} finished. Status: {job.Status:G}.  Finish time: {job.EndTime:G}");
                    return(true);
                }
                logger.LogVerbose($"DC Job {jobId} is still in progress");
                await Task.Delay(config.TimeToPauseBetweenChecks);

                continue;
            }
            logger.LogWarning($"Waiting {config.TimeToWaitForJobToComplete} but Job {jobId} still not finished.");
            return(false);
        }
예제 #13
0
        private async Task CheckJobStatus(string partitionEndpointName, long jobId)
        {
            try
            {
                using (var scope = scopeFactory.Create($"CheckJobStatus:{jobId}, ThreadId {Thread.CurrentThread.ManagedThreadId}, PartitionId {partitionEndpointName}"))
                {
                    try
                    {
                        var jobStatusService = GetJobStatusService(scope);
                        var finished         = await jobStatusService.ManageStatus(jobId, cancellationToken).ConfigureAwait(false);

                        logger.LogVerbose($"Job: {jobId},  finished: {finished}");
                        await scope.Commit();

                        currentJobs[jobId] = finished;
                    }
                    catch (Exception ex)
                    {
                        scope.Abort();
                        logger.LogWarning($"Failed to update job status for job: {jobId}, ThreadId {Thread.CurrentThread.ManagedThreadId}, PartitionId {partitionEndpointName} Error: {ex.Message}. {ex}");
                    }
                }
            }
            catch (Exception e)
            {
                logger.LogError($"Failed to create or abort the scope of the state manager transaction for: {jobId}, ThreadId {Thread.CurrentThread.ManagedThreadId}, PartitionId {partitionEndpointName} Error: {e.Message}", e);
                throw;
            }
        }
예제 #14
0
        public async Task Handle(ResetActorsCommand message, IMessageHandlerContext context)
        {
            logger.LogDebug("Resetting datalock actors.");
            var resetTasks = new List <Task>();

            foreach (var uln in message.Ulns)
            {
                var actorId = new ActorId(uln);
                logger.LogVerbose($"Creating actor proxy, actor id: {uln}.");
                var actor = proxyFactory.CreateActorProxy <IDataLockService>(new Uri("fabric:/SFA.DAS.Payments.DataLocks.ServiceFabric/DataLockServiceActorService"), actorId);
                logger.LogVerbose($"Actor proxy created. Actor id: {uln}, now resetting the cache.");
                resetTasks.Add(actor.Reset());
            }

            await Task.WhenAll(resetTasks).ConfigureAwait(false);

            logger.LogInfo("Finished resetting the datalock actors");
        }
        public async Task StartJob(long jobId, long ukprn, DateTime ilrSubmissionTime, short collectionYear, byte collectionPeriod, List <GeneratedMessage> generatedMessages, DateTimeOffset startTime)
        {
            logger.LogVerbose($"Sending request to record start of earnings job. Job Id: {jobId}, Ukprn: {ukprn}");
            try
            {
                var batchSize = 1000; //TODO: this should come from config
                List <GeneratedMessage> batch;
                var providerEarningsEvent = new RecordEarningsJob
                {
                    StartTime         = startTime,
                    JobId             = jobId,
                    Ukprn             = ukprn,
                    IlrSubmissionTime = ilrSubmissionTime,
                    CollectionYear    = collectionYear,
                    CollectionPeriod  = collectionPeriod,
                    GeneratedMessages = generatedMessages.Take(batchSize).ToList(),
                    LearnerCount      = generatedMessages.Count
                };

                var jobsEndpointName        = config.GetSettingOrDefault("Monitoring_JobsService_EndpointName", "sfa-das-payments-monitoring-jobs");
                var partitionedEndpointName = $"{jobsEndpointName}{partitionName.PartitionNameForJob(jobId, ukprn)}";
                logger.LogVerbose($"Endpoint for RecordEarningsJob for Job Id {jobId} is `{partitionedEndpointName}`");
                await messageSession.Send(partitionedEndpointName, providerEarningsEvent).ConfigureAwait(false);

                var skip = batchSize;

                while ((batch = generatedMessages.Skip(skip).Take(1000).ToList()).Count > 0)
                {
                    skip += batchSize;
                    var providerEarningsAdditionalMessages = new RecordEarningsJobAdditionalMessages
                    {
                        JobId             = jobId,
                        GeneratedMessages = batch,
                    };
                    await messageSession.Send(partitionedEndpointName, providerEarningsAdditionalMessages).ConfigureAwait(false);
                }
                logger.LogDebug($"Sent request(s) to record start of earnings job. Job Id: {jobId}, Ukprn: {ukprn}");
            }
            catch (Exception ex)
            {
                logger.LogError($"Failed to send the request to record the earnings job. Job: {jobId}, Error: {ex.Message}", ex);
                throw;
            }
        }
        public async Task RecordCompletedJobMessageStatus(RecordJobMessageProcessingStatus jobMessageStatus, CancellationToken cancellationToken)
        {
            var completedMessage = new CompletedMessage
            {
                MessageId     = jobMessageStatus.Id, JobId = jobMessageStatus.JobId,
                CompletedTime = jobMessageStatus.EndTime, Succeeded = jobMessageStatus.Succeeded
            };

            logger.LogVerbose($"Now storing the completed message. Message id: {completedMessage.MessageId}, Job: {completedMessage.JobId}, End time: {completedMessage.CompletedTime}, Succeeded: {completedMessage.Succeeded}");
            await jobStorageService.StoreCompletedMessage(completedMessage, cancellationToken);

            logger.LogVerbose($"Stored completed message. Now storing {jobMessageStatus.GeneratedMessages.Count} in progress messages generated while processing message: {completedMessage.MessageId} for job: {completedMessage.JobId}");
            await jobStorageService.StoreInProgressMessages(jobMessageStatus.JobId,
                                                            jobMessageStatus.GeneratedMessages.Select(message => new InProgressMessage
            {
                MessageId = message.MessageId, JobId = jobMessageStatus.JobId, MessageName = message.MessageName
            }).ToList(), cancellationToken);

            logger.LogDebug($"Recorded completion of message processing.  Job Id: {jobMessageStatus.JobId}, Message id: {jobMessageStatus.Id}.");
        }
        public async Task Handle(ResetCacheCommand message, IMessageHandlerContext context)
        {
            logger.LogDebug($"Resetting cache for provider :{message.Ukprn}");
            var ulns = await repository.ApprenticeshipUlnsByProvider(message.Ukprn);

            var resetTasks = new List <Task>();

            foreach (var uln in ulns)
            {
                var actorId = new ActorId(uln);
                logger.LogVerbose($"Creating actor proxy for actor id: {uln}");
                var actor = proxyFactory.CreateActorProxy <IDataLockService>(new Uri("fabric:/SFA.DAS.Payments.DataLocks.ServiceFabric/DataLockServiceActorService"), actorId);
                logger.LogVerbose($"Actor proxy created, now resetting the cache.");
                resetTasks.Add(actor.Reset());
            }

            await Task.WhenAll(resetTasks).ConfigureAwait(false);

            logger.LogInfo($"Finished resetting the cache for provider: {message.Ukprn}");
        }
        public async Task Process(SubmissionJobFailed message, CancellationToken cancellationToken)
        {
            logger.LogVerbose($"Flushing cached earning events before removing data for job: {message.JobId}, provider: {message.Ukprn}, collection period: {message.CollectionPeriod}");
            await batchService.StorePayments(cancellationToken).ConfigureAwait(false);

            logger.LogDebug($"Flushed data. Now removing earning events for job: {message.JobId}, provider: {message.Ukprn}, collection period: {message.CollectionPeriod}");
            await repository.RemoveFailedSubmissionEvents(message.JobId, cancellationToken)
            .ConfigureAwait(false);

            logger.LogInfo($"Finished removing earning events for job: {message.JobId}, provider: {message.Ukprn}, collection period: {message.CollectionPeriod}");
        }
예제 #19
0
        public async Task ProcessedJobMessage(long jobId, Guid messageId, string messageName, List <GeneratedMessage> generatedMessages)
        {
            try
            {
                logger.LogVerbose($"Sending request to record successful processing of event. Job Id: {jobId}, Event: id: {messageId} ");

                var batchSize = 1000; //TODO: this should come from config
                List <GeneratedMessage> batch;

                var itemProcessedEvent = new RecordJobMessageProcessingStatus
                {
                    JobId             = jobId,
                    Id                = messageId,
                    MessageName       = messageName,
                    EndTime           = DateTimeOffset.UtcNow,
                    GeneratedMessages = generatedMessages.Take(batchSize).ToList() ?? new List <GeneratedMessage>(),
                    Succeeded         = true,
                };

                var partitionedEndpointName = config.GetMonitoringEndpointName(jobId);
                await messageSession.Send(partitionedEndpointName, itemProcessedEvent).ConfigureAwait(false);

                var skip = batchSize;
                while ((batch = generatedMessages.Skip(skip).Take(batchSize).ToList()).Count > 0)
                {
                    skip += batchSize;
                    var providerEarningsAdditionalMessages = new RecordJobAdditionalMessages
                    {
                        JobId             = jobId,
                        GeneratedMessages = batch,
                    };
                    await messageSession.Send(partitionedEndpointName, providerEarningsAdditionalMessages).ConfigureAwait(false);
                }
                logger.LogDebug(
                    $"Sent request to record successful processing of event. Job Id: {jobId}, Event: id: {messageId} ");
            }
            catch (Exception ex)
            {
                logger.LogWarning($"Failed to send the job status message. Job: {jobId}, Message: {messageId}, {messageName}, Error: {ex.Message}, {ex}");
            }
        }
        public async Task Process(SubmissionJobSucceeded message, CancellationToken cancellationToken)
        {
            logger.LogVerbose($"Flushing cached earning events before removing old data for provider. Job: {message.JobId}, provider: {message.Ukprn}, collection period: {message.CollectionPeriod}");
            await batchService.StorePayments(cancellationToken).ConfigureAwait(false);

            logger.LogDebug($"Flushed cache. Now removing old earning events for provider. Job: {message.JobId}, provider: {message.Ukprn}, collection period: {message.CollectionPeriod}");
            await repository.RemovePriorEvents(message.Ukprn, message.AcademicYear, message.CollectionPeriod, message.IlrSubmissionDateTime,
                                               cancellationToken)
            .ConfigureAwait(false);

            logger.LogInfo($"Finished removing old earning events for provider. Job: {message.JobId}, provider: {message.Ukprn}, collection period: {message.CollectionPeriod}");
        }
예제 #21
0
        public async Task HandleRequiredPayment(CalculatedRequiredLevyAmount message)
        {
            try
            {
                using (var operation = telemetry.StartOperation("LevyFundedService.HandleRequiredPayment", message.EventId.ToString()))
                {
                    var stopwatch = Stopwatch.StartNew();
                    paymentLogger.LogVerbose($"Handling RequiredPayment for {Id}, Job: {message.JobId}, UKPRN: {message.Ukprn}, Account: {message.AccountId}");
                    await fundingSourceService.AddRequiredPayment(message).ConfigureAwait(false);

                    paymentLogger.LogInfo($"Finished handling required payment for {Id}, Job: {message.JobId}, UKPRN: {message.Ukprn}, Account: {message.AccountId}");
                    telemetry.TrackDuration("LevyFundedService.HandleRequiredPayment", stopwatch, message);
                    telemetry.StopOperation(operation);
                }
            }
            catch (Exception e)
            {
                paymentLogger.LogError($"Error handling required levy payment. Error:{e.Message}", e);
                throw;
            }
        }
예제 #22
0
        public async Task <ProviderPaymentEvent> GetPaymentEvent(FundingSourcePaymentEvent message)
        {
            var isMonthEnd = await providerPeriodEndService
                             .MonthEndStarted(message.Ukprn, message.CollectionPeriod.AcademicYear, message.CollectionPeriod.Period)
                             .ConfigureAwait(false);

            if (!isMonthEnd)
            {
                return(null);
            }

            paymentLogger.LogVerbose($"Processing Month End for {message.GetType().Name} Ukprn {message.Ukprn} - AcademicYear {message.CollectionPeriod.AcademicYear} - Period {message.CollectionPeriod.Period}");

            var monthEndJobId = await providerPeriodEndService.GetMonthEndJobId(message.Ukprn,
                                                                                message.CollectionPeriod.AcademicYear,
                                                                                message.CollectionPeriod.Period)
                                .ConfigureAwait(false);

            var payment = MapToProviderPaymentEvent(message, monthEndJobId, message.EventId);

            return(payment);
        }
        public async Task <int> Process(int batchSize, CancellationToken cancellationToken)
        {
            logger.LogVerbose("Processing batch.");
            var batch = await cache.GetPayments(batchSize, cancellationToken).ConfigureAwait(false);

            if (batch.Count < 1)
            {
                logger.LogVerbose("No records found to process.");
                return(0);
            }

            try
            {
                using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled))
                {
                    foreach (var changeEvent in batch)
                    {
                        await SaveDataLockEvent(cancellationToken, changeEvent);

                        foreach (var period in changeEvent.Periods)
                        {
                            await SaveEventPeriods(period, changeEvent, cancellationToken);
                        }

                        foreach (var commitment in changeEvent.CommitmentVersions)
                        {
                            await SaveCommitmentVersion(commitment, changeEvent, cancellationToken);
                        }

                        foreach (var error in changeEvent.Errors)
                        {
                            await SaveErrorCode(error, changeEvent, cancellationToken);
                        }

                        logger.LogDebug(
                            $"Saved PriceEpisodeStatusChange event {changeEvent.DataLock.DataLockEventId} for UKPRN {changeEvent.DataLock.UKPRN}. " +
                            $"Commitment versions: {changeEvent.CommitmentVersions.Length}, " +
                            $"periods: {changeEvent.Periods.Length}, errors: {changeEvent.Errors.Length}");
                    }

                    await dataLockEventWriter.Flush(cancellationToken);

                    await dataLockEventCommitmentVersionWriter.Flush(cancellationToken);

                    await dataLockEventPeriodWriter.Flush(cancellationToken);

                    await dataLockEventErrorWriter.Flush(cancellationToken);

                    scope.Complete();
                }
            }
            catch (Exception e)
            {
                logger.LogError($"Error saving batch of DataLockStatusChanged events. Error: {e.Message}", e);
                throw;
            }

            return(batch.Count);
        }
        public async Task <ReadOnlyCollection <FundingSourcePaymentEvent> > HandleMonthEnd(
            ProcessLevyPaymentsOnMonthEndCommand command)
        {
            paymentLogger.LogVerbose(
                $"Handling ProcessLevyPaymentsOnMonthEndCommand for {Id}, Job: {command.JobId}, Account: {command.AccountId}");
            try
            {
                using (var operation =
                           telemetry.StartOperation("LevyFundedService.HandleMonthEnd", command.CommandId.ToString()))
                {
                    var stopwatch = Stopwatch.StartNew();


                    var fundingSourceEvents =
                        await fundingSourceEventGenerationService.HandleMonthEnd(command.AccountId, command.JobId, command.CollectionPeriod);

                    await monthEndCache.AddOrReplace(CacheKeys.MonthEndStartedForThisAccountCacheKey, true, CancellationToken.None);

                    telemetry.TrackDurationWithMetrics("LevyFundedService.HandleMonthEnd",
                                                       stopwatch,
                                                       command,
                                                       command.AccountId,
                                                       new Dictionary <string, double>
                    {
                        { TelemetryKeys.Count, fundingSourceEvents.Count }
                    });

                    telemetry.StopOperation(operation);
                    return(fundingSourceEvents);
                }
            }
            catch (Exception ex)
            {
                paymentLogger.LogError($"Failed to get levy or co-invested month end payments. Error: {ex.Message}",
                                       ex);
                throw;
            }
        }
예제 #25
0
        public async Task StartPeriodEndJob <T>(T periodEndJob) where T : RecordPeriodEndJob
        {
            var jobName = periodEndJob.GetType().Name;

            logger.LogDebug($"Sending request to record start of {jobName}. Job Id: {periodEndJob.JobId}, collection period: {periodEndJob.CollectionYear}-{periodEndJob.CollectionPeriod}");

            var partitionedEndpointName = config.GetMonitoringEndpointName(periodEndJob.JobId);

            logger.LogVerbose($"Endpoint for PeriodEndJobClient for {jobName} with Job Id {periodEndJob.JobId} is `{partitionedEndpointName}`");

            await messageSession.Send(partitionedEndpointName, periodEndJob).ConfigureAwait(false);

            logger.LogInfo($"Sent request to record period end job: {jobName}. Job Id: {periodEndJob.JobId}, collection period: {periodEndJob.CollectionYear}-{periodEndJob.CollectionPeriod}");
        }
예제 #26
0
        public async Task <ReadOnlyCollection <PeriodisedRequiredPaymentEvent> > HandleApprenticeship2ContractTypeEarningsEvent(ApprenticeshipContractType2EarningEvent earningEvent, CancellationToken cancellationToken)
        {
            paymentLogger.LogVerbose($"Handling ApprenticeshipContractType2EarningEvent for jobId:{earningEvent.JobId} with apprenticeship key based on {logSafeApprenticeshipKeyString}");

            using (var operation = telemetry.StartOperation("RequiredPaymentsService.HandleApprenticeship2ContractTypeEarningsEvent", earningEvent.EventId.ToString()))
            {
                var stopwatch = Stopwatch.StartNew();
                await ResetPaymentHistoryCacheIfDifferentCollectionPeriod(earningEvent.CollectionPeriod)
                .ConfigureAwait(false);

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

                var requiredPaymentEvents = await contractType2EarningsEventProcessor.HandleEarningEvent(earningEvent, paymentHistoryCache, cancellationToken).ConfigureAwait(false);

                Log(requiredPaymentEvents);
                telemetry.TrackDuration("RequiredPaymentsService.HandleApprenticeship2ContractTypeEarningsEvent", stopwatch, earningEvent);
                telemetry.StopOperation(operation);
                return(requiredPaymentEvents);
            }
        }
예제 #27
0
        private async Task BatchUpdateLevyAccounts(List <LevyAccountModel> levyAccountModels, CancellationToken cancellationToken)
        {
            try
            {
                await Task.WhenAll(levyAccountModels.Select(x => levyAccountBulkWriter.Write(x, cancellationToken))).ConfigureAwait(false);

                await levyAccountBulkWriter.DeleteAndFlush(cancellationToken).ConfigureAwait(false);

                logger.LogVerbose($"Successfully Added  {levyAccountModels.Count} Batch of Levy Accounts Details");
            }
            catch (Exception e)
            {
                logger.LogError($"Error while Adding  {levyAccountModels.Count} Batch of Levy Accounts Details", e);
            }
        }
예제 #28
0
        protected async Task StartJob <T>(long jobId, short collectionYear, byte collectionPeriod, List <GeneratedMessage> generatedMessages) where T : RecordPeriodEndJob, new()
        {
            logger.LogDebug($"Sending request to record start of period end job. Job Id: {jobId}, collection period: {collectionYear}-{collectionPeriod}");
            var job = new T
            {
                JobId             = jobId,
                CollectionYear    = collectionYear,
                CollectionPeriod  = collectionPeriod,
                GeneratedMessages = generatedMessages
            };
            var partitionedEndpointName = config.GetMonitoringEndpointName(jobId);

            logger.LogVerbose($"Endpoint for PeriodEndJobClient for Job Id {jobId} is `{partitionedEndpointName}`");
            await messageSession.Send(partitionedEndpointName, job).ConfigureAwait(false);

            logger.LogInfo($"Sent request to record period end job. Job Id: {jobId}, collection period: {collectionYear}-{collectionPeriod}");
        }
        public async Task PerformExportPaymentsAndEarningsToV1(CollectionPeriod collectionPeriod)
        {
            logger.LogVerbose($"Started V1 payments export for collection period {collectionPeriod}");

            while (true)
            {
                int page = -1;
                try
                {
                    page = await paymentExportProgressCache.GetPage(collectionPeriod.AcademicYear, collectionPeriod.Period);

                    logger.LogVerbose($"Starting with page: {page}");

                    var payments = providerPaymentsRepository.GetMonthEndPayments(collectionPeriod, exportBatchSize, page);

                    if (payments.Count == 0)
                    {
                        logger.LogVerbose($"Finished exporting payments to V1 for collection period: {collectionPeriod}");
                        break;
                    }

                    logger.LogVerbose($"Found {payments.Count} payments to process");

                    var result = paymentMapper.MapV2Payments(payments);
                    await legacyPaymentsRepository
                    .WritePaymentInformation(result.payments, result.requiredPayments, result.earnings)
                    .ConfigureAwait(false);

                    logger.LogVerbose($"Completed write for page: {page} for collection period: {collectionPeriod}");

                    await paymentExportProgressCache
                    .IncrementPage(collectionPeriod.AcademicYear, collectionPeriod.Period)
                    .ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    logger.LogError($"Error processing page: {page} during V1 legacy export", e);
                    throw;
                }
            }

            logger.LogVerbose($"Completed V1 payments export for collection period {collectionPeriod}");
        }
        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());
        }