Beispiel #1
0
        private async Task GenerateCalculations(SpecGeneratorConfiguration configuration, Specification specification)
        {
            int totalCalculations = 0;

            if (configuration.NumberOfCalculations > 0)
            {
                totalCalculations = configuration.NumberOfCalculations;
            }

            List <Policy> policies = new List <Policy>(specification.Policies);

            List <AllocationLine> allocationLines = new List <AllocationLine>();

            _logger.Information("Generating {totalCalculations} calculations across {Count} policies", totalCalculations, policies.Count);

            foreach (FundingStream fundingStream in specification.FundingStreams)
            {
                foreach (AllocationLine allocationLine in fundingStream.AllocationLines)
                {
                    allocationLines.Add(allocationLine);
                }
            }

            IEnumerable <string> allocationLineNames = allocationLines.Select(a => a.Name);

            _logger.Information("Creating calculations in the following Allocation Lines {allocationLineNames}", allocationLineNames);

            for (int i = 0; i < totalCalculations; i++)
            {
                Policy         policy         = policies[i % policies.Count];
                AllocationLine allocationLine = allocationLines[i % allocationLines.Count];

                CalculationCreateModel calculationCreateModel = new CalculationCreateModel()
                {
                    SpecificationId  = specification.Id,
                    PolicyId         = policy.Id,
                    AllocationLineId = allocationLine.Id,
                    CalculationType  = CalculationSpecificationType.Funding,
                    Description      = "SpecGenerator",
                    Name             = $"{specification.Name} - Calculation {i + 1}",
                    IsPublic         = false,
                };

                await _specsClient.CreateCalculation(calculationCreateModel);
            }
        }
Beispiel #2
0
        private PublishedProviderResult CreateSuccessorResult(SpecificationCurrentVersion specification, ProviderChangeItem providerChangeItem, Reference author, Period fundingPeriod)
        {
            FundingStream fundingStream = GetFundingStream(specification, providerChangeItem);
            PublishedFundingStreamDefinition fundingStreamDefinition = _mapper.Map <PublishedFundingStreamDefinition>(fundingStream);

            AllocationLine allocationLine = fundingStream.AllocationLines.FirstOrDefault(a => a.ProviderLookups.Any(l => l.ProviderType == providerChangeItem.SuccessorProvider.ProviderType && l.ProviderSubType == providerChangeItem.SuccessorProvider.ProviderSubType));
            PublishedAllocationLineDefinition publishedAllocationLine = _mapper.Map <PublishedAllocationLineDefinition>(allocationLine);

            PublishedProviderResult successorResult = new PublishedProviderResult
            {
                FundingPeriod       = fundingPeriod,
                FundingStreamResult = new PublishedFundingStreamResult
                {
                    AllocationLineResult = new PublishedAllocationLineResult
                    {
                        AllocationLine = publishedAllocationLine,
                        Current        = new PublishedAllocationLineResultVersion
                        {
                            Author          = author,
                            Calculations    = null, // don't set calcs as result hasn't been generated from calculation run
                            Date            = DateTimeOffset.Now,
                            Provider        = providerChangeItem.SuccessorProvider,
                            ProviderId      = providerChangeItem.SuccessorProviderId,
                            SpecificationId = specification.Id,
                            Status          = AllocationLineStatus.Held,
                            Value           = 0,
                            Version         = 1
                        },
                        HasResultBeenVaried = true
                    },
                    DistributionPeriod  = $"{fundingStream.PeriodType.Id}{specification.FundingPeriod.Id}",
                    FundingStream       = fundingStreamDefinition,
                    FundingStreamPeriod = $"{fundingStream.Id}{specification.FundingPeriod.Id}"
                },
                ProviderId      = providerChangeItem.SuccessorProviderId,
                SpecificationId = specification.Id,
            };

            successorResult.FundingStreamResult.AllocationLineResult.Current.PublishedProviderResultId = successorResult.Id;

            EnsurePredecessors(successorResult, providerChangeItem.UpdatedProvider.Id);

            return(successorResult);
        }
Beispiel #3
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;
            }
        }
Beispiel #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);
        }
Beispiel #5
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);
        }
Beispiel #6
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);
        }
        private IEnumerable <PublishedFundingStreamResult> AssembleFundingStreamResults(ProviderResult providerResult, SpecificationCurrentVersion specificationCurrentVersion, Reference author, IEnumerable <FundingStream> allFundingStreams)
        {
            IList <PublishedFundingStreamResult> publishedFundingStreamResults = new List <PublishedFundingStreamResult>();

            Dictionary <string, PublishedAllocationLineDefinition> publishedAllocationLines = new Dictionary <string, PublishedAllocationLineDefinition>();

            foreach (Reference fundingStreamReference in specificationCurrentVersion.FundingStreams)
            {
                FundingStream fundingStream = allFundingStreams.FirstOrDefault(m => m.Id == fundingStreamReference.Id);

                if (fundingStream == null)
                {
                    throw new NonRetriableException($"Failed to find a funding stream for id: {fundingStreamReference.Id}");
                }

                PublishedFundingStreamDefinition publishedFundingStreamDefinition = _mapper.Map <PublishedFundingStreamDefinition>(fundingStream);

                List <PublishedProviderCalculationResult> publishedProviderCalculationResults = new List <PublishedProviderCalculationResult>(providerResult.CalculationResults.Count());

                foreach (CalculationResult calculationResult in providerResult.CalculationResults)
                {
                    (Policy policy, Policy parentPolicy, Calculation calculation) = FindPolicy(calculationResult.CalculationSpecification?.Id, specificationCurrentVersion.Policies);

                    if (calculation == null)
                    {
                        throw new NonRetriableException($"Calculation specification not found in specification. Calculation Spec Id ='{calculationResult?.CalculationSpecification?.Id}'");
                    }

                    if (calculation.CalculationType == CalculationType.Number && !calculation.IsPublic)
                    {
                        continue;
                    }

                    PublishedProviderCalculationResult publishedProviderCalculationResult = new PublishedProviderCalculationResult()
                    {
                        CalculationSpecification = calculationResult.CalculationSpecification,
                        AllocationLine           = calculationResult.AllocationLine,
                        IsPublic           = calculation.IsPublic,
                        CalculationType    = ConvertCalculationType(calculationResult.CalculationType),
                        Value              = calculationResult.Value,
                        CalculationVersion = calculationResult.Version
                    };

                    if (policy != null)
                    {
                        publishedProviderCalculationResult.Policy = new PolicySummary(policy.Id, policy.Name, policy.Description);
                    }

                    if (parentPolicy != null)
                    {
                        publishedProviderCalculationResult.ParentPolicy = new PolicySummary(parentPolicy.Id, parentPolicy.Name, parentPolicy.Description);
                    }

                    publishedProviderCalculationResults.Add(publishedProviderCalculationResult);
                }

                IEnumerable <IGrouping <string, CalculationResult> > allocationLineGroups = providerResult
                                                                                            .CalculationResults
                                                                                            .Where(c => c.CalculationType == Models.Calcs.CalculationType.Funding && c.Value.HasValue && c.AllocationLine != null && !string.IsNullOrWhiteSpace(c.AllocationLine.Id))
                                                                                            .GroupBy(m => m.AllocationLine.Id);

                foreach (IGrouping <string, CalculationResult> allocationLineResultGroup in allocationLineGroups)
                {
                    PublishedAllocationLineDefinition publishedAllocationLine;
                    if (!publishedAllocationLines.TryGetValue(allocationLineResultGroup.Key, out publishedAllocationLine))
                    {
                        AllocationLine allocationLine = fundingStream.AllocationLines.FirstOrDefault(m => m.Id == allocationLineResultGroup.Key);
                        if (allocationLine != null)
                        {
                            publishedAllocationLine = _mapper.Map <PublishedAllocationLineDefinition>(allocationLine);
                            publishedAllocationLines.Add(allocationLineResultGroup.Key, publishedAllocationLine);
                        }
                    }

                    if (publishedAllocationLine != null)
                    {
                        PublishedFundingStreamResult publishedFundingStreamResult = new PublishedFundingStreamResult
                        {
                            FundingStream = publishedFundingStreamDefinition,

                            FundingStreamPeriod = $"{fundingStream.Id}{specificationCurrentVersion.FundingPeriod.Id}",

                            DistributionPeriod = $"{fundingStream.PeriodType.Id}{specificationCurrentVersion.FundingPeriod.Id}"
                        };

                        PublishedAllocationLineResultVersion publishedAllocationLineResultVersion = new PublishedAllocationLineResultVersion
                        {
                            Author          = author,
                            Date            = DateTimeOffset.Now,
                            Status          = AllocationLineStatus.Held,
                            Value           = allocationLineResultGroup.Sum(m => m.Value),
                            Provider        = providerResult.Provider,
                            SpecificationId = specificationCurrentVersion.Id,
                            ProviderId      = providerResult.Provider.Id,
                            Calculations    = publishedProviderCalculationResults.Where(c => c.AllocationLine == null || string.Equals(c.AllocationLine.Id, publishedAllocationLine.Id, StringComparison.InvariantCultureIgnoreCase)),
                        };

                        publishedFundingStreamResult.AllocationLineResult = new PublishedAllocationLineResult
                        {
                            AllocationLine = publishedAllocationLine,
                            Current        = publishedAllocationLineResultVersion
                        };

                        publishedFundingStreamResults.Add(publishedFundingStreamResult);
                    }
                }
            }

            return(publishedFundingStreamResults);
        }
        public void CreateCalculation_WhenSomethingGoesWrongDuringIndexing_ShouldThrowException()
        {
            //Arrange
            const string errorMessage = "Encountered 802 error code";

            AllocationLine allocationLine = new AllocationLine
            {
                Id   = "02a6eeaf-e1a0-476e-9cf9-8aa5d9129345",
                Name = "test alloctaion"
            };

            List <FundingStream> fundingStreams = new List <FundingStream>();

            FundingStream fundingStream = new FundingStream
            {
                AllocationLines = new List <AllocationLine>
                {
                    allocationLine
                },
                Id = FundingStreamId
            };

            fundingStreams.Add(fundingStream);

            Policy policy = new Policy
            {
                Id   = PolicyId,
                Name = PolicyName,
            };

            Specification specification = CreateSpecification();

            specification.Current.Policies       = new[] { policy };
            specification.Current.FundingStreams = new List <Reference>()
            {
                new Reference {
                    Id = FundingStreamId
                }
            };

            CalculationCreateModel model = new CalculationCreateModel
            {
                SpecificationId  = SpecificationId,
                PolicyId         = PolicyId,
                AllocationLineId = AllocationLineId
            };

            string json = JsonConvert.SerializeObject(model);

            byte[]       byteArray = Encoding.UTF8.GetBytes(json);
            MemoryStream stream    = new MemoryStream(byteArray);

            ClaimsPrincipal principle = new ClaimsPrincipal(new[]
            {
                new ClaimsIdentity(new [] { new Claim(ClaimTypes.Sid, UserId), new Claim(ClaimTypes.Name, Username) })
            });

            HttpContext context = Substitute.For <HttpContext>();

            context
            .User
            .Returns(principle);

            HttpRequest request = Substitute.For <HttpRequest>();

            request
            .Body
            .Returns(stream);

            request
            .HttpContext
            .Returns(context);

            IHeaderDictionary headerDictionary = new HeaderDictionary();

            headerDictionary
            .Add("sfa-correlationId", new StringValues(SfaCorrelationId));

            request
            .Headers
            .Returns(headerDictionary);

            ILogger logger = CreateLogger();

            ISpecificationsRepository specificationsRepository = CreateSpecificationsRepository();

            specificationsRepository
            .GetSpecificationById(Arg.Is(SpecificationId))
            .Returns(specification);

            specificationsRepository
            .GetFundingStreams(Arg.Any <Expression <Func <FundingStream, bool> > >())
            .Returns(fundingStreams);

            specificationsRepository
            .UpdateSpecification(Arg.Is(specification))
            .Returns(HttpStatusCode.OK);

            Calculation calculation = new Calculation
            {
                AllocationLine = new Reference()
            };

            IMapper mapper = CreateMapper();

            mapper
            .Map <Calculation>(Arg.Any <CalculationCreateModel>())
            .Returns(calculation);

            IMessengerService messengerService = CreateMessengerService();

            ISearchRepository <SpecificationIndex> mockSearchRepository = CreateSearchRepository();

            mockSearchRepository
            .Index(Arg.Any <IEnumerable <SpecificationIndex> >())
            .Returns(new List <IndexError>()
            {
                new IndexError()
                {
                    ErrorMessage = errorMessage
                }
            });

            SpecificationVersion newSpecVersion = specification.Current.Clone() as SpecificationVersion;

            newSpecVersion.PublishStatus = PublishStatus.Updated;
            newSpecVersion.Version       = 2;
            IVersionRepository <SpecificationVersion> mockVersionRepository = CreateVersionRepository();

            mockVersionRepository
            .CreateVersion(Arg.Any <SpecificationVersion>(), Arg.Any <SpecificationVersion>())
            .Returns(newSpecVersion);

            SpecificationsService service = CreateService(logs: logger, specificationsRepository: specificationsRepository,
                                                          mapper: mapper, messengerService: messengerService, specificationVersionRepository: mockVersionRepository, searchRepository: mockSearchRepository);


            //Act
            Func <Task <IActionResult> > createCalculation = async() => await service.CreateCalculation(request);

            //Assert
            createCalculation
            .Should()
            .Throw <ApplicationException>()
            .Which
            .Message
            .Should()
            .Be($"Could not index specification {specification.Current.Id} because: {errorMessage}");
        }
        public async Task CreateCalculation_GivenValidModelForSubPolicyAndSubPolicyFoundAndUpdated_ReturnsOK()
        {
            //Arrange
            AllocationLine allocationLine = new AllocationLine
            {
                Id   = "02a6eeaf-e1a0-476e-9cf9-8aa5d9129345",
                Name = "test alloctaion"
            };

            List <FundingStream> fundingStreams = new List <FundingStream>();

            FundingStream fundingStream = new FundingStream
            {
                AllocationLines = new List <AllocationLine>
                {
                    allocationLine
                },
                Id = FundingStreamId
            };

            fundingStreams.Add(fundingStream);

            Policy policy = new Policy
            {
                Id   = PolicyId,
                Name = PolicyName,
            };

            Specification specification = CreateSpecification();

            specification.Current.Policies       = new[] { policy };
            specification.Current.FundingStreams = new List <Reference>()
            {
                new Reference {
                    Id = FundingStreamId
                }
            };

            CalculationCreateModel model = new CalculationCreateModel
            {
                SpecificationId  = SpecificationId,
                PolicyId         = PolicyId,
                AllocationLineId = AllocationLineId
            };

            string json = JsonConvert.SerializeObject(model);

            byte[]       byteArray = Encoding.UTF8.GetBytes(json);
            MemoryStream stream    = new MemoryStream(byteArray);

            ClaimsPrincipal principle = new ClaimsPrincipal(new[]
            {
                new ClaimsIdentity(new [] { new Claim(ClaimTypes.Sid, UserId), new Claim(ClaimTypes.Name, Username) })
            });

            HttpContext context = Substitute.For <HttpContext>();

            context
            .User
            .Returns(principle);

            HttpRequest request = Substitute.For <HttpRequest>();

            request
            .Body
            .Returns(stream);

            request
            .HttpContext
            .Returns(context);

            IHeaderDictionary headerDictionary = new HeaderDictionary();

            headerDictionary
            .Add("sfa-correlationId", new StringValues(SfaCorrelationId));

            request
            .Headers
            .Returns(headerDictionary);

            ILogger logger = CreateLogger();

            ISpecificationsRepository specificationsRepository = CreateSpecificationsRepository();

            specificationsRepository
            .GetSpecificationById(Arg.Is(SpecificationId))
            .Returns(specification);

            specificationsRepository
            .GetFundingStreams(Arg.Any <Expression <Func <FundingStream, bool> > >())
            .Returns(fundingStreams);

            specificationsRepository
            .UpdateSpecification(Arg.Is(specification))
            .Returns(HttpStatusCode.OK);

            Calculation calculation = new Calculation
            {
                AllocationLine = new Reference()
            };

            IMapper mapper = CreateMapper();

            mapper
            .Map <Calculation>(Arg.Any <CalculationCreateModel>())
            .Returns(calculation);

            IMessengerService messengerService = CreateMessengerService();

            SpecificationVersion newSpecVersion = specification.Current.Clone() as SpecificationVersion;

            newSpecVersion.PublishStatus = PublishStatus.Updated;
            newSpecVersion.Version       = 2;

            IVersionRepository <SpecificationVersion> mockVersionRepository = CreateVersionRepository();

            mockVersionRepository
            .CreateVersion(Arg.Any <SpecificationVersion>(), Arg.Any <SpecificationVersion>())
            .Returns(newSpecVersion);

            SpecificationsService service = CreateService(logs: logger, specificationsRepository: specificationsRepository,
                                                          mapper: mapper, messengerService: messengerService, specificationVersionRepository: mockVersionRepository);

            //Act
            IActionResult result = await service.CreateCalculation(request);

            //Assert
            result
            .Should()
            .BeOfType <OkObjectResult>();

            await
            messengerService
            .Received(1)
            .SendToQueue(Arg.Is("calc-events-create-draft"),
                         Arg.Is <Models.Calcs.Calculation>(m =>
                                                           m.CalculationSpecification.Id == calculation.Id &&
                                                           m.CalculationSpecification.Name == calculation.Name &&
                                                           m.Name == calculation.Name &&
                                                           !string.IsNullOrEmpty(m.Id) &&
                                                           m.AllocationLine.Id == allocationLine.Id &&
                                                           m.AllocationLine.Name == allocationLine.Name),
                         Arg.Is <IDictionary <string, string> >(m =>
                                                                m["user-id"] == UserId &&
                                                                m["user-name"] == Username &&
                                                                m["sfa-correlationId"] == SfaCorrelationId));
        }
        public async Task CreateCalculation_GivenValidModelAndPolicyFoundButAddingCalcCausesBadRequest_ReturnsBadRequest()
        {
            //Arrange
            AllocationLine allocationLine = new AllocationLine
            {
                Id   = "02a6eeaf-e1a0-476e-9cf9-8aa5d9129345",
                Name = "test alloctaion"
            };

            List <FundingStream> fundingStreams = new List <FundingStream>();

            FundingStream fundingStream = new FundingStream
            {
                AllocationLines = new List <AllocationLine>
                {
                    allocationLine
                },
                Id = FundingStreamId
            };

            fundingStreams.Add(fundingStream);

            Policy policy = new Policy
            {
                Id   = PolicyId,
                Name = PolicyName,
            };

            Specification specification = new Specification
            {
                Current = new SpecificationVersion()
                {
                    Policies = new[]
                    {
                        policy,
                    },
                    FundingStreams = new List <Reference>()
                    {
                        new Reference {
                            Id = FundingStreamId
                        }
                    },
                },
            };

            CalculationCreateModel model = new CalculationCreateModel
            {
                SpecificationId  = SpecificationId,
                PolicyId         = PolicyId,
                AllocationLineId = AllocationLineId
            };

            string json = JsonConvert.SerializeObject(model);

            byte[]       byteArray = Encoding.UTF8.GetBytes(json);
            MemoryStream stream    = new MemoryStream(byteArray);

            HttpRequest request = Substitute.For <HttpRequest>();

            request
            .Body
            .Returns(stream);

            ILogger logger = CreateLogger();

            ISpecificationsRepository specificationsRepository = CreateSpecificationsRepository();

            specificationsRepository
            .GetSpecificationById(Arg.Is(SpecificationId))
            .Returns(specification);

            specificationsRepository
            .GetFundingStreams(Arg.Any <Expression <Func <FundingStream, bool> > >())
            .Returns(fundingStreams);

            specificationsRepository
            .UpdateSpecification(Arg.Is(specification))
            .Returns(HttpStatusCode.BadRequest);

            Calculation calculation = new Calculation
            {
                AllocationLine = new Reference()
            };

            IMapper mapper = CreateMapper();

            mapper
            .Map <Calculation>(Arg.Any <CalculationCreateModel>())
            .Returns(calculation);

            SpecificationsService service = CreateService(logs: logger, specificationsRepository: specificationsRepository, mapper: mapper);

            //Act
            IActionResult result = await service.CreateCalculation(request);

            //Assert
            result
            .Should()
            .BeOfType <StatusCodeResult>();

            StatusCodeResult statusCodeResult = (StatusCodeResult)result;

            statusCodeResult
            .StatusCode
            .Should()
            .Be(400);

            logger
            .Received(1)
            .Error($"Failed to update specification when creating a calc with status BadRequest");
        }