private IEnumerable <ProfilingPeriod> MergeProfilingPeriods(IEnumerable <ProfilingPeriod> lhs, IEnumerable <ProfilingPeriod> rhs) { if (lhs.IsNullOrEmpty()) { return(DuplicateEnumerable(rhs)); } List <ProfilingPeriod> list = new List <ProfilingPeriod>(lhs); JsonSerializerSettings deserialiseSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; foreach (ProfilingPeriod item in rhs) { ProfilingPeriod foundItem = lhs.FirstOrDefault(p => p.PeriodDate == item.PeriodDate && p.Type == item.Type && p.DistributionPeriod == item.DistributionPeriod); if (foundItem == null) { ProfilingPeriod clone = JsonConvert.DeserializeObject <ProfilingPeriod>(JsonConvert.SerializeObject(item), deserialiseSettings); list.Add(clone); } else { foundItem.Value += item.Value; } } return(list); }
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); }