public async Task Handle(PeriodEndStartedEvent message, IMessageHandlerContext context) { logger.LogDebug($"Received period end started event. Details: {message.ToJson()}"); var fabricClient = new FabricClient(); var serviceDescription = new DeleteServiceDescription(new Uri(ServiceNames.DatalockApprovalsService)) { ForceDelete = true, }; await fabricClient.ServiceManager.DeleteServiceAsync(serviceDescription); logger.LogInfo($"Finished period end started handler. Details: {message.ToJson()}"); }
public async Task Handle(PeriodEndStoppedEvent message, IMessageHandlerContext context) { logger.LogInfo($"Processing Month End Event for Message Id : {context.MessageId}"); var currentExecutionContext = (ESFA.DC.Logging.ExecutionContext)executionContext; currentExecutionContext.JobId = message.JobId.ToString(); logger.LogDebug($"Processing period end event. Collection: {message.CollectionPeriod.Period:00}-{message.CollectionPeriod.AcademicYear}, job: {message.JobId}"); var commands = await periodEndService.GenerateProviderMonthEndCommands(message).ConfigureAwait(false); if (!commands.Any()) { logger.LogWarning($"No Provider Ukprn found for period end payment {message.CollectionPeriod.Period:00}-{message.CollectionPeriod.AcademicYear}, job: {message.JobId}"); return; } foreach (var command in commands) { logger.LogDebug($"Sending month end command for provider: {command.Ukprn}"); await context.SendLocal(command).ConfigureAwait(false); } logger.LogInfo($"Successfully processed Period End Event for {message.CollectionPeriod.Period:00}-{message.CollectionPeriod.AcademicYear}, job: {message.JobId}"); }
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); } logger.LogWarning($"Waiting {config.TimeToWaitForJobToComplete} but Job {jobId} still not finished."); return(false); }
public async Task Process(ApprenticeshipCreatedEvent createdEvent) { try { logger.LogDebug($"Now processing the apprenticeship created event. " + $"Apprenticeship id: {createdEvent.ApprenticeshipId}, " + $"employer account id: {createdEvent.AccountId}, " + $"Ukprn: {createdEvent.ProviderId}."); var model = mapper.Map <ApprenticeshipModel>(createdEvent); var duplicates = await apprenticeshipService.NewApprenticeship(model).ConfigureAwait(false); logger.LogDebug($"Apprenticeship saved to database. " + $"Apprenticeship id: {createdEvent.ApprenticeshipId}, " + $"employer account id: {createdEvent.AccountId}, " + $"Ukprn: {createdEvent.ProviderId}."); var updatedEvent = mapper.Map <ApprenticeshipUpdated>(model); updatedEvent.Duplicates = duplicates.Select(duplicate => new ApprenticeshipDuplicate { Ukprn = duplicate.Ukprn, ApprenticeshipId = duplicate.ApprenticeshipId }).ToList(); var endpointInstance = await endpointInstanceFactory.GetEndpointInstance().ConfigureAwait(false); await endpointInstance.Publish(updatedEvent).ConfigureAwait(false); logger.LogInfo($"Finished processing the apprenticeship created event. " + $"Apprenticeship id: {createdEvent.ApprenticeshipId}, " + $"employer account id: {createdEvent.AccountId}, " + $"Ukprn: {createdEvent.ProviderId}."); } catch (ApprenticeshipAlreadyExistsException e) { logger.LogWarning($"Apprenticeship already exists while trying to add a new apprenticeship: {e.Message}\n" + $"Apprenticeship id: {createdEvent.ApprenticeshipId}, " + $"employer account id: {createdEvent.AccountId}, " + $"Ukprn: {createdEvent.ProviderId}."); } catch (InvalidOperationException e) { logger.LogError($"Unhandled exception while adding apprenticeship: {e.Message}\n" + $"Apprenticeship id: {createdEvent.ApprenticeshipId}, " + $"employer account id: {createdEvent.AccountId}, " + $"Ukprn: {createdEvent.ProviderId}.", e); throw; } catch (Exception ex) { logger.LogError($"Error processing the apprenticeship event. Error: {ex.Message}", ex); throw; } }
public async Task ProcessEasAtMonthEnd(long jobId, int academicYear, int collectionPeriod) { var properties = new Dictionary <string, string> { { TelemetryKeys.JobId, jobId.ToString() }, { TelemetryKeys.CollectionPeriod, collectionPeriod.ToString() }, { TelemetryKeys.AcademicYear, academicYear.ToString() }, }; try { logger.LogInfo("Started the Provider Adjustments Processor."); var historicPayments = await repository.GetPreviousProviderAdjustments(academicYear).ConfigureAwait(false); logger.LogInfo($"Found {historicPayments.Count} historic payments"); var currentPayments = await repository.GetCurrentProviderAdjustments(academicYear).ConfigureAwait(false); logger.LogInfo($"Found {currentPayments.Count} payments from API"); var payments = calculator.CalculateDelta(historicPayments, currentPayments, academicYear, collectionPeriod); logger.LogInfo($"Calculated {payments.Count} new payments"); await repository.AddProviderAdjustments(payments).ConfigureAwait(false); logger.LogInfo("Finished the Provider Adjustments Processor."); SendEASTelemetry(true, properties, historicPayments, currentPayments); } catch (Exception) { SendEASTelemetry(false, properties); throw; } }
public async Task Handle(IList <RecordPeriodEndSubmissionWindowValidationJob> messages, CancellationToken cancellationToken) { foreach (var message in messages) { var stopwatch = new Stopwatch(); stopwatch.Start(); logger.LogInfo($"Handling period end submission window validation job: {message.ToJson()}"); await periodEndJobService.RecordPeriodEndJob(message, cancellationToken); var metricsValid = await submissionWindowValidationClient.Validate(message.JobId, message.CollectionYear, message.CollectionPeriod); var jobStatus = metricsValid ? JobStatus.Completed : JobStatus.CompletedWithErrors; await jobStorageService.SaveJobStatus(message.JobId, jobStatus, DateTimeOffset.Now, cancellationToken); logger.LogInfo($"Handled period end submission window validation job: {message.JobId}"); stopwatch.Stop(); SendTelemetry(message, jobStatus, stopwatch.Elapsed); } }
/// <summary> /// This is the main entry point for your service instance. /// </summary> /// <param name="cancellationToken">Canceled when Service Fabric needs to shut down this service instance.</param> protected override async Task RunAsync(CancellationToken cancellationToken) { var initialised = false; try { logger.LogDebug("Starting the Provider Ajustments service."); initialised = true; logger.LogInfo("Started the Provider Adjustments service."); await Task.Delay(Timeout.Infinite, cancellationToken); } catch (Exception exception) when(!(exception is TaskCanceledException)) { // Ignore, as an exception is only really thrown on cancellation of the token. logger.LogError($"Reference Data Stateless Service Exception. Error: {exception}.", exception); } finally { if (initialised) { logger.LogInfo("Provider Adjustments Stateless Service End"); } } }
public async Task Handle(ProcessProviderMonthEndAct1CompletionPaymentCommand message, IMessageHandlerContext context) { paymentLogger.LogInfo($"Processing Provider Month End Act1 Completion Payment Command with ukprn: {message.Ukprn}, Message Id : {context.MessageId}"); var paymentEvents = await completionPaymentService.GetAct1CompletionPaymentEvents(message); if (!paymentEvents.Any()) { paymentLogger.LogWarning($"No Act1 Completion Payment Event Found for ukprn: {message.Ukprn}, Collection: {message.CollectionPeriod.Period:00}-{message.CollectionPeriod.AcademicYear}, job: {message.JobId}"); return; } var dasEndPoint = await dasEndpointFactory.GetEndpointInstanceAsync(); foreach (var paymentEvent in paymentEvents) { paymentLogger.LogDebug($"Processing Act1 Completion Payment Event. Ukprn: {message.Ukprn}, Collection: {message.CollectionPeriod.Period:00}-{message.CollectionPeriod.AcademicYear}, job: {message.JobId}"); await dasEndPoint.Publish(paymentEvent); } paymentLogger.LogInfo($"Successfully Processed Month End Act1 Completion Payment Command for ukprn: {message.Ukprn}, Collection:{message.CollectionPeriod.Period:00}-{message.CollectionPeriod.AcademicYear}, job: {message.JobId}"); }
/// <summary> /// Opens the asynchronous. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> public async Task <string> OpenAsync(CancellationToken cancellationToken) { try { logger.LogDebug($"Opening endpoint: {config.EndpointName}"); endpointInstance = await endpointInstanceFactory.GetEndpointInstance().ConfigureAwait(false); logger.LogInfo($"Finished opening endpoint listener: {config.EndpointName}"); return(config.EndpointName); } catch (Exception e) { logger.LogFatal($"Cannot start the endpoint: '{config.EndpointName}'. Error: {e.Message}", e); throw; } }
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.ToString()); var actor = proxyFactory.CreateActorProxy <IDataLockService>(new Uri("fabric:/SFA.DAS.Payments.DataLocks.ServiceFabric/DataLockServiceActorService"), actorId); resetTasks.Add(actor.Reset()); } await Task.WhenAll(resetTasks).ConfigureAwait(false); logger.LogInfo("Finished resetting the datalock actors"); }
public async Task StoreDataLocks(List <DataLockEventModel> models, CancellationToken cancellationToken) { try { await repository.SaveDataLockEvents(models, cancellationToken); } catch (Exception e) { if (!e.IsUniqueKeyConstraintException() && !e.IsDeadLockException()) { throw; } logger.LogInfo($"Batch contained a duplicate DataLock. Will store each individually and discard duplicate."); await repository.SaveDataLocksIndividually(models.Select(model => mapper.Map(model)).ToList(), cancellationToken).ConfigureAwait(false); } }
public async Task <bool> HandleAsync(JobContextMessage message, CancellationToken cancellationToken) { try { logger.LogDebug("Getting task type from period end message."); var taskType = GetTask(message); logger.LogDebug("Got period end type now create the period end event."); var periodEndEvent = CreatePeriodEndEvent(taskType); logger.LogDebug($"Created period end event. Type: {periodEndEvent.GetType().Name}"); periodEndEvent.JobId = message.JobId; periodEndEvent.CollectionPeriod = new CollectionPeriod { AcademicYear = Convert.ToInt16(GetMessageValue(message, JobContextMessageConstants.KeyValuePairs.CollectionYear)), Period = Convert.ToByte(GetMessageValue(message, JobContextMessageConstants.KeyValuePairs.ReturnPeriod)) }; logger.LogDebug($"Got period end event: {periodEndEvent.ToJson()}"); await RecordPeriodEndJob(taskType, periodEndEvent).ConfigureAwait(false); var endpointInstance = await endpointInstanceFactory.GetEndpointInstance(); await endpointInstance.Publish(periodEndEvent); logger.LogInfo($"Finished publishing the period end event. Name: {periodEndEvent.GetType().Name}, JobId: {periodEndEvent.JobId}, Collection Period: {periodEndEvent.CollectionPeriod.Period}-{periodEndEvent.CollectionPeriod.AcademicYear}."); // TODO: This is a temporary workaround to enable the PeriodEndStart and PeriodEndStop messages to return true as otherwise the service will // TODO: just hang as there is nothing implemented to handle the Start and Stop events and so the job status service will never get a completion and so this will never return true. // PV2-1345 will handle PeriodEndStart // PeriodEndStoppedEvent will be handled by the PeriodEndStoppedEventHandler which in turn is handled by the ProcessProviderMonthEndCommandHandler but we don't want to wait for it if (periodEndEvent is PeriodEndStartedEvent || periodEndEvent is PeriodEndStoppedEvent) { logger.LogDebug("Returning as this is either a PeriodEndStart or PeriodEndStop event"); return(true); } await jobStatusService.WaitForJobToFinish(message.JobId, cancellationToken); return(true); } catch (Exception ex) { logger.LogError($"Failed to process job context message. Message: {message.ToJson()}", ex); throw; } }
public async Task <List <ProviderAdjustment> > GetCurrentProviderAdjustments(int academicYear) { logger.LogInfo("Getting Current Provider Adjustments - Getting Token"); var token = await GetToken(); logger.LogInfo("Token retrieved"); var providerAdjustments = new List <ProviderAdjustment>(); var pageNumber = 1; while (true) { var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/Eas/{academicYear}?pagenumber={pageNumber}&pagesize={pageSize}"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); logger.LogInfo($"Getting page {pageNumber} of data from API"); var httpResponse = await client.SendAsync(request).ConfigureAwait(false); logger.LogInfo($"Successfully connected to the API"); var responseContent = await httpResponse.Content.ReadAsStringAsync(); logger.LogDebug($"Response code: {httpResponse.StatusCode}, Reason: {httpResponse.ReasonPhrase}, " + $"Request: {httpResponse.RequestMessage.RequestUri}"); if (httpResponse.IsSuccessStatusCode) { var batch = JsonConvert.DeserializeObject <List <ProviderAdjustment> >(responseContent); if (batch.Count == 0) { logger.LogInfo($"No messages on page {pageNumber}"); break; } logger.LogInfo($"Successfully retrieved {batch.Count} records from API"); providerAdjustments.AddRange(batch); } else { logger.LogError($"Error getting EAS records: {responseContent}, {httpResponse}"); throw new InvalidOperationException($"Error getting EAS records: {responseContent}"); } pageNumber++; } logger.LogInfo($"Finished reading records from the API. Got {providerAdjustments.Count} records"); return(providerAdjustments); }
public async Task Handle(IList <CalculatedRequiredLevyAmount> messages, CancellationToken cancellationToken) { logger.LogInfo($"Received {messages.Count} messages"); await levyTransactionBatchStorageService.StoreLevyTransactions(messages, cancellationToken) .ConfigureAwait(false); var monitoringClient = monitoringClientFactory.Create(); foreach (var calculatedRequiredLevyAmount in messages) { await monitoringClient.ProcessedJobMessage(calculatedRequiredLevyAmount.JobId, calculatedRequiredLevyAmount.EventId, calculatedRequiredLevyAmount.GetType().ToString(), new List <GeneratedMessage>() ); } }
public async Task <bool> IsDuplicate(IPaymentsEvent earningEvent, CancellationToken cancellationToken) { logger.LogDebug($"Checking if earning event of type {earningEvent.GetType().Name} with guid: {earningEvent.EventId} has already been received."); var earningEventKey = new EarningEventKey(earningEvent); logger.LogDebug($"Earning event key: {earningEventKey.LogSafeKey}"); if (await cache.Contains(earningEventKey.Key, cancellationToken).ConfigureAwait(false)) { logger.LogWarning($"Key: {earningEventKey.LogSafeKey} found in the cache and is probably a duplicate."); return(true); } logger.LogDebug($"New earnings event. Event key: {earningEventKey.LogSafeKey}, event id: {earningEvent.EventId}"); await cache.Add(earningEventKey.Key, earningEventKey, cancellationToken); logger.LogInfo($"Added new earnings event to cache. Key: {earningEventKey.LogSafeKey}"); return(false); }
public async Task Handle(FundingSourcePaymentEvent message, IMessageHandlerContext context) { paymentLogger.LogDebug($"Processing Funding Source Payment Event for Message Id : {context.MessageId}"); var paymentModel = mapper.Map <ProviderPaymentEventModel>(message); await paymentsService.ProcessPayment(paymentModel, default(CancellationToken)).ConfigureAwait(false); var afterMonthEndPayment = await afterMonthEndPaymentService.GetPaymentEvent(message); if (afterMonthEndPayment != null) { paymentLogger.LogInfo($"Sent {afterMonthEndPayment.GetType().Name} for {message.JobId} and Message Type {message.GetType().Name}"); paymentLogger.LogDebug($"Sending Provider Payment Event {JsonConvert.SerializeObject(afterMonthEndPayment)} for Message Id : {context.MessageId}. {message.ToDebug()}"); await context.Publish(afterMonthEndPayment).ConfigureAwait(false); } paymentLogger.LogDebug($"Finished processing Funding Source Payment Event for Message Id : {context.MessageId}. {message.ToDebug()}"); }
public async Task RecordPeriodEndJob(RecordPeriodEndJob periodEndJob, CancellationToken cancellationToken) { logger.LogDebug($"Sending request to record {periodEndJob.GetType().Name}. Job Id: {periodEndJob.JobId}, collection period: {periodEndJob.CollectionYear}-{periodEndJob.CollectionPeriod}"); var jobDetails = new JobModel { JobType = GetJobType(periodEndJob), CollectionPeriod = periodEndJob.CollectionPeriod, AcademicYear = periodEndJob.CollectionYear, DcJobId = periodEndJob.JobId, Status = JobStatus.InProgress, StartTime = DateTimeOffset.UtcNow }; await RecordNewJob(jobDetails, periodEndJob.GeneratedMessages, cancellationToken).ConfigureAwait(false); logger.LogInfo($"Sent request to record {periodEndJob.GetType().Name}. Job Id: {periodEndJob.JobId}, collection period: {periodEndJob.CollectionYear}-{periodEndJob.CollectionPeriod}"); }
public async Task <bool> IsDuplicate(ProcessLearnerCommand processLearnerCommand, CancellationToken cancellationToken) { logger.LogDebug($"Checking if command of type {processLearnerCommand.GetType().Name} with guid: {processLearnerCommand.CommandId} has already been received."); var learnerKey = new LearnerKey(processLearnerCommand); logger.LogDebug($"learner key: {learnerKey.LogSafeKey}"); if (await cache.Contains(learnerKey.Key, cancellationToken).ConfigureAwait(false)) { logger.LogWarning($"Key: {learnerKey.LogSafeKey} found in the cache and is probably a duplicate."); return(true); } logger.LogDebug($"New learner command. Command key: {learnerKey.LogSafeKey}, command id: {processLearnerCommand.CommandId}"); await cache.Add(learnerKey.Key, learnerKey, cancellationToken); logger.LogInfo($"Added new earnings event to cache. Key: {learnerKey.LogSafeKey}"); return(false); }
public async Task Handle(PeriodEndStoppedEvent message, IMessageHandlerContext context) { logger.LogDebug($"Received period end stopped event. Details: {message.ToJson()}"); var fabricClient = new FabricClient(); var serviceDescription = new StatelessServiceDescription { ApplicationName = new Uri("fabric:/SFA.DAS.Payments.DataLocks.ServiceFabric"), PartitionSchemeDescription = new SingletonPartitionSchemeDescription(), ServiceName = new Uri(ServiceNames.DatalockApprovalsService), ServiceTypeName = "SFA.DAS.Payments.DataLocks.ApprovalsServiceType", InstanceCount = instanceCount, }; await fabricClient.ServiceManager.CreateServiceAsync(serviceDescription); logger.LogInfo($"Finished period end stopped handler. Details: {message.ToJson()}"); }
public async Task <SubmissionsSummaryModel> ValidateSubmissionWindow(long jobId, short academicYear, byte collectionPeriod, CancellationToken cancellationToken) { var isEstimatingMetrics = jobId == 0; var logMessage = isEstimatingMetrics ? "estimating" : "building"; try { logger.LogDebug($"Started {logMessage} metrics for job: {jobId}, Academic year: {academicYear}, Collection period: {collectionPeriod}"); var stopwatch = Stopwatch.StartNew(); var submissionSummaries = await submissionMetricsRepository.GetSubmissionsSummaryMetrics(jobId, academicYear, collectionPeriod, cancellationToken); var metrics = submissionsSummary.GetMetrics(jobId, academicYear, collectionPeriod, submissionSummaries); var collectionPeriodTolerance = await submissionMetricsRepository.GetCollectionPeriodTolerance(collectionPeriod, academicYear, cancellationToken); submissionsSummary.CalculateIsWithinTolerance(collectionPeriodTolerance?.SubmissionToleranceLower, collectionPeriodTolerance?.SubmissionToleranceUpper); cancellationToken.ThrowIfCancellationRequested(); var dataDuration = stopwatch.ElapsedMilliseconds; logger.LogDebug($"finished getting data from databases for job: {jobId}, Took: {dataDuration}ms."); if (!isEstimatingMetrics) { await submissionMetricsRepository.SaveSubmissionsSummaryMetrics(metrics, cancellationToken); } stopwatch.Stop(); SendTelemetry(metrics, stopwatch.ElapsedMilliseconds); logger.LogInfo($"Finished {logMessage} Submissions Summary Metrics for job: {jobId}, Academic year: {academicYear}, Collection period: {collectionPeriod}. Took: {stopwatch.ElapsedMilliseconds}ms"); return(metrics); } catch (Exception e) { logger.LogWarning($"Error {logMessage} the Submissions Summary metrics report for job: {jobId}, Error: {e}"); throw; } }
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.ToString()); 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 static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "v1/PeriodEnd/Submission/Successful")] HttpRequest req, [Inject] IProvidersRequiringReprocessingService providersRequiringReprocessingService, [Inject] IPaymentLogger logger) { if (!short.TryParse(req.Query["academicYear"], out var academicYear) || !byte.TryParse(req.Query["collectionPeriod"], out var collectionPeriod)) { return(new StatusCodeResult(400)); } logger.LogDebug($"Entering {nameof(SuccessfulSubmissions)} Function for AcademicYear: {academicYear} and Collection Period: {collectionPeriod}"); var submissionJobs = await providersRequiringReprocessingService.SuccessfulSubmissions(academicYear, collectionPeriod); logger.LogInfo("Successfully retrieved latest successful submission jobs"); return(new OkObjectResult(submissionJobs)); }
public async Task <List <LevyAccountModel> > GetDasLevyAccountDetails() { logger.LogDebug("Started Importing DAS Employer Accounts"); var dasLevyAccountDetails = new List <LevyAccountModel>(); var totalPages = await GetTotalPageSize(); for (var pageNumber = 1; pageNumber <= totalPages; pageNumber++) { var levyAccountDetails = await GetPageOfLevyAccounts(pageNumber); dasLevyAccountDetails.AddRange(levyAccountDetails); } logger.LogInfo("Finished Importing DAS Employer Accounts"); return(dasLevyAccountDetails.IsNullOrEmpty() ? null : dasLevyAccountDetails); }
/// <summary> /// Opens the asynchronous. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> public async Task <string> OpenAsync(CancellationToken cancellationToken) { try { logger.LogDebug($"Opening endpoint: {config.EndpointName}"); if (endpointInstance == null) { var endpointConfiguration = CreateEndpointConfiguration(); //endpointConfiguration.UseContainer<AutofacBuilder>(c => c.ExistingLifetimeScope(lifetimeScope)); endpointInstance = await Endpoint.Start(endpointConfiguration).ConfigureAwait(false); } logger.LogInfo($"Finished opening endpoint listener: {config.EndpointName}"); return("das endpoint comms listener"); } catch (Exception e) { logger.LogFatal($"Cannot start the endpoint: '{config.EndpointName}'. Error: {e.Message}", e); throw; } }
public async Task Handle(IdentifiedRemovedLearningAim message, IMessageHandlerContext context) { logger.LogDebug("Processing 'IdentifiedRemovedLearningAim' message."); ((ExecutionContext)executionContext).JobId = message.JobId.ToString(); var calculatedRequiredLevyAmount = await clawbackRemovedLearnerAimsProcessor.GenerateClawbackForRemovedLearnerAim(message, CancellationToken.None).ConfigureAwait(false); logger.LogDebug($"Got {calculatedRequiredLevyAmount?.Count ?? 0} Calculated Required Levy Amount events."); if (calculatedRequiredLevyAmount != null) { await Task.WhenAll(calculatedRequiredLevyAmount.Select(context.Publish)).ConfigureAwait(false); } logger.LogInfo("Successfully processed IdentifiedRemovedLearningAim event for " + $"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}"); }
protected async Task CleanUpJob(object state) { var lastMessageTime = await GetLastMessageStatusTme().ConfigureAwait(false); if (lastMessageTime == null || lastMessageTime.Value.AddMinutes(MaxIdleMinutesForJob) > DateTimeOffset.UtcNow) { logger.LogVerbose(lastMessageTime.HasValue ? $"Job timeout not triggered. Is due to timeout at: {lastMessageTime.Value.AddMinutes(MaxIdleMinutesForJob)}" : "No job messages have been received so cannot start job timeout."); return; } logger.LogInfo($"Job has timed out due to max idle time. Job: {Id.GetStringId()}"); var statusService = lifetimeScope.Resolve <IJobStatusService>(); await statusService.StopJob(); if (jobCleanUpTimer != null) { UnregisterTimer(jobCleanUpTimer); } }
public async Task <List <EarningEvent> > CreateLearnerEarningEvents(ProcessLearnerCommand processLearnerCommand) { var earningEvents = new List <EarningEvent>(); if (await duplicateLearnerService.IsDuplicate(processLearnerCommand, CancellationToken.None)) { return(earningEvents); } logger.LogDebug($"Handling ILR learner submission. Job: {processLearnerCommand.JobId}, Collection year: {processLearnerCommand.CollectionYear}, Learner: {processLearnerCommand.Learner.LearnRefNumber}"); var processorResult = learnerSubmissionProcessor.GenerateEarnings(processLearnerCommand); if (processorResult.Validation.Failed) { logger.LogInfo($"ILR Learner Submission failed validation. Job: {processLearnerCommand.JobId}, Collection year: {processLearnerCommand.CollectionYear}, Learner: {processLearnerCommand.Learner.LearnRefNumber}"); return(earningEvents); } earningEvents = processorResult.EarningEvents; return(earningEvents); }
private async Task Run(string partitionEndpointName) { await LoadExistingJobs().ConfigureAwait(false); while (!cancellationToken.IsCancellationRequested) { var tasks = currentJobs.Select(job => CheckJobStatus(partitionEndpointName, job.Key)).ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); var completedJobs = currentJobs.Where(item => item.Value).ToList(); foreach (var completedJob in completedJobs) { logger.LogInfo($"Found completed job. Will now stop monitoring job: {completedJob.Key}, ThreadId {Thread.CurrentThread.ManagedThreadId}, PartitionId {partitionEndpointName}"); if (!currentJobs.TryRemove(completedJob.Key, out _)) { logger.LogWarning($"Couldn't remove completed job from jobs list. ThreadId {Thread.CurrentThread.ManagedThreadId}, PartitionId {partitionEndpointName}, Job: {completedJob.Key}, status: {completedJob.Value}"); } } await Task.Delay(interval, cancellationToken).ConfigureAwait(false); } }
public async Task ProcessPayment(ProviderPaymentEventModel payment, CancellationToken cancellationToken) { var stopwatch = new Stopwatch(); stopwatch.Start(); var isCurrentProviderIlr = await IsCurrentProviderIlr(payment.JobId, payment.Ukprn, payment.IlrSubmissionDateTime, cancellationToken).ConfigureAwait(false); if (!isCurrentProviderIlr) { paymentLogger.LogWarning($"Received out of sequence payment with Job Id {payment.JobId} for Ukprn {payment.Ukprn} "); telemetry.TrackEvent("Provider payments service received out of sequence payment"); return; } paymentLogger.LogVerbose($"Received valid payment with Job Id {payment.JobId} for Ukprn {payment.Ukprn} "); await paymentCache.AddPayment(payment, cancellationToken); stopwatch.Stop(); telemetry.TrackDuration(GetType().FullName + ".ProcessPayment", stopwatch.Elapsed); paymentLogger.LogInfo($"Finished adding the payment to the cache. EventId: {payment.EventId}, FundingSourceId: {payment.FundingSourceId}, UKPRN: {payment.Ukprn}"); }
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()); }