public async Task <IEnumerable <PublishedProviderResultExisting> > GetExistingPublishedProviderResultsForSpecificationId(string specificationId) { SqlQuerySpec sqlQuerySpec = new SqlQuerySpec { QueryText = @"SELECT r.id, r.updatedAt, r.content.providerId, r.content.fundingStreamResult.allocationLineResult.current[""value""], r.content.fundingStreamResult.allocationLineResult.allocationLine.id AS allocationLineId, r.content.fundingStreamResult.allocationLineResult.current.major AS major, r.content.fundingStreamResult.allocationLineResult.current.minor AS minor, r.content.fundingStreamResult.allocationLineResult.current.version AS version, r.content.fundingStreamResult.allocationLineResult.current.status AS status, r.content.fundingStreamResult.allocationLineResult.published AS published, r.content.fundingStreamResult.allocationLineResult.current.profilePeriods, r.content.fundingStreamResult.allocationLineResult.current.financialEnvelopes, r.content.fundingStreamResult.allocationLineResult.hasResultBeenVaried, r.content.fundingStreamResult.allocationLineResult.current.provider FROM Root r WHERE r.documentType = 'PublishedProviderResult' AND r.deleted = false AND r.content.specificationId = @SpecificationId", Parameters = new SqlParameterCollection { new SqlParameter("@SpecificationId", specificationId) } }; IEnumerable <dynamic> existingResults = await _cosmosRepository.QueryDynamic(sqlQuerySpec, true, 1000); List <PublishedProviderResultExisting> results = new List <PublishedProviderResultExisting>(); foreach (dynamic existingResult in existingResults) { PublishedProviderResultExisting result = new PublishedProviderResultExisting() { AllocationLineId = existingResult.allocationLineId, Id = existingResult.id, ProviderId = existingResult.providerId, Value = existingResult.value != null?Convert.ToDecimal(existingResult.value) : null, Minor = DynamicExtensions.PropertyExists(existingResult, "minor") ? (int)existingResult.minor : 0, Major = DynamicExtensions.PropertyExists(existingResult, "major") ? (int)existingResult.major : 0, UpdatedAt = (DateTimeOffset?)existingResult.updatedAt, Version = DynamicExtensions.PropertyExists(existingResult, "version") ? (int)existingResult.version : 0, Published = DynamicExtensions.PropertyExistsAndIsNotNull(existingResult, "published") ? ((JObject)existingResult.published).ToObject <PublishedAllocationLineResultVersion>() : null, HasResultBeenVaried = DynamicExtensions.PropertyExists(existingResult, "hasResultBeenVaried") ? (bool)existingResult.hasResultBeenVaried : false, Provider = DynamicExtensions.PropertyExistsAndIsNotNull(existingResult, "provider") ? ((JObject)existingResult.provider).ToObject <ProviderSummary>() : null }; result.Status = Enum.Parse(typeof(AllocationLineStatus), existingResult.status); result.ProfilePeriods = DynamicExtensions.PropertyExistsAndIsNotNull(existingResult, "profilePeriods") ? ((JArray)existingResult.profilePeriods).ToObject <List <ProfilingPeriod> >() : Enumerable.Empty <ProfilingPeriod>(); result.FinancialEnvelopes = DynamicExtensions.PropertyExistsAndIsNotNull(existingResult, "financialEnvelopes") ? ((JArray)existingResult.financialEnvelopes).ToObject <List <FinancialEnvelope> >() : Enumerable.Empty <FinancialEnvelope>(); results.Add(result); } return(results); }
private void ProcessProviderDataChanged( ProviderChangeItem providerChange, AllocationLine allocationLine, IEnumerable <PublishedProviderResultExisting> existingPublishedProviderResults, IEnumerable <PublishedProviderResult> allPublishedProviderResults, List <PublishedProviderResult> resultsToSave) { // See if the affected provider has already had a result generated that needs to be saved PublishedProviderResult affectedResult = resultsToSave.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { // Get previous result for affected provider PublishedProviderResultExisting affectedProviderExistingResult = existingPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.AllocationLineId == allocationLine.Id); if (affectedProviderExistingResult != null) { // No new result to save so copy the from the generated version to use as a base affectedResult = allPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); CopyPropertiesFromExisitngResult(affectedResult, affectedProviderExistingResult); _logger.Information($"Creating new result for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{affectedResult.SpecificationId}'"); resultsToSave.Add(affectedResult); } } // If still no result to save then there is nothing to update if (affectedResult != null) { _logger.Information($"Processing data update for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{affectedResult.SpecificationId}'"); affectedResult.FundingStreamResult.AllocationLineResult.Current.Provider = providerChange.UpdatedProvider; affectedResult.FundingStreamResult.AllocationLineResult.Current.VariationReasons = providerChange.VariationReasons; } }
private void CopyPropertiesFromExisitngResult(PublishedProviderResult result, PublishedProviderResultExisting existingResult) { result.FundingStreamResult.AllocationLineResult.Current.Version = existingResult.Version + 1; result.FundingStreamResult.AllocationLineResult.Current.Major = existingResult.Major; result.FundingStreamResult.AllocationLineResult.Current.Minor = existingResult.Minor; result.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods = existingResult.ProfilePeriods.ToArray(); result.FundingStreamResult.AllocationLineResult.Current.FinancialEnvelopes = existingResult.FinancialEnvelopes; if (existingResult.Status != AllocationLineStatus.Held) { result.FundingStreamResult.AllocationLineResult.Current.Status = AllocationLineStatus.Updated; } if (existingResult.Published != null) { result.FundingStreamResult.AllocationLineResult.Published = existingResult.Published; } }
private (IEnumerable <ProviderVariationError> variationErrors, bool canContinue) ProcessProviderClosedWithoutSuccessor( ProviderChangeItem providerChange, AllocationLine allocationLine, SpecificationCurrentVersion specification, PublishedProviderResultExisting affectedProviderExistingResult, IEnumerable <PublishedProviderResult> allPublishedProviderResults, List <PublishedProviderResult> resultsToSave) { List <ProviderVariationError> errors = new List <ProviderVariationError>(); _logger.Information($"Processing provider {providerChange.UpdatedProvider.Id} when closed without successor. Specification '{specification.Id}' and allocation line {allocationLine.Id}"); if (affectedProviderExistingResult.HasResultBeenVaried) { // Don't apply variation logic to an already varied result _logger.Information($"Result for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id} has already been varied. Specification '{specification.Id}'"); return(errors, false); } // Find profiling periods after the variation date, for the affected provider IEnumerable <ProfilingPeriod> affectedProfilingPeriods = Enumerable.Empty <ProfilingPeriod>(); if (!affectedProviderExistingResult.ProfilePeriods.IsNullOrEmpty()) { affectedProfilingPeriods = affectedProviderExistingResult.ProfilePeriods.Where(p => p.PeriodDate > specification.VariationDate); } if (affectedProfilingPeriods.IsNullOrEmpty()) { _logger.Information($"There are no affected profiling periods for the allocation line result {allocationLine.Id} and provider {providerChange.UpdatedProvider.Id}"); return(errors, true); } // See if the affected provider has already had a result generated that needs to be saved PublishedProviderResult affectedResult = resultsToSave.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { // No new result to save so copy the from the generated version to use as a base affectedResult = allPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { errors.Add(new ProviderVariationError { AllocationLineId = allocationLine.Id, Error = "Could not find/create result for successor", UKPRN = providerChange.UpdatedProvider.UKPRN }); return(errors, false); } _logger.Information($"Creating new result for affected provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{specification.Id}'"); resultsToSave.Add(affectedResult); } // Have to copy info from existing result otherwise they won't be set CopyPropertiesFromExisitngResult(affectedResult, affectedProviderExistingResult); EnsureProviderUpToDate(affectedResult, providerChange); decimal affectedProfilingPeriodsTotal = affectedProfilingPeriods.Sum(p => p.Value); // Zero out the affected profile periods in the affected provider foreach (ProfilingPeriod profilePeriod in affectedProfilingPeriods) { profilePeriod.Value = 0; } // Remove the amount in the affected profiling periods from the affected providers allocation total affectedResult.FundingStreamResult.AllocationLineResult.Current.Value -= affectedProfilingPeriodsTotal; // Set a flag to indicate result has been varied affectedResult.FundingStreamResult.AllocationLineResult.HasResultBeenVaried = true; return(errors, true); }
public async Task <ProcessProviderVariationsResult> ProcessProviderVariations( JobViewModel triggeringJob, SpecificationCurrentVersion specification, IEnumerable <ProviderResult> providerResults, IEnumerable <PublishedProviderResultExisting> existingPublishedProviderResults, IEnumerable <PublishedProviderResult> allPublishedProviderResults, List <PublishedProviderResult> resultsToSave, Reference author) { Guard.ArgumentNotNull(triggeringJob, nameof(triggeringJob)); Guard.ArgumentNotNull(specification, nameof(specification)); Guard.ArgumentNotNull(providerResults, nameof(providerResults)); Guard.ArgumentNotNull(existingPublishedProviderResults, nameof(existingPublishedProviderResults)); Guard.ArgumentNotNull(allPublishedProviderResults, nameof(allPublishedProviderResults)); Guard.ArgumentNotNull(resultsToSave, nameof(resultsToSave)); Guard.ArgumentNotNull(author, nameof(author)); List <ProviderVariationError> errors = new List <ProviderVariationError>(); ProcessProviderVariationsResult result = new ProcessProviderVariationsResult(); // Only process on a refresh, not on choose if (existingPublishedProviderResults.Any()) { _logger.Information($"Processing provider variations for specification '{specification.Id}' and job '{triggeringJob.Id}'"); IEnumerable <ProviderChangeItem> providerVariations; try { providerVariations = await _providerVariationAssemblerService.AssembleProviderVariationItems(providerResults, existingPublishedProviderResults, specification.Id); if (providerVariations.AnyWithNullCheck(v => v.HasProviderClosed) && !specification.VariationDate.HasValue) { errors.Add(new ProviderVariationError { Error = "Variations have been found for the scoped providers, but the specification has no variation date set" }); result.Errors = errors; return(result); } Period fundingPeriod = await GetFundingPeriod(specification); foreach (ProviderChangeItem providerChange in providerVariations) { foreach (AllocationLine allocationLine in specification.FundingStreams.SelectMany(f => f.AllocationLines)) { (IEnumerable <ProviderVariationError> variationErrors, bool canContinue)processingResult = (Enumerable.Empty <ProviderVariationError>(), true); if (providerChange.HasProviderClosed && providerChange.DoesProviderHaveSuccessor) { // If successor has a previous result PublishedProviderResultExisting successorExistingResult = null; if (allocationLine.ProviderLookups.Any(p => p.ProviderType == providerChange.UpdatedProvider.ProviderType && p.ProviderSubType == providerChange.UpdatedProvider.ProviderSubType)) { successorExistingResult = existingPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.SuccessorProviderId); } if (successorExistingResult != null) { processingResult = ProcessProviderClosedWithSuccessor(providerChange, allocationLine, specification, existingPublishedProviderResults, allPublishedProviderResults, resultsToSave, successorExistingResult); } else { processingResult = ProcessProviderClosedWithSuccessor(providerChange, allocationLine, specification, existingPublishedProviderResults, allPublishedProviderResults, resultsToSave, author, fundingPeriod); } } else if (providerChange.HasProviderClosed && !providerChange.DoesProviderHaveSuccessor) { // Get previous result for affected provider PublishedProviderResultExisting affectedProviderExistingResult = existingPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.AllocationLineId == allocationLine.Id); if (affectedProviderExistingResult != null) { processingResult = ProcessProviderClosedWithoutSuccessor(providerChange, allocationLine, specification, affectedProviderExistingResult, allPublishedProviderResults, resultsToSave); } else { _logger.Information($"Provider '{providerChange.UpdatedProvider.Id}' has closed without successor but has no existing result. Specification '{specification.Id}' and allocation line '{allocationLine.Id}'"); } } errors.AddRange(processingResult.variationErrors); if (!processingResult.canContinue) { continue; } if (providerChange.HasProviderDataChanged) { ProcessProviderDataChanged(providerChange, allocationLine, existingPublishedProviderResults, allPublishedProviderResults, resultsToSave); } } } if (!errors.Any()) { result.ProviderChanges = providerVariations; } } catch (Exception ex) { errors.Add(new ProviderVariationError { Error = ex.Message }); result.Errors = errors; return(result); } } else { _logger.Information($"Not processing variations for specification '{specification.Id}' as job '{triggeringJob.Id}' is not for Refresh."); } result.Errors = errors; return(result); }
private (IEnumerable <ProviderVariationError> variationErrors, bool canContinue) ProcessProviderClosedWithSuccessor( ProviderChangeItem providerChange, AllocationLine allocationLine, SpecificationCurrentVersion specification, IEnumerable <PublishedProviderResultExisting> existingPublishedProviderResults, IEnumerable <PublishedProviderResult> allPublishedProviderResults, List <PublishedProviderResult> resultsToSave, Reference author, Period fundingPeriod) { List <ProviderVariationError> errors = new List <ProviderVariationError>(); _logger.Information($"Processing Provider '{providerChange.UpdatedProvider.Id}' when closed with successor but has no existing result. Specifiction '{specification.Id}' and allocation line {allocationLine.Id}"); // Get previous result for affected provider PublishedProviderResultExisting affectedProviderExistingResult = existingPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.AllocationLineId == allocationLine.Id); if (affectedProviderExistingResult == null) { _logger.Information($"No existing result for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id} to vary. Specification '{specification.Id}'"); return(errors, true); } if (affectedProviderExistingResult.HasResultBeenVaried) { // Don't apply variation logic to an already varied result _logger.Information($"Result for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id} has already been varied. Specification '{specification.Id}'"); return(errors, false); } // Find profiling periods after the variation date, for the affected provider IEnumerable <ProfilingPeriod> affectedProfilingPeriods = Enumerable.Empty <ProfilingPeriod>(); if (!affectedProviderExistingResult.ProfilePeriods.IsNullOrEmpty()) { affectedProfilingPeriods = affectedProviderExistingResult.ProfilePeriods.Where(p => p.PeriodDate > specification.VariationDate); } if (affectedProfilingPeriods.IsNullOrEmpty()) { _logger.Information($"There are no affected profiling periods for the allocation line result {allocationLine.Id} and provider {providerChange.UpdatedProvider.Id}"); return(errors, true); } // Check for an existing result for the successor IEnumerable <PublishedProviderResult> successorResults = resultsToSave.Where(r => r.ProviderId == providerChange.SuccessorProviderId); PublishedProviderResult successorResult = null; // Find existing result which is in the same funding stream as current allocation line (spec may have multiple) and which matches the provider type and subtype if (successorResults.Any()) { foreach (PublishedProviderResult existingResult in successorResults) { foreach (FundingStream fundingStream in specification.FundingStreams) { if (fundingStream.AllocationLines.Any(a => a.Id == allocationLine.Id)) { foreach (AllocationLine fsAllocationLine in fundingStream.AllocationLines) { if (fsAllocationLine.ProviderLookups.AnyWithNullCheck(p => p.ProviderType == providerChange.SuccessorProvider.ProviderType && p.ProviderSubType == providerChange.SuccessorProvider.ProviderSubType)) { successorResult = existingResult; break; } } if (successorResult != null) { break; } } } if (successorResult != null) { break; } } } if (successorResult == null) { // If no new result for the successor then create one successorResult = CreateSuccessorResult(specification, providerChange, author, fundingPeriod); _logger.Information($"Creating new result for successor provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{specification.Id}'"); resultsToSave.Add(successorResult); } // Copy info from existing result otherwise they won't be set successorResult.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods = MergeProfilingPeriods(successorResult.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods, affectedProviderExistingResult.ProfilePeriods); successorResult.FundingStreamResult.AllocationLineResult.Current.FinancialEnvelopes = MergeFinancialEnvelopes(successorResult.FundingStreamResult.AllocationLineResult.Current.FinancialEnvelopes, affectedProviderExistingResult.FinancialEnvelopes); decimal affectedProfilingPeriodsTotal = affectedProfilingPeriods.Sum(p => p.Value); // As we have moved all the periods from the affected result to the successor result we need to zero out the unaffected profile periods IEnumerable <ProfilingPeriod> unaffectedProfilingPeriods = affectedProviderExistingResult.ProfilePeriods.Except(affectedProfilingPeriods); foreach (ProfilingPeriod profilePeriod in unaffectedProfilingPeriods) { ProfilingPeriod successorProfilePeriod = successorResult.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods.FirstOrDefault(p => p.Period == profilePeriod.Period && p.Year == profilePeriod.Year && p.Type == profilePeriod.Type); // Zero the unaffected profile period successorProfilePeriod.Value = 0; } // Set the allocation total to the sum of the affected profiling periods successorResult.FundingStreamResult.AllocationLineResult.Current.Value += affectedProfilingPeriodsTotal; // Set a flag to indicate successor result has been varied successorResult.FundingStreamResult.AllocationLineResult.HasResultBeenVaried = true; // See if the affected provider has already had a result generated that needs to be saved PublishedProviderResult affectedResult = resultsToSave.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { // No new result to save so copy the from the generated version to use as a base affectedResult = allPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { errors.Add(new ProviderVariationError { AllocationLineId = allocationLine.Id, Error = "Could not find/create result for affected provider", UKPRN = providerChange.UpdatedProvider.UKPRN }); return(errors, false); } _logger.Information($"Creating new result for affected provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{specification.Id}'"); resultsToSave.Add(affectedResult); } // Have to copy info from existing result otherwise they won't be set CopyPropertiesFromExisitngResult(affectedResult, affectedProviderExistingResult); EnsureProviderUpToDate(affectedResult, providerChange); // Zero out the affected profile periods in the affected provider foreach (ProfilingPeriod profilePeriod in affectedProfilingPeriods) { profilePeriod.Value = 0; } // Remove the amount in the affected profiling periods from the affected providers allocation total affectedResult.FundingStreamResult.AllocationLineResult.Current.Value -= affectedProfilingPeriodsTotal; // Set a flag to indicate result has been varied affectedResult.FundingStreamResult.AllocationLineResult.HasResultBeenVaried = true; // Ensure the predecessor information is added to the successor EnsurePredecessors(successorResult, providerChange.UpdatedProvider.UKPRN); return(errors, true); }
private (IEnumerable <ProviderVariationError> variationErrors, bool canContinue) ProcessProviderClosedWithSuccessor( ProviderChangeItem providerChange, AllocationLine allocationLine, SpecificationCurrentVersion specification, IEnumerable <PublishedProviderResultExisting> existingPublishedProviderResults, IEnumerable <PublishedProviderResult> allPublishedProviderResults, List <PublishedProviderResult> resultsToSave, PublishedProviderResultExisting successorExistingResult) { List <ProviderVariationError> errors = new List <ProviderVariationError>(); _logger.Information($"Processing provider {providerChange.UpdatedProvider.Id} when closed with successor. Specification '{specification.Id}' and allocation line {allocationLine.Id}"); // Get previous result for affected provider PublishedProviderResultExisting affectedProviderExistingResult = existingPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.AllocationLineId == allocationLine.Id); if (affectedProviderExistingResult == null) { _logger.Information($"No existing result for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id} to vary. Specification '{specification.Id}'"); return(errors, true); } if (affectedProviderExistingResult.HasResultBeenVaried) { // Don't apply variation logic to an already varied result _logger.Information($"Result for provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id} has already been varied. Specification '{specification.Id}'"); return(errors, false); } // Find profiling periods after the variation date, for the affected provider IEnumerable <ProfilingPeriod> affectedProfilingPeriods = Enumerable.Empty <ProfilingPeriod>(); if (!affectedProviderExistingResult.ProfilePeriods.IsNullOrEmpty()) { affectedProfilingPeriods = affectedProviderExistingResult.ProfilePeriods.Where(p => p.PeriodDate > specification.VariationDate); } if (affectedProfilingPeriods.IsNullOrEmpty()) { _logger.Information($"There are no affected profiling periods for the allocation line result {allocationLine.Id} and provider {providerChange.UpdatedProvider.Id}"); return(errors, true); } // See if the successor has already had a result generated that needs to be saved PublishedProviderResult successorResult = resultsToSave.FirstOrDefault(r => r.ProviderId == providerChange.SuccessorProviderId && (allocationLine.ProviderLookups != null && allocationLine.ProviderLookups.Any(p => p.ProviderType == providerChange.UpdatedProvider.ProviderType && p.ProviderSubType == providerChange.UpdatedProvider.ProviderSubType))); if (successorResult == null) { // If no new result for the successor so copy from the generated list as a base successorResult = allPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.SuccessorProviderId && (allocationLine.ProviderLookups != null && allocationLine.ProviderLookups.Any(p => p.ProviderType == providerChange.UpdatedProvider.ProviderType && p.ProviderSubType == providerChange.UpdatedProvider.ProviderSubType))); if (successorResult == null) { _logger.Information($"Could not find result for successor provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id} to update. Specification '{specification.Id}'"); errors.Add(new ProviderVariationError { UKPRN = providerChange.UpdatedProvider.UKPRN, Error = "Could not find/create result for successor", AllocationLineId = allocationLine.Id }); return(errors, false); } _logger.Information($"Creating new result for successor provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{specification.Id}'"); resultsToSave.Add(successorResult); } // Have to copy info from existing result otherwise they won't be set CopyPropertiesFromExisitngResult(successorResult, successorExistingResult); EnsurePredecessors(successorResult, affectedProviderExistingResult.ProviderId); decimal affectedProfilingPeriodsTotal = affectedProfilingPeriods.Sum(p => p.Value); // Move the values from each affected profiling periods from the affected provider to the successor foreach (ProfilingPeriod profilePeriod in affectedProfilingPeriods) { ProfilingPeriod successorProfilePeriod = successorResult.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods.FirstOrDefault(p => p.Period == profilePeriod.Period && p.Year == profilePeriod.Year && p.Type == profilePeriod.Type); if (successorProfilePeriod == null) { _logger.Information($"Creating new profile for successor provider {providerChange.SuccessorProviderId}. Specification '{specification.Id}' and allocation line {allocationLine.Id}"); successorProfilePeriod = new Models.Results.ProfilingPeriod { DistributionPeriod = profilePeriod.DistributionPeriod, Occurrence = profilePeriod.Occurrence, Period = profilePeriod.Period, Type = profilePeriod.Type, Year = profilePeriod.Year }; List <ProfilingPeriod> tempPeriods = new List <ProfilingPeriod>(successorResult.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods); tempPeriods.AddRange(new[] { successorProfilePeriod }); successorResult.FundingStreamResult.AllocationLineResult.Current.ProfilingPeriods = tempPeriods; } // Add the value from the affected profile to the matching successor profile successorProfilePeriod.Value += profilePeriod.Value; } // Add the amount in the affected profiling periods to the successors allocation total successorResult.FundingStreamResult.AllocationLineResult.Current.Value += affectedProfilingPeriodsTotal; // Set a flag to indicate successor result has been varied successorResult.FundingStreamResult.AllocationLineResult.HasResultBeenVaried = true; // See if the affected provider has already had a result generated that needs to be saved PublishedProviderResult affectedResult = resultsToSave.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { // No new result to save so copy the from the generated version to use as a base affectedResult = allPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerChange.UpdatedProvider.Id && r.FundingStreamResult.AllocationLineResult.AllocationLine.Id == allocationLine.Id); if (affectedResult == null) { errors.Add(new ProviderVariationError { AllocationLineId = allocationLine.Id, Error = "Could not find/create result for successor", UKPRN = providerChange.UpdatedProvider.UKPRN }); return(errors, false); } _logger.Information($"Creating new result for affected provider {providerChange.UpdatedProvider.Id} and allocation line {allocationLine.Id}. Specification '{specification.Id}'"); resultsToSave.Add(affectedResult); } // Have to copy info from existing result otherwise they won't be set CopyPropertiesFromExisitngResult(affectedResult, affectedProviderExistingResult); EnsureProviderUpToDate(affectedResult, providerChange); // Zero out the affected profile periods in the affected provider foreach (ProfilingPeriod profilePeriod in affectedProfilingPeriods) { profilePeriod.Value = 0; } // Remove the amount in the affected profiling periods from the affected providers allocation total affectedResult.FundingStreamResult.AllocationLineResult.Current.Value -= affectedProfilingPeriodsTotal; // Set a flag to indicate result has been varied affectedResult.FundingStreamResult.AllocationLineResult.HasResultBeenVaried = true; // Ensure the predecessor information is added to the successor EnsurePredecessors(successorResult, providerChange.UpdatedProvider.UKPRN); return(errors, true); }
public async Task <IEnumerable <ProviderChangeItem> > AssembleProviderVariationItems(IEnumerable <ProviderResult> providerResults, IEnumerable <PublishedProviderResultExisting> existingPublishedProviderResults, string specificationId) { List <ProviderChangeItem> changeItems = new List <ProviderChangeItem>(); IEnumerable <ProviderSummary> coreProviderData = await _providerService.FetchCoreProviderData(); if (coreProviderData.IsNullOrEmpty()) { throw new NonRetriableException("Failed to retrieve core provider data"); } foreach (ProviderResult providerResult in providerResults) { PublishedProviderResultExisting existingResult = existingPublishedProviderResults.FirstOrDefault(r => r.ProviderId == providerResult.Provider.Id); // Ignore calculation results with no funding and no existing result if (existingResult == null && providerResult != null && providerResult.AllocationLineResults.All(r => !r.Value.HasValue)) { continue; } ProviderSummary coreProvider = coreProviderData.FirstOrDefault(p => p.Id == providerResult.Provider.Id); if (coreProvider == null) { throw new NonRetriableException($"Could not find provider in core data with id '{providerResult.Provider.Id}'"); } ProviderChangeItem changeItem = new ProviderChangeItem { UpdatedProvider = coreProvider }; if (existingResult != null) { if (existingResult.HasResultBeenVaried) { // Don't replay an already processed variation continue; } List <VariationReason> variationReasons = new List <VariationReason>(); if (coreProvider.Status == ProviderStatusClosed) { changeItem.HasProviderClosed = true; changeItem.ProviderReasonCode = coreProvider.ReasonEstablishmentClosed; changeItem.SuccessorProviderId = coreProvider.Successor; changeItem.DoesProviderHaveSuccessor = !string.IsNullOrWhiteSpace(coreProvider.Successor); } if (existingResult.Provider.Authority != coreProvider.Authority) { changeItem.HasProviderDataChanged = true; variationReasons.Add(VariationReason.AuthorityFieldUpdated); } if (existingResult.Provider.EstablishmentNumber != coreProvider.EstablishmentNumber) { changeItem.HasProviderDataChanged = true; variationReasons.Add(VariationReason.EstablishmentNumberFieldUpdated); } if (existingResult.Provider.DfeEstablishmentNumber != coreProvider.DfeEstablishmentNumber) { changeItem.HasProviderDataChanged = true; variationReasons.Add(VariationReason.DfeEstablishmentNumberFieldUpdated); } if (existingResult.Provider.Name != coreProvider.Name) { changeItem.HasProviderDataChanged = true; variationReasons.Add(VariationReason.NameFieldUpdated); } if (existingResult.Provider.LACode != coreProvider.LACode) { changeItem.HasProviderDataChanged = true; variationReasons.Add(VariationReason.LACodeFieldUpdated); } if (existingResult.Provider.LegalName != coreProvider.LegalName) { changeItem.HasProviderDataChanged = true; variationReasons.Add(VariationReason.LegalNameFieldUpdated); } if (coreProvider.Status != ProviderStatusClosed && !string.IsNullOrWhiteSpace(coreProvider.Successor)) { throw new NonRetriableException($"Provider has successor in core provider data but is not set to 'Closed' for provider '{providerResult.Provider.Id}'"); } if (!string.IsNullOrWhiteSpace(coreProvider.Successor)) { ProviderSummary successorCoreProvider = coreProviderData.FirstOrDefault(p => p.Id == coreProvider.Successor); if (successorCoreProvider == null) { throw new NonRetriableException($"Could not find provider successor in core provider data for provider '{providerResult.Provider.Id}' and successor '{coreProvider.Successor}'"); } changeItem.SuccessorProvider = successorCoreProvider; } changeItem.VariationReasons = variationReasons; changeItem.PriorProviderState = providerResult.Provider; } else { if (providerResult.AllocationLineResults.Any(r => r.Value.HasValue)) { changeItem.HasProviderOpened = true; changeItem.ProviderReasonCode = coreProvider.ReasonEstablishmentOpened; } } if (changeItem.HasProviderClosed || changeItem.HasProviderDataChanged || changeItem.HasProviderOpened) { if (!changeItems.Any(i => i.UpdatedProvider.Id == changeItem.UpdatedProvider.Id)) { changeItems.Add(changeItem); } } } return(changeItems); }