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);
        }
Ejemplo n.º 2
0
        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;
            }
        }
Ejemplo n.º 3
0
        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;
            }
        }
Ejemplo n.º 4
0
        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);
        }
Ejemplo n.º 5
0
        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);
        }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 8
0
        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);
        }