private static void CommonTestSetup(Mock <IApprenticeshipRepository> repository, DataLockEvent dataLock, List <EarningPeriod> periods, List <ApprenticeshipModel> apprenticeships, List <DataLockFailure> dataLockFailures = null ) { repository .Setup(x => x.Get(It.IsAny <List <long> >(), CancellationToken.None)) .Returns(Task.FromResult(apprenticeships)); dataLock.IlrFileName = "bob"; apprenticeships[0].ApprenticeshipPriceEpisodes.ForEach(x => x.ApprenticeshipId = apprenticeships[0].Id); periods[0].ApprenticeshipId = apprenticeships[0].Id; periods[0].ApprenticeshipPriceEpisodeId = apprenticeships[0].ApprenticeshipPriceEpisodes[0].Id; periods[0].DataLockFailures = dataLock is PayableEarningEvent ? null : dataLockFailures; periods[0].PriceEpisodeIdentifier = dataLock.PriceEpisodes[0].Identifier; dataLock.OnProgrammeEarnings[0].Type = OnProgrammeEarningType.Learning; dataLock.OnProgrammeEarnings[0].Periods = periods.AsReadOnly(); if (dataLock is EarningFailedDataLockMatching) { dataLockFailures.ForEach(x => { x.ApprenticeshipId = apprenticeships[0].Id; x.ApprenticeshipPriceEpisodeIds = apprenticeships[0].ApprenticeshipPriceEpisodes.Select(o => o.Id).ToList(); }); } }
private bool EventsMatch(DataLockEvent original, DataLockEvent client) { return(original.Id == client.Id && original.ProcessDateTime == client.ProcessDateTime && original.IlrFileName == client.IlrFileName && original.Ukprn == client.Ukprn && original.Uln == client.Uln && original.LearnRefNumber == client.LearnRefNumber && original.AimSeqNumber == client.AimSeqNumber && original.PriceEpisodeIdentifier == client.PriceEpisodeIdentifier && original.ApprenticeshipId == client.ApprenticeshipId && original.EmployerAccountId == client.EmployerAccountId && original.EventSource == client.EventSource && original.HasErrors == client.HasErrors && original.IlrStartDate == client.IlrStartDate && original.IlrStandardCode == client.IlrStandardCode && original.IlrProgrammeType == client.IlrProgrammeType && original.IlrFrameworkCode == client.IlrFrameworkCode && original.IlrPathwayCode == client.IlrPathwayCode && original.IlrTrainingPrice == client.IlrTrainingPrice && original.IlrEndpointAssessorPrice == client.IlrEndpointAssessorPrice && EventErrorsMatch(original.Errors, client.Errors) && EventPeriodsMatch(original.Periods, client.Periods) && EventApprenticeshipsMatch(original.Apprenticeships, client.Apprenticeships)); }
private Dictionary <(TransactionType type, byte period), EarningPeriod> GetFailuresGroupedByTypeAndPeriod( DataLockEvent dataLockEvent) { var result = new Dictionary <(TransactionType type, byte period), EarningPeriod>(); if (dataLockEvent.OnProgrammeEarnings != null && dataLockEvent.OnProgrammeEarnings.Any()) { foreach (var onProgrammeEarning in dataLockEvent.OnProgrammeEarnings) { foreach (var period in onProgrammeEarning.Periods) { if (period.Amount == 0 && string.IsNullOrWhiteSpace(period.PriceEpisodeIdentifier)) { continue; // DataLocks are generated for all periods, even irrelevant, ignore until fixed } var key = ((TransactionType)onProgrammeEarning.Type, period.Period); if (result.ContainsKey(key)) { paymentLogger.LogWarning($"DataLockEvent trying to add duplicate key \n\n " + $"Existing Period: {JsonConvert.SerializeObject(result[key])}\n\n" + $"Period that failed to add: {JsonConvert.SerializeObject(period)}"); } else { result.Add(key, period); } } } } if (dataLockEvent.IncentiveEarnings != null && dataLockEvent.IncentiveEarnings.Any()) { foreach (var incentiveEarning in dataLockEvent.IncentiveEarnings) { foreach (var period in incentiveEarning.Periods) { if (period.Amount == 0 && string.IsNullOrWhiteSpace(period.PriceEpisodeIdentifier)) { continue; // DataLocks are generated for all periods, even irrelevant, ignore until fixed } var key = ((TransactionType)incentiveEarning.Type, period.Period); if (result.ContainsKey(key)) { paymentLogger.LogWarning($"DataLockEvent trying to add duplicate key \n\n " + $"Existing Period: {JsonConvert.SerializeObject(result[key])}\n\n" + $"Period that failed to add: {JsonConvert.SerializeObject(period)}"); } else { result.Add(key, period); } } } } return(result); }
public List <DataLockEventPriceEpisodeModel> Resolve(DataLockEvent source, DataLockEventModel destination, List <DataLockEventPriceEpisodeModel> destMember, ResolutionContext context) { var priceEpisodeModels = source.PriceEpisodes? .Select(priceEpisode => context.Mapper.Map <DataLockEventPriceEpisodeModel>(priceEpisode)) .ToList() ?? new List <DataLockEventPriceEpisodeModel>(); priceEpisodeModels.ForEach(model => model.DataLockEventId = source.EventId); return(priceEpisodeModels); }
public async Task <List <DataLockStatusChanged> > ProcessPayableEarning(DataLockEvent payableEarningEvent) { using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled)) { var result = new List <DataLockStatusChanged>(); var changedToPassed = new DataLockStatusChangedToPassed { TransactionTypesAndPeriods = new Dictionary <TransactionType, List <EarningPeriod> >() }; var failuresToDelete = new List <long>(); var newPassesGroupedByTypeAndPeriod = GetPassesGroupedByTypeAndPeriod(payableEarningEvent); var oldFailures = await dataLockFailureRepository.GetFailures( payableEarningEvent.Ukprn, payableEarningEvent.Learner.ReferenceNumber, payableEarningEvent.LearningAim.FrameworkCode, payableEarningEvent.LearningAim.PathwayCode, payableEarningEvent.LearningAim.ProgrammeType, payableEarningEvent.LearningAim.StandardCode, payableEarningEvent.LearningAim.Reference, payableEarningEvent.CollectionYear ).ConfigureAwait(false); foreach (var oldFailure in oldFailures) { if (newPassesGroupedByTypeAndPeriod.TryGetValue( (oldFailure.TransactionType, oldFailure.DeliveryPeriod), out var newPass)) { AddTypeAndPeriodToEvent(changedToPassed, oldFailure.TransactionType, newPass, payableEarningEvent); failuresToDelete.Add(oldFailure.Id); } } if (changedToPassed.TransactionTypesAndPeriods.Count > 0) { result.Add(changedToPassed); } foreach (var dataLockStatusChanged in result) { mapper.Map(payableEarningEvent, dataLockStatusChanged); } await dataLockFailureRepository.ReplaceFailures(failuresToDelete, new List <DataLockFailureEntity>(), payableEarningEvent.EarningEventId, payableEarningEvent.EventId).ConfigureAwait(false); scope.Complete(); paymentLogger.LogDebug( $"Deleted {failuresToDelete.Count} old DL failures for UKPRN {payableEarningEvent.Ukprn} Learner Ref {payableEarningEvent.Learner.ReferenceNumber} on R{payableEarningEvent.CollectionPeriod.Period:00}"); return(result); } }
public async Task ThenTheDataLockIsSaved() { var dataLock = new DataLockEvent { Id = 1 }; await _handler.Handle(new CreateDataLockCommand { Event = dataLock }); _dataLockRepository.Verify(x => x.SaveDataLock(dataLock), Times.Once); }
public async Task SaveDataLock(DataLockEvent dataLock) { await WithConnection(async c => { using (var transaction = c.BeginTransaction()) { try { var parameters = new DynamicParameters(); parameters.Add("@DataLockId", dataLock.Id, DbType.Int64); parameters.Add("@ProcessDateTime", dataLock.ProcessDateTime, DbType.DateTime2); parameters.Add("@IlrFileName", dataLock.IlrFileName, DbType.String); parameters.Add("@UkPrn", dataLock.Ukprn, DbType.Int64); parameters.Add("@Uln", dataLock.Uln, DbType.Int64); parameters.Add("@LearnRefNumber", dataLock.LearnRefNumber, DbType.String); parameters.Add("@AimSeqNumber", dataLock.AimSeqNumber, DbType.Int64); parameters.Add("@PriceEpisodeIdentifier", dataLock.PriceEpisodeIdentifier, DbType.String); parameters.Add("@ApprenticeshipId", dataLock.ApprenticeshipId, DbType.Int64); parameters.Add("@EmployerAccountId", dataLock.EmployerAccountId, DbType.Int64); parameters.Add("@EventSource", dataLock.EventSource, DbType.Int32); parameters.Add("@Status", dataLock.Status, DbType.Int32); parameters.Add("@HasErrors", dataLock.HasErrors, DbType.Boolean); parameters.Add("@IlrStartDate", dataLock.IlrStartDate, DbType.Date); parameters.Add("@IlrStandardCode", dataLock.IlrStandardCode, DbType.Int64); parameters.Add("@IlrProgrammeType", dataLock.IlrProgrammeType, DbType.Int32); parameters.Add("@IlrFrameworkCode", dataLock.IlrFrameworkCode, DbType.Int32); parameters.Add("@IlrPathwayCode", dataLock.IlrPathwayCode, DbType.Int32); parameters.Add("@IlrTrainingPrice", dataLock.IlrTrainingPrice, DbType.Decimal); parameters.Add("@IlrEndpointAssessorPrice", dataLock.IlrEndpointAssessorPrice, DbType.Decimal); parameters.Add("@IlrPriceEffectiveFromDate", dataLock.IlrPriceEffectiveFromDate, DbType.Date); parameters.Add("@IlrPriceEffectiveToDate", dataLock.IlrPriceEffectiveToDate, DbType.Date); var id = await c.ExecuteScalarAsync <long>( sql: "[Data_Load].[SaveDataLock]", param: parameters, commandType: CommandType.StoredProcedure, transaction: transaction); if (dataLock.HasErrors) { await SaveDataLockErrors(c, transaction, id, dataLock.Errors); } transaction.Commit(); } catch (SqlException ex) { transaction.Rollback(); throw; } } return(0); }); }
private async Task SaveDataLock(DataLockEvent dataLock) { try { await _dataLockRepository.SaveDataLock(dataLock); } catch (Exception ex) { _logger.Error(ex, $"Exception thrown saving data lock"); throw; } }
public async Task ThenTheCreateEventCommandShouldBeSent() { //Arrange var dataLockEvent = new DataLockEvent { Id = 123 }; //Act await _handler.Handle(dataLockEvent); //Assert _mediator.Verify(x => x.PublishAsync(It.IsAny <CreateDataLockCommand>()), Times.Once); _mediator.Verify(x => x.PublishAsync(It.Is <CreateDataLockCommand>(c => c.Event == dataLockEvent)), Times.Once); }
public async Task AndSavingADataLockFailsThenTheExceptionIsLogged() { var expectedException = new Exception(); var failingDataLock = new DataLockEvent(); var command = new CreateDataLockCommand { Event = failingDataLock }; _dataLockRepository.Setup(x => x.SaveDataLock(failingDataLock)).Throws(expectedException); Assert.ThrowsAsync <Exception>(() => _handler.Handle(command)); _logger.Verify(x => x.Error(expectedException, $"Exception thrown saving data lock")); }
public List <DataLockEventNonPayablePeriodModel> Resolve(DataLockEvent source, DataLockEventModel destination, List <DataLockEventNonPayablePeriodModel> destMember, ResolutionContext context) { var periods = destination.NonPayablePeriods ?? new List <DataLockEventNonPayablePeriodModel>(); periods.AddRange(source.OnProgrammeEarnings? .SelectMany(onProgEarning => onProgEarning.Periods, (onProgEarning, period) => new { onProgEarning, period }) .Select(item => new DataLockEventNonPayablePeriodModel { TransactionType = (TransactionType)item.onProgEarning.Type, DeliveryPeriod = item.period.Period, Amount = item.period.Amount, PriceEpisodeIdentifier = item.period.PriceEpisodeIdentifier, SfaContributionPercentage = item.period.SfaContributionPercentage, DataLockEventId = source.EventId, CensusDate = item.onProgEarning.CensusDate, LearningStartDate = source.LearningAim.StartDate, DataLockEventNonPayablePeriodId = Guid.NewGuid(), Failures = item.period.DataLockFailures?.Select(failure => new DataLockEventNonPayablePeriodFailureModel { ApprenticeshipId = failure.ApprenticeshipId, DataLockFailure = failure.DataLockError, }).ToList() ?? new List <DataLockEventNonPayablePeriodFailureModel>(), }) ?? new List <DataLockEventNonPayablePeriodModel>() ); periods.AddRange(source.IncentiveEarnings? .SelectMany(incentiveEarning => incentiveEarning.Periods, (incentiveEarning, period) => new { incentiveEarning, period }) .Select(item => new DataLockEventNonPayablePeriodModel { TransactionType = (TransactionType)item.incentiveEarning.Type, DeliveryPeriod = item.period.Period, Amount = item.period.Amount, PriceEpisodeIdentifier = item.period.PriceEpisodeIdentifier, SfaContributionPercentage = item.period.SfaContributionPercentage, DataLockEventId = source.EventId, CensusDate = item.incentiveEarning.CensusDate, LearningStartDate = source.LearningAim.StartDate, DataLockEventNonPayablePeriodId = Guid.NewGuid(), Failures = item.period.DataLockFailures?.Select(failure => new DataLockEventNonPayablePeriodFailureModel { ApprenticeshipId = failure.ApprenticeshipId, DataLockFailure = failure.DataLockError }).ToList() ?? new List <DataLockEventNonPayablePeriodFailureModel>(), }) ?? new List <DataLockEventNonPayablePeriodModel>() ); periods.ForEach(period => period.Failures.ForEach(failure => failure.DataLockEventNonPayablePeriodId = period.DataLockEventNonPayablePeriodId)); return(periods); }
public DataLockStatus Map(DataLockEvent dataLockEvent) { return(new DataLockStatus { DataLockEventId = dataLockEvent.Id, DataLockEventDatetime = dataLockEvent.ProcessDateTime, PriceEpisodeIdentifier = dataLockEvent.PriceEpisodeIdentifier, ApprenticeshipId = dataLockEvent.ApprenticeshipId, IlrTrainingCourseCode = DeriveTrainingCourseCode(dataLockEvent), IlrTrainingType = DeriveTrainingType(dataLockEvent), IlrActualStartDate = dataLockEvent.IlrStartDate, IlrEffectiveFromDate = dataLockEvent.IlrPriceEffectiveFromDate, IlrPriceEffectiveToDate = dataLockEvent.IlrPriceEffectiveToDate, IlrTotalCost = dataLockEvent.IlrTrainingPrice + dataLockEvent.IlrEndpointAssessorPrice, ErrorCode = DetermineErrorCode(dataLockEvent.Errors), Status = dataLockEvent.Errors?.Any() ?? false ? Status.Fail : Status.Pass, EventStatus = (EventStatus)dataLockEvent.Status }); }
private Dictionary <(TransactionType type, byte period), EarningPeriod> GetPassesGroupedByTypeAndPeriod( DataLockEvent dataLockEvent) { var result = new Dictionary <(TransactionType type, byte period), EarningPeriod>(); foreach (var onProgrammeEarning in dataLockEvent.OnProgrammeEarnings) { foreach (var period in onProgrammeEarning.Periods) { var key = ((TransactionType)onProgrammeEarning.Type, period.Period); if (!result.ContainsKey(key)) { result.Add(key, period); } else { paymentLogger.LogWarning($"DataLockEvent trying to add duplicate key \n\n " + $"Existing Period: {JsonConvert.SerializeObject(result[key])}\n\n" + $"Period that failed to add: {JsonConvert.SerializeObject(period)}"); } } } foreach (var incentiveEarning in dataLockEvent.IncentiveEarnings) { foreach (var period in incentiveEarning.Periods) { var key = ((TransactionType)incentiveEarning.Type, period.Period); if (!result.ContainsKey(key)) { result.Add(key, period); } else { paymentLogger.LogWarning($"DataLockEvent trying to add duplicate key \n\n " + $"Existing Period: {JsonConvert.SerializeObject(result[key])}\n\n" + $"Period that failed to add: {JsonConvert.SerializeObject(period)}"); } } } return(result); }
private static DataLockFailureEntity CreateEntity(DataLockEvent dataLockEvent, TransactionType transactionType, byte deliveryPeriod, EarningPeriod period) { return(new DataLockFailureEntity { Ukprn = dataLockEvent.Ukprn, CollectionPeriod = dataLockEvent.CollectionPeriod.Period, AcademicYear = dataLockEvent.CollectionYear, TransactionType = transactionType, DeliveryPeriod = deliveryPeriod, LearnerReferenceNumber = dataLockEvent.Learner.ReferenceNumber, LearnerUln = dataLockEvent.Learner.Uln, LearningAimFrameworkCode = dataLockEvent.LearningAim.FrameworkCode, LearningAimPathwayCode = dataLockEvent.LearningAim.PathwayCode, LearningAimProgrammeType = dataLockEvent.LearningAim.ProgrammeType, LearningAimReference = dataLockEvent.LearningAim.Reference, LearningAimStandardCode = dataLockEvent.LearningAim.StandardCode, EarningPeriod = period }); }
public async Task ProcessDataLockEvent(DataLockEvent dataLockEvent) { if (dataLockEvent is FunctionalSkillDataLockEvent) { return; } if (dataLockEvent is EarningFailedDataLockMatching) { var hasNoApprenticeship = dataLockEvent .OnProgrammeEarnings .SelectMany(o => o.Periods) .SelectMany(p => p.DataLockFailures) .All(d => !d.ApprenticeshipId.HasValue); if (hasNoApprenticeship) { return; } } if (dataLockEvent is PayableEarningEvent) { var hasNoApprenticeship = dataLockEvent .OnProgrammeEarnings .SelectMany(o => o.Periods) .All(p => !p.ApprenticeshipId.HasValue); if (hasNoApprenticeship) { return; } } await receivedDataLockEventStore.Add(new ReceivedDataLockEvent { JobId = dataLockEvent.JobId, Ukprn = dataLockEvent.Ukprn, Message = dataLockEvent.ToJson(), MessageType = dataLockEvent.GetType().AssemblyQualifiedName }); }
private static void CommonTestSetup(DataLockEvent dataLock, List <EarningPeriod> periods, ApprenticeshipModel apprenticeship, List <DataLockFailure> dataLockFailures = null ) { apprenticeship.ApprenticeshipPriceEpisodes.ForEach(x => x.ApprenticeshipId = apprenticeship.Id); periods[0].ApprenticeshipId = apprenticeship.Id; periods[0].ApprenticeshipPriceEpisodeId = apprenticeship.ApprenticeshipPriceEpisodes[0].Id; periods[0].DataLockFailures = dataLock is PayableEarningEvent ? null : dataLockFailures; periods[0].PriceEpisodeIdentifier = dataLock.PriceEpisodes[0].Identifier; dataLock.OnProgrammeEarnings[0].Type = OnProgrammeEarningType.Learning; dataLock.OnProgrammeEarnings[0].Periods = periods.AsReadOnly(); if (dataLock is EarningFailedDataLockMatching) { dataLockFailures.ForEach(x => { x.ApprenticeshipId = apprenticeship.Id; x.ApprenticeshipPriceEpisodeIds = apprenticeship.ApprenticeshipPriceEpisodes.Select(o => o.Id).ToList(); }); } }
public List <DataLockEventPayablePeriodModel> Resolve(DataLockEvent source, DataLockEventModel destination, List <DataLockEventPayablePeriodModel> destMember, ResolutionContext context) { var periods = destination.PayablePeriods ?? new List <DataLockEventPayablePeriodModel>(); periods.AddRange(source.OnProgrammeEarnings? .SelectMany(onProgEarning => onProgEarning.Periods, (onProgEarning, period) => new { onProgEarning, period }) .Select(item => new DataLockEventPayablePeriodModel { TransactionType = (TransactionType)item.onProgEarning.Type, DeliveryPeriod = item.period.Period, Amount = item.period.Amount, PriceEpisodeIdentifier = item.period.PriceEpisodeIdentifier, SfaContributionPercentage = item.period.SfaContributionPercentage, DataLockEventId = source.EventId, LearningStartDate = source.LearningAim.StartDate, ApprenticeshipId = item.period.ApprenticeshipId, ApprenticeshipPriceEpisodeId = item.period.ApprenticeshipPriceEpisodeId, ApprenticeshipEmployerType = item.period.ApprenticeshipEmployerType, }) ?? new List <DataLockEventPayablePeriodModel>() ); periods.AddRange(source.IncentiveEarnings? .SelectMany(incentiveEarning => incentiveEarning.Periods, (incentiveEarning, period) => new { incentiveEarning, period }) .Select(item => new DataLockEventPayablePeriodModel { TransactionType = (TransactionType)item.incentiveEarning.Type, DeliveryPeriod = item.period.Period, Amount = item.period.Amount, PriceEpisodeIdentifier = item.period.PriceEpisodeIdentifier, SfaContributionPercentage = item.period.SfaContributionPercentage, DataLockEventId = source.EventId, LearningStartDate = source.LearningAim.StartDate, ApprenticeshipId = item.period.ApprenticeshipId, ApprenticeshipPriceEpisodeId = item.period.ApprenticeshipPriceEpisodeId, ApprenticeshipEmployerType = item.period.ApprenticeshipEmployerType, }) ?? new List <DataLockEventPayablePeriodModel>() ); return(periods); }
private async Task <(long, Guid)> AddDataLockEvent(long jobId, Guid earningEventId, byte collectionPeriod, short academicYear) { var dataLockEvent = new DataLockEvent { EventId = Guid.NewGuid(), EarningEventId = earningEventId, IlrSubmissionDateTime = DateTime.Now, LearnerReferenceNumber = "1234", LearningAimReference = "1", LearningAimFundingLineType = "2", Ukprn = ukprn, CollectionPeriod = collectionPeriod, AcademicYear = academicYear, JobId = jobId, EventTime = DateTimeOffset.Now }; await submissionDataContext.DataLockEvents.AddAsync(dataLockEvent); await submissionDataContext.SaveChangesAsync(); return(dataLockEvent.Id, dataLockEvent.EventId); }
public void Arrange() { _eventService = new Mock <IProviderEventService>(); _logger = new Mock <ILog>(); var dataLock = new DataLockEvent { Id = 1 }; var _expectedDataLocks = new PageOfResults <DataLockEvent> { PageNumber = 1, TotalNumberOfPages = 1, Items = new DataLockEvent[1] { dataLock } }; _eventService.Setup(x => x.GetUnprocessedDataLocks()).ReturnsAsync(_expectedDataLocks); _collector = new DataLockEventCollector(_eventService.Object, _logger.Object, new DataConfiguration { DataLocksEnabled = true }); }
public DataLockEventModel Map(DataLockEvent dataLockEvent) { return(mapper.Map <DataLockEventModel>(dataLockEvent)); }
private TrainingType DeriveTrainingType(DataLockEvent dataLockEvent) { return(dataLockEvent.IlrProgrammeType == 25 ? TrainingType.Standard : TrainingType.Framework); }
private string DeriveTrainingCourseCode(DataLockEvent dataLockEvent) { return(dataLockEvent.IlrProgrammeType == 25 ? $"{dataLockEvent.IlrStandardCode}" : $"{dataLockEvent.IlrFrameworkCode}-{dataLockEvent.IlrProgrammeType}-{dataLockEvent.IlrPathwayCode}"); }
public void Arrange() { _configuration = new PaymentsEventsApiConfiguration { ApiBaseUrl = "some-url/", ClientToken = "super_secure_token" }; _dataLockEvent = new DataLockEvent { Id = 1, ProcessDateTime = new DateTime(2017, 2, 8, 9, 10, 11), IlrFileName = "ILR-123456", Ukprn = 123456, Uln = 987654, LearnRefNumber = "Lrn1", AimSeqNumber = 1, PriceEpisodeIdentifier = "25-27-01/05/2017", ApprenticeshipId = 1, EmployerAccountId = 123, EventSource = EventSource.Submission, HasErrors = true, IlrStartDate = new DateTime(2017, 5, 1), IlrStandardCode = 27, IlrTrainingPrice = 12000m, IlrEndpointAssessorPrice = 3000m, Errors = new [] { new DataLockEventError { ErrorCode = "Err15", SystemDescription = "Mismatch on price." } }, Periods = new [] { new DataLockEventPeriod { ApprenticeshipVersion = "1-019", Period = new NamedCalendarPeriod { Id = "1617-R09", Month = 4, Year = 2017 }, IsPayable = false, TransactionType = TransactionType.Learning }, new DataLockEventPeriod { ApprenticeshipVersion = "1-019", Period = new NamedCalendarPeriod { Id = "1617-R10", Month = 5, Year = 2017 }, IsPayable = false } }, Apprenticeships = new [] { new DataLockEventApprenticeship { Version = "19", StartDate = new DateTime(2017, 5, 1), StandardCode = 27, NegotiatedPrice = 17500m, EffectiveDate = new DateTime(2017, 5, 1) } } }; _httpClient = new Mock <SecureHttpClient>(); _httpClient.Setup(c => c.GetAsync(It.IsAny <string>())) .Returns(Task.FromResult(JsonConvert.SerializeObject(new PageOfResults <DataLockEvent> { PageNumber = 1, TotalNumberOfPages = 2, Items = new[] { _dataLockEvent } }))); _client = new Client.PaymentsEventsApiClient(_configuration, _httpClient.Object); }
public void GivenTheDataLockServiceHasGeneratedTheFollowingEvents(Table table) { var dataContext = Container.Resolve <AuditDataContext>(); var submissions = table.CreateSet <SubmissionData>(); foreach (var submission in submissions) { var dataLockEvent = new DataLockEvent { EventId = Guid.NewGuid(), EarningEventId = Guid.NewGuid(), IlrSubmissionDateTime = submission.SubmissionTime, LearnerReferenceNumber = submission.Learner, LearningAimReference = "1", LearningAimFundingLineType = "2", Ukprn = TestSession.Ukprn, CollectionPeriod = 1, AcademicYear = 1 }; if (submission.Failure) { var nonPayablePeriod = new DataLockEventNonPayablePeriod { DataLockEventId = dataLockEvent.EventId, DataLockEventNonPayablePeriodId = Guid.NewGuid() }; dataContext.DataLockEventNonPayablePeriods.Add(nonPayablePeriod); var nonPayableFailures = new DataLockEventNonPayablePeriodFailures { DataLockEventNonPayablePeriodId = nonPayablePeriod.DataLockEventNonPayablePeriodId }; dataContext.DataLockEventNonPayablePeriodFailures.Add(nonPayableFailures); } else { var dataLockPayablePeriod = new DataLockPayablePeriod { DataLockEventId = dataLockEvent.EventId }; dataContext.DataLockPayablePeriods.Add(dataLockPayablePeriod); var priceEpisode = new DataLockEventPriceEpisode { PriceEpisodeIdentifier = "1", PlannedEndDate = DateTime.Now, DataLockEventId = dataLockEvent.EventId, }; dataContext.DataLockEventPriceEpisodes.Add(priceEpisode); } dataContext.DataLockEvents.Add(dataLockEvent); } dataContext.SaveChanges(); }
public async Task TestCreatesDataLockChangedEventWhenNewFailure(DataLockEvent failureEvent) { // arrange failureEvent.Ukprn = 1; failureEvent.Learner = new Learner { ReferenceNumber = "2", Uln = 3 }; failureEvent.LearningAim = new LearningAim { FrameworkCode = 4, StandardCode = 5, Reference = "6", PathwayCode = 7, ProgrammeType = 8, FundingLineType = "9" }; failureEvent.CollectionYear = 1819; failureEvent.CollectionPeriod = new CollectionPeriod { AcademicYear = 7, Period = 8 }; failureEvent.OnProgrammeEarnings = new List <OnProgrammeEarning> { new OnProgrammeEarning { Type = OnProgrammeEarningType.Learning, Periods = new ReadOnlyCollection <EarningPeriod>(new List <EarningPeriod> { new EarningPeriod { Period = 1, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_03 } } }, new EarningPeriod { Period = 2, Amount = 1 } }) } }; failureEvent.IncentiveEarnings = new List <IncentiveEarning>(); var dbFailures = new List <DataLockFailureEntity>(); repositoryMock.Setup(r => r.GetFailures(1, "2", 4, 7, 8, 5, "6", 1819)).ReturnsAsync(dbFailures).Verifiable(); repositoryMock.Setup(r => r.ReplaceFailures(It.Is <List <long> >(old => old.Count == 0), It.Is <List <DataLockFailureEntity> >(newF => newF.Count == 1), It.IsAny <Guid>(), It.IsAny <Guid>())).Returns(Task.CompletedTask).Verifiable(); dataLockStatusServiceMock.Setup(s => s.GetStatusChange(null, failureEvent.OnProgrammeEarnings[0].Periods[0].DataLockFailures)).Returns(DataLockStatusChange.ChangedToFailed).Verifiable(); // act var statusChangedEvents = await processor.ProcessDataLockFailure(failureEvent).ConfigureAwait(false); // assert statusChangedEvents.Should().NotBeNull(); statusChangedEvents.Should().HaveCount(1); statusChangedEvents[0].Should().BeOfType <DataLockStatusChangedToFailed>(); statusChangedEvents[0].TransactionTypesAndPeriods.Should().HaveCount(1); statusChangedEvents[0].TransactionTypesAndPeriods.First().Key.Should().Be(1); statusChangedEvents[0].TransactionTypesAndPeriods.First().Value.Should().HaveCount(1); statusChangedEvents[0].TransactionTypesAndPeriods.First().Value[0].Period.Should().Be(1); }
public async Task TestCreatesMultipleEventsForPeriodsAndTypes(DataLockEvent failureEvent) { // arrange // change of dlock code var oldTT2P5 = new DataLockFailureEntity { Id = 1, DeliveryPeriod = 5, TransactionType = TransactionType.Completion, EarningPeriod = new EarningPeriod { Period = 5, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_06 } } } }; var newTT2p5 = new EarningPeriod { Period = 5, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_04 } } }; // no new - changed to pass var oldTT3p3 = new DataLockFailureEntity { Id = 2, DeliveryPeriod = 3, TransactionType = TransactionType.Balancing, EarningPeriod = new EarningPeriod { Period = 3, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_06 } } } }; var newTT3p3 = new EarningPeriod { Period = 3, Amount = 1, ApprenticeshipId = 4, ApprenticeshipPriceEpisodeId = 4 }; // no change var oldTT2p6 = new DataLockFailureEntity { Id = 3, DeliveryPeriod = 6, TransactionType = TransactionType.Completion, EarningPeriod = new EarningPeriod { Period = 6, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_04 } } } }; var newTT2p6 = new EarningPeriod { Period = 6, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_04 } } }; // change of dlock code var oldTT16p5 = new DataLockFailureEntity { Id = 4, DeliveryPeriod = 5, TransactionType = TransactionType.CareLeaverApprenticePayment, EarningPeriod = new EarningPeriod { Period = 5, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_06 } } } }; var newTT16p5 = new EarningPeriod { Period = 5, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_04 } } }; // no new - change to pass var oldTT10p3 = new DataLockFailureEntity { Id = 5, DeliveryPeriod = 3, TransactionType = TransactionType.Balancing16To18FrameworkUplift, EarningPeriod = new EarningPeriod { Period = 3, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_06 } } } }; var newTT10p3 = new EarningPeriod { Period = 3, Amount = 1, ApprenticeshipId = 10, ApprenticeshipPriceEpisodeId = 10, DataLockFailures = new List <DataLockFailure>() }; // no change var oldTT16p6 = new DataLockFailureEntity { Id = 6, DeliveryPeriod = 6, TransactionType = TransactionType.CareLeaverApprenticePayment, EarningPeriod = new EarningPeriod { Period = 6, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_04 } } } }; var newTT16p6 = new EarningPeriod { Period = 6, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_04 } } }; // no old - change to fail var newTT2p1 = new EarningPeriod { Period = 1, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_03 } } }; var newTT2p2 = new EarningPeriod { Period = 2, Amount = 1 }; var newTT16p1 = new EarningPeriod { Period = 1, Amount = 1, DataLockFailures = new List <DataLockFailure> { new DataLockFailure { DataLockError = DataLockErrorCode.DLOCK_03 } } }; var newTT16p2 = new EarningPeriod { Period = 2, Amount = 1 }; failureEvent.Ukprn = 1; failureEvent.Learner = new Learner { ReferenceNumber = "2", Uln = 3 }; failureEvent.LearningAim = new LearningAim { FrameworkCode = 4, StandardCode = 5, Reference = "6", PathwayCode = 7, ProgrammeType = 8, FundingLineType = "9" }; failureEvent.CollectionYear = 1819; failureEvent.CollectionPeriod = new CollectionPeriod { AcademicYear = 7, Period = 8 }; failureEvent.OnProgrammeEarnings = new List <OnProgrammeEarning> { new OnProgrammeEarning { Type = OnProgrammeEarningType.Completion, // TT2 Periods = new ReadOnlyCollection <EarningPeriod>(new List <EarningPeriod> { // changed to fail newTT2p1, // no change newTT2p2, // change of dlock code newTT2p5, // no change newTT2p6, newTT10p3 }) }, new OnProgrammeEarning { Type = OnProgrammeEarningType.Balancing, Periods = new ReadOnlyCollection <EarningPeriod>(new List <EarningPeriod> { newTT3p3 }) } }; failureEvent.IncentiveEarnings = new List <IncentiveEarning> { new IncentiveEarning { Type = IncentiveEarningType.CareLeaverApprenticePayment, // TT16 Periods = new ReadOnlyCollection <EarningPeriod>(new List <EarningPeriod> { // changed to fail newTT16p1, // no change newTT16p2, // change of dlock code newTT16p5, // no change newTT16p6, }) }, new IncentiveEarning { Type = IncentiveEarningType.Balancing16To18FrameworkUplift, Periods = new ReadOnlyCollection <EarningPeriod>(new List <EarningPeriod> { newTT10p3 }) } }; var oldFailures = new List <DataLockFailureEntity> { oldTT2P5, oldTT3p3, oldTT2p6, oldTT16p5, oldTT10p3, oldTT16p6, }; repositoryMock.Setup(r => r.GetFailures(1, "2", 4, 7, 8, 5, "6", 1819)).ReturnsAsync(oldFailures).Verifiable(); repositoryMock.Setup(r => r.ReplaceFailures(It.Is <List <long> >(old => old.Count == 4), It.Is <List <DataLockFailureEntity> >(newF => newF.Count == 4), It.IsAny <Guid>(), It.IsAny <Guid>())).Returns(Task.CompletedTask).Verifiable(); // TT2 // P1 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(null, newTT2p1.DataLockFailures)).Returns(DataLockStatusChange.ChangedToFailed).Verifiable(); // P2 //dataLockStatusServiceMock.Setup(s => s.GetStatusChange(null, newTT2p2.DataLockFailures)).Returns(DataLockStatusChange.NoChange).Verifiable(); // P5 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(oldTT2P5.EarningPeriod.DataLockFailures, newTT2p5.DataLockFailures)).Returns(DataLockStatusChange.FailureChanged).Verifiable(); // P6 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(oldTT2p6.EarningPeriod.DataLockFailures, newTT2p6.DataLockFailures)).Returns(DataLockStatusChange.NoChange).Verifiable(); // TT3 P3 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(oldTT3p3.EarningPeriod.DataLockFailures, newTT3p3.DataLockFailures)).Returns(DataLockStatusChange.ChangedToPassed).Verifiable(); // TT10 P3 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(oldTT10p3.EarningPeriod.DataLockFailures, newTT10p3.DataLockFailures)).Returns(DataLockStatusChange.ChangedToPassed).Verifiable(); // TT16 // P1 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(null, newTT16p1.DataLockFailures)).Returns(DataLockStatusChange.ChangedToFailed).Verifiable(); // P2 //dataLockStatusServiceMock.Setup(s => s.GetStatusChange(null, newTT16p2.DataLockFailures)).Returns(DataLockStatusChange.NoChange).Verifiable(); // P5 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(oldTT16p5.EarningPeriod.DataLockFailures, newTT16p5.DataLockFailures)).Returns(DataLockStatusChange.FailureChanged).Verifiable(); // P6 dataLockStatusServiceMock.Setup(s => s.GetStatusChange(oldTT16p6.EarningPeriod.DataLockFailures, newTT16p6.DataLockFailures)).Returns(DataLockStatusChange.NoChange).Verifiable(); var statusChangedEvents = await processor.ProcessDataLockFailure(failureEvent).ConfigureAwait(false); // assert statusChangedEvents.Should().NotBeNull(); statusChangedEvents.Should().HaveCount(3); var changedToFail = statusChangedEvents.SingleOrDefault(e => e is DataLockStatusChangedToFailed); changedToFail.Should().NotBeNull(); changedToFail.TransactionTypesAndPeriods.Should().HaveCount(2); changedToFail.TransactionTypesAndPeriods.Should().ContainKey(TransactionType.Completion); changedToFail.TransactionTypesAndPeriods[TransactionType.Completion].Should().HaveCount(1); changedToFail.TransactionTypesAndPeriods[TransactionType.Completion][0].Period.Should().Be(1); changedToFail.TransactionTypesAndPeriods.Should().ContainKey(TransactionType.CareLeaverApprenticePayment); changedToFail.TransactionTypesAndPeriods[TransactionType.CareLeaverApprenticePayment].Should().HaveCount(1); changedToFail.TransactionTypesAndPeriods[TransactionType.CareLeaverApprenticePayment][0].Period.Should().Be(1); var changedToPass = statusChangedEvents.SingleOrDefault(e => e is DataLockStatusChangedToPassed); changedToPass.Should().NotBeNull(); changedToPass.TransactionTypesAndPeriods.Should().HaveCount(2); changedToPass.TransactionTypesAndPeriods.Should().ContainKey(TransactionType.Balancing); changedToPass.TransactionTypesAndPeriods[TransactionType.Balancing].Should().HaveCount(1); changedToPass.TransactionTypesAndPeriods[TransactionType.Balancing][0].Period.Should().Be(3); changedToPass.TransactionTypesAndPeriods.Should().ContainKey(TransactionType.Balancing16To18FrameworkUplift); changedToPass.TransactionTypesAndPeriods[TransactionType.Balancing16To18FrameworkUplift].Should().HaveCount(1); changedToPass.TransactionTypesAndPeriods[TransactionType.Balancing16To18FrameworkUplift][0].Period.Should().Be(3); var changedCode = statusChangedEvents.SingleOrDefault(e => e is DataLockFailureChanged); changedCode.Should().NotBeNull(); changedCode.TransactionTypesAndPeriods.Should().HaveCount(2); changedCode.TransactionTypesAndPeriods.Should().ContainKey(TransactionType.Completion); changedCode.TransactionTypesAndPeriods[TransactionType.Completion].Should().HaveCount(1); changedCode.TransactionTypesAndPeriods[TransactionType.Completion][0].Period.Should().Be(5); changedCode.TransactionTypesAndPeriods.Should().ContainKey(TransactionType.CareLeaverApprenticePayment); changedCode.TransactionTypesAndPeriods[TransactionType.CareLeaverApprenticePayment].Should().HaveCount(1); changedCode.TransactionTypesAndPeriods[TransactionType.CareLeaverApprenticePayment][0].Period.Should().Be(5); }
public void Arrange() { _configuration = new Mock <IPaymentsEventsApiConfiguration>(); _configuration.Setup(m => m.ApiBaseUrl).Returns(ExpectedApiBaseUrl); _configuration.Setup(m => m.ClientToken).Returns(ClientToken); _dataLockEvent = new DataLockEvent { Id = 1, ProcessDateTime = new DateTime(2017, 2, 8, 9, 10, 11), IlrFileName = "ILR-123456", Ukprn = 123456, Uln = 987654, LearnRefNumber = "Lrn1", AimSeqNumber = 1, PriceEpisodeIdentifier = "25-27-01/05/2017", ApprenticeshipId = 1, EmployerAccountId = 123, EventSource = EventSource.Submission, HasErrors = true, IlrStartDate = new DateTime(2017, 5, 1), IlrStandardCode = 27, IlrTrainingPrice = 12000m, IlrEndpointAssessorPrice = 3000m, Errors = new[] { new DataLockEventError { ErrorCode = "Err15", SystemDescription = "Mismatch on price." } }, Periods = new[] { new DataLockEventPeriod { ApprenticeshipVersion = "1-019", Period = new NamedCalendarPeriod { Id = "1617-R09", Month = 4, Year = 2017 }, IsPayable = false, TransactionType = TransactionType.Learning }, new DataLockEventPeriod { ApprenticeshipVersion = "1-019", Period = new NamedCalendarPeriod { Id = "1617-R10", Month = 5, Year = 2017 }, IsPayable = false } }, Apprenticeships = new[] { new DataLockEventApprenticeship { Version = "19", StartDate = new DateTime(2017, 5, 1), StandardCode = 27, NegotiatedPrice = 17500m, EffectiveDate = new DateTime(2017, 5, 1) } } }; _httpMessageHandlerMock = SetupHttpMessageHandler(JsonConvert.SerializeObject( new PageOfResults <DataLockEvent> { PageNumber = 1, TotalNumberOfPages = 2, Items = new[] { _dataLockEvent } })); // use real http client with mocked handler var httpClient = new HttpClient(_httpMessageHandlerMock.Object); _client = new Client.PaymentsEventsApiClient(_configuration.Object, httpClient); }
private void AddTypeAndPeriodToEvent(DataLockStatusChanged statusChangedEvent, TransactionType transactionType, EarningPeriod period, DataLockEvent dataLockEvent) { if (statusChangedEvent is DataLockStatusChangedToPassed) { if (period.DataLockFailures?.Count > 0) { paymentLogger.LogWarning($"DataLockStatusChangedToPassed has data lock failures. EarningPeriod: {JsonConvert.SerializeObject(period)}"); return; } if (!period.ApprenticeshipId.HasValue || !period.ApprenticeshipPriceEpisodeId.HasValue) { paymentLogger.LogWarning($"DataLockStatusChangedToPassed has no apprenticeship ID. DataLockEvent: {JsonConvert.SerializeObject(dataLockEvent)}"); return; } } if (statusChangedEvent.TransactionTypesAndPeriods.TryGetValue(transactionType, out var periods)) { periods.Add(period); } else { statusChangedEvent.TransactionTypesAndPeriods.Add(transactionType, new List <EarningPeriod> { period }); } }
public static CurrentPriceEpisode AssociateWith(this CurrentPriceEpisode priceEpisode, DataLockEvent dlock) { priceEpisode.Uln = dlock.Learner.Uln; priceEpisode.Ukprn = dlock.Ukprn; priceEpisode.JobId = dlock.JobId + 1; priceEpisode.MessageType = typeof(List <PriceEpisodeStatusChange>).AssemblyQualifiedName; priceEpisode.Message = JsonConvert.SerializeObject( new List <PriceEpisodeStatusChange> { new PriceEpisodeStatusChange { DataLock = new LegacyDataLockEvent { PriceEpisodeIdentifier = priceEpisode.PriceEpisodeIdentifier, Status = PriceEpisodeStatus.New, UKPRN = dlock.Ukprn, ULN = dlock.Learner.Uln, AcademicYear = "1920" } } }); return(priceEpisode); }
public async Task <List <DataLockStatusChanged> > ProcessDataLockFailure(DataLockEvent dataLockEvent) { var result = new List <DataLockStatusChanged>(); var changedToFailed = new DataLockStatusChangedToFailed { TransactionTypesAndPeriods = new Dictionary <TransactionType, List <EarningPeriod> >() }; var changedToPassed = new DataLockStatusChangedToPassed { TransactionTypesAndPeriods = new Dictionary <TransactionType, List <EarningPeriod> >() }; var failureChanged = new DataLockFailureChanged { TransactionTypesAndPeriods = new Dictionary <TransactionType, List <EarningPeriod> >() }; var failuresToDelete = new List <long>(); var failuresToRecord = new List <DataLockFailureEntity>(); var newFailuresGroupedByTypeAndPeriod = GetFailuresGroupedByTypeAndPeriod(dataLockEvent); using (var scope = TransactionScopeFactory.CreateSerialisableTransaction()) { var oldFailures = await dataLockFailureRepository.GetFailures( dataLockEvent.Ukprn, dataLockEvent.Learner.ReferenceNumber, dataLockEvent.LearningAim.FrameworkCode, dataLockEvent.LearningAim.PathwayCode, dataLockEvent.LearningAim.ProgrammeType, dataLockEvent.LearningAim.StandardCode, dataLockEvent.LearningAim.Reference, dataLockEvent.CollectionYear ).ConfigureAwait(false); var fullListOfKeys = newFailuresGroupedByTypeAndPeriod.Keys .Concat(oldFailures.Select(f => (f.TransactionType, f.DeliveryPeriod))) .Distinct() .ToList(); foreach (var key in fullListOfKeys) { var transactionType = key.Item1; var period = key.Item2; if (!newFailuresGroupedByTypeAndPeriod.TryGetValue(key, out var newPeriod)) { paymentLogger.LogWarning( $"Earning does not have transaction type {transactionType} for period {period} which is present in DataLockFailure. UKPRN {dataLockEvent.Ukprn}, LearnRefNumber: {dataLockEvent.Learner.ReferenceNumber}"); continue; } var oldFailureEntity = oldFailures.FirstOrDefault(f => f.TransactionType == transactionType && f.DeliveryPeriod == period); var oldFailure = oldFailureEntity?.EarningPeriod.DataLockFailures; var newFailure = newPeriod?.DataLockFailures; var statusChange = dataLockStatusService.GetStatusChange(oldFailure, newFailure); switch (statusChange) { case DataLockStatusChange.ChangedToFailed: AddTypeAndPeriodToEvent(changedToFailed, transactionType, newPeriod, dataLockEvent); failuresToRecord.Add(CreateEntity(dataLockEvent, transactionType, period, newPeriod)); break; case DataLockStatusChange.ChangedToPassed: AddTypeAndPeriodToEvent(changedToPassed, transactionType, newPeriod, dataLockEvent); failuresToDelete.Add(oldFailureEntity.Id); break; case DataLockStatusChange.FailureChanged: AddTypeAndPeriodToEvent(failureChanged, transactionType, newPeriod, dataLockEvent); failuresToRecord.Add(CreateEntity(dataLockEvent, transactionType, period, newPeriod)); failuresToDelete.Add(oldFailureEntity.Id); break; } } if (changedToFailed.TransactionTypesAndPeriods.Count > 0) { result.Add(changedToFailed); } if (changedToPassed.TransactionTypesAndPeriods.Count > 0) { result.Add(changedToPassed); } if (failureChanged.TransactionTypesAndPeriods.Count > 0) { result.Add(failureChanged); } foreach (var dataLockStatusChanged in result) { mapper.Map(dataLockEvent, dataLockStatusChanged); } await dataLockFailureRepository.ReplaceFailures(failuresToDelete, failuresToRecord, dataLockEvent.EarningEventId, dataLockEvent.EventId).ConfigureAwait(false); scope.Complete(); paymentLogger.LogDebug( $"Deleted {failuresToDelete.Count} old DL failures, created {failuresToRecord.Count} new for UKPRN {dataLockEvent.Ukprn} Learner Ref {dataLockEvent.Learner.ReferenceNumber} on R{dataLockEvent.CollectionPeriod.Period:00}"); return(result); } }