private static decimal?GetAllocatedSplitPayment(PgPaymentRule paymentInput, RuleType ruleType, PgPaymentRuleDefinition paymentRuleDefinition, string splitName, CostStages costStage)
        {
            var split = paymentRuleDefinition.GetSplitByNameAndStage(splitName, costStage);

            if (!split.HasValue)
            {
                return(null);
            }

            // change the allocation based on detailed splits
            var allocation = GetAllocation(paymentInput, ruleType, splitName);

            decimal payment = 0;

            if (DependentSections.ContainsKey(splitName))
            {
                foreach (var dependentSplit in DependentSections[splitName])
                {
                    if (paymentRuleDefinition.HasExplicitSplitForSectionAtStage(dependentSplit, costStage))
                    {
                        allocation -= GetAllocation(paymentInput, ruleType, dependentSplit);
                        payment    += GetAllocatedSplitPayment(paymentInput, ruleType, paymentRuleDefinition, dependentSplit, costStage) ?? 0;
                    }
                }
            }

            payment += (allocation ?? 0) * split.Value;

            return(payment);
        }
        private List <PaymentAmountResult> GetNextStagesPaymentAmounts(
            PaymentAmountResult fResult,
            PgPaymentRule paymentRule,
            IEnumerable <StageModel> nextStages)
        {
            var isAipeProjection = paymentRule.IsAIPE && fResult.StageName == CostStages.Aipe.ToString();
            var result           = new List <PaymentAmountResult>();

            var currentResult = fResult;
            var currentInput  = paymentRule;

            foreach (var nextStage in nextStages)
            {
                var input = new PgPaymentRule
                {
                    // we only need these values for next stage projection calculation
                    CostStages = nextStage.Key,
                    // paymentRule already has current total - old payments. We only need to subtract
                    // the freshly calculated values for this stage
                    InsuranceCost         = currentInput.InsuranceCost - currentResult.InsuranceCostPayment,
                    OtherCost             = currentInput.OtherCost - currentResult.OtherCostPayment,
                    PostProductionCost    = currentInput.PostProductionCost - currentResult.PostProductionCostPayment,
                    ProductionCost        = currentInput.ProductionCost - currentResult.ProductionCostPayment,
                    TargetBudgetTotalCost = currentInput.TargetBudgetTotalCost - currentResult.TargetBudgetTotalPayment,
                    TechnicalFeeCost      = currentInput.TechnicalFeeCost - currentResult.TechnicalFeeCostPayment,
                    TalentFeeCost         = currentInput.TalentFeeCost - currentResult.TalentFeeCostPayment,
                    IsAIPE = currentInput.IsAIPE,
                    CostCarryOverAmount = currentResult.CostCarryOverAmount,

                    TotalCost       = currentInput.TotalCost - currentResult.TotalCostAmountPayment,
                    TotalCostAmount = currentResult.TotalCostAmount ?? 0m
                };
                var previousPayment = new CostStageRevisionTotalPayments
                {
                    InsuranceCostPayments         = currentResult.InsuranceCostPayment ?? 0,
                    TalentFeeCostPayments         = currentResult.TalentFeeCostPayment ?? 0,
                    TechnicalFeeCostPayments      = currentResult.TechnicalFeeCostPayment ?? 0,
                    PostProductionCostPayments    = currentResult.PostProductionCostPayment ?? 0,
                    ProductionCostPayments        = currentResult.ProductionCostPayment ?? 0,
                    OtherCostPayments             = currentResult.OtherCostPayment ?? 0,
                    CarryOverAmount               = currentResult.CostCarryOverAmount ?? 0,
                    TargetBudgetTotalCostPayments = currentResult.TargetBudgetTotalPayment ?? 0,
                    TotalCostPayments             = currentResult.TotalCostAmountPayment ?? 0
                };
                currentResult = CalculateStagePaymentAmountsWithRule(fResult.MatchedPaymentRule, nextStage.Key, input, true, previousPayment, isAipeProjection, isNextStageCalc: true);
                currentInput  = input;

                result.Add(currentResult);
            }

            return(result);
        }
        private static decimal?GetAllocation(PgPaymentRule paymentInput, RuleType ruleType, string splitName)
        {
            decimal?allocation;

            switch (splitName)
            {
            case Constants.CostSection.InsuranceTotal:
                allocation = paymentInput.InsuranceCost;
                break;

            case Constants.CostSection.PostProduction:
                allocation = paymentInput.PostProductionCost;
                break;

            case Constants.CostSection.Production:
                allocation = paymentInput.ProductionCost;
                break;

            case Constants.CostSection.TechnicalFee:
                allocation = paymentInput.TechnicalFeeCost;
                break;

            case Constants.CostSection.TalentFees:
                allocation = paymentInput.TalentFeeCost;
                break;

            case Constants.CostSection.Other:
                if (ruleType == RuleType.VendorPayment)
                {
                    // ADC-2243
                    allocation = paymentInput.OtherCost + paymentInput.PostProductionCost + paymentInput.InsuranceCost + paymentInput.TechnicalFeeCost;
                }
                else
                {
                    allocation = paymentInput.OtherCost;
                }

                break;

            default:
                allocation = paymentInput.OtherCost;
                break;
            }

            return(allocation);
        }
        private static decimal?GetAllocatedAmount(PgPaymentRule input, RuleType ruleType, PgPaymentRuleDefinition paymentRuleDefinition, string splitName, CostStages costStage)
        {
            if (!paymentRuleDefinition.HasExplicitSplitForSectionAtStage(splitName, costStage))
            {
                return(null);
            }

            // change the allocation based on detailed splits
            var allocation = GetAllocation(input, ruleType, splitName);

            if (DependentSections.ContainsKey(splitName))
            {
                foreach (var dependentSplit in DependentSections[splitName])
                {
                    if (paymentRuleDefinition.HasExplicitSplitForSectionAtStage(dependentSplit, costStage))
                    {
                        allocation -= GetAllocation(input, ruleType, dependentSplit);
                    }
                }
            }

            return(allocation);
        }
        private async Task <PaymentAmountResult> GetPaymentAmount(PgPaymentRule paymentRule, CostStageRevisionTotalPayments previousPayments, CostSectionTotals totals, string productionCategory = null)
        {
            var rules = new List <CompiledRule <PgPaymentRule> >();

            // Try match the rules one by one. First Vendor specific rules, then AIPE/Non-AIPE rules, and then standart rules if nothing matched.
            if (paymentRule.DirectPaymentVendorId.HasValue)
            {
                var vendorRules = await _ruleService.GetCompiledByVendorId <PgPaymentRule>(paymentRule.DirectPaymentVendorId.Value, RuleType.VendorPayment, productionCategory);

                rules.AddRange(vendorRules);
            }

            if (paymentRule.IsAIPE)
            {
                var aipeRules = await _ruleService.GetCompiledByRuleType <PgPaymentRule>(RuleType.AIPEPayment);

                rules.AddRange(aipeRules);
            }
            else
            {
                var nonAipeRules = await _ruleService.GetCompiledByRuleType <PgPaymentRule>(RuleType.NonAIPEPayment);

                rules.AddRange(nonAipeRules);
            }

            var commonRules = await _ruleService.GetCompiledByRuleType <PgPaymentRule>(RuleType.CommonPayment);

            rules.AddRange(commonRules);

            PaymentAmountResult MatchFunc(PgPaymentRule t, dataAccess.Entity.Rule r) =>
            CalculateStagePaymentAmountsWithRule(r, paymentRule.CostStages, t, false, previousPayments, false, totals);

            _ruleService.TryMatchRule(rules, paymentRule, MatchFunc, out var fResult);

            return(fResult);
        }
        private async Task SaveTotals(Guid costStageRevisionId, PaymentAmountResult paymentAmount, PgPaymentRule payment, bool isFirstFA = true)
        {
            // save the totals here
            var toSave = new List <CostStageRevisionPaymentTotal>();

            if (paymentAmount.IsDetailedSplit)
            {
                toSave.AddRange(new List <CostStageRevisionPaymentTotal>
                {
                    new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.InsuranceCostPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.InsuranceCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.InsuranceCostTotal,
                        LineItemTotalType            = Constants.CostSection.InsuranceTotal,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName,
                        CalculatedAt = DateTime.UtcNow
                    },
                    new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.OtherCostPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.OtherCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.OtherCostTotal,
                        LineItemTotalType            = Constants.CostSection.Other,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName
                    },
                    new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.PostProductionCostPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.PostProductionCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.PostProductionCostTotal,
                        LineItemTotalType            = Constants.CostSection.PostProduction,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName
                    },
                    new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.ProductionCostPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.ProductionCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.ProductionCostTotal,
                        LineItemTotalType            = Constants.CostSection.Production,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName
                    },
                    new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.TargetBudgetTotalPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.TargetBudgetTotalCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.TargetBudgetTotal,
                        LineItemTotalType            = Constants.CostSection.TargetBudgetTotal,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName
                    },
                    new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.TechnicalFeeCostPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.TechnicalFeeCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.TechnicalFeeCostTotal,
                        LineItemTotalType            = Constants.CostSection.TechnicalFee,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName
                    }
                });

                if (paymentAmount.TalentFeeCostPayment.HasValue)
                {
                    toSave.Add(new CostStageRevisionPaymentTotal
                    {
                        CostStageRevisionId          = costStageRevisionId,
                        LineItemTotalCalculatedValue = paymentAmount.TalentFeeCostPayment.GetValueOrDefault(0),
                        LineItemRemainingCost        = payment.TalentFeeCost.GetValueOrDefault(0),
                        LineItemFullCost             = payment.StageTotals.TalentFeeCostTotal,
                        LineItemTotalType            = Constants.CostSection.TalentFees,
                        IsProjection = paymentAmount.IsProjection,
                        StageName    = paymentAmount.StageName
                    });
                }
            }
            if (isFirstFA)
            {
                toSave.Add(new CostStageRevisionPaymentTotal
                {
                    CostStageRevisionId          = costStageRevisionId,
                    LineItemTotalCalculatedValue = paymentAmount.TotalCostAmountPayment.GetValueOrDefault(0),
                    LineItemRemainingCost        = paymentAmount.CostCarryOverAmount.GetValueOrDefault(0),
                    LineItemFullCost             = payment.StageTotals.TotalCostAmountTotal,
                    LineItemTotalType            = Constants.CostSection.CostTotal,
                    IsProjection = paymentAmount.IsProjection,
                    StageName    = paymentAmount.StageName
                }
                           );
            }
            else
            {
                toSave.Add(new CostStageRevisionPaymentTotal
                {
                    CostStageRevisionId          = costStageRevisionId,
                    LineItemTotalCalculatedValue = payment.TotalCost.GetValueOrDefault(0),
                    LineItemRemainingCost        = paymentAmount.CostCarryOverAmount.GetValueOrDefault(0),
                    LineItemFullCost             = payment.StageTotals.TotalCostAmountTotal,
                    LineItemTotalType            = Constants.CostSection.CostTotal,
                    IsProjection = paymentAmount.IsProjection,
                    StageName    = paymentAmount.StageName
                }
                           );
            }


            foreach (var total in toSave)
            {
                total.CalculatedAt = DateTime.UtcNow;
            }

            await _costStageRevisionService.SaveCostStageRevisionPaymentTotals(toSave);
        }
        private PaymentAmountResult CalculateStagePaymentAmountsWithRule(
            dataAccess.Entity.Rule paymentRule,
            string costStageKey,
            PgPaymentRule input,
            bool isProjection,
            CostStageRevisionTotalPayments previousPayments,
            bool isAipeProjection       = false,
            CostSectionTotals rawTotals = null,
            bool isNextStageCalc        = false
            )
        {
            var res = new PaymentAmountResult();
            var pgPaymentRuleData = JsonConvert.DeserializeObject <PgPaymentRuleDefinition>(paymentRule.Definition);

            res.IsDetailedSplit = pgPaymentRuleData.DetailedSplit;
            var     costStage = (CostStages)Enum.Parse(typeof(CostStages), costStageKey);
            decimal?totalCalculatedValue;

            res.CostCarryOverAmount = 0;
            // do the specific calculations here:
            // - sum up all the individual costs
            // -- if it is an AIPE projection (projection of any stage triggered in AIPE stage) - we only have TargetBudgetTotal, so use that anyways
            if (pgPaymentRuleData.DetailedSplit && !isAipeProjection)
            {
                // ADC-2243: run through the payment allocation rule to ensure the 'input' is correctly allocated
                //ADC-2711: check null and get 0
                res.InsuranceCostPayment = GetAllocatedSplitPayment(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.InsuranceTotal, costStage) ?? 0;
                var talentFeeCostPayment = GetAllocatedSplitPayment(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TalentFees, costStage);

                if (talentFeeCostPayment.HasValue)
                {
                    res.TalentFeeCostPayment = talentFeeCostPayment;
                }

                res.PostProductionCostPayment = GetAllocatedSplitPayment(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.PostProduction, costStage) ?? 0;
                res.ProductionCostPayment     = GetAllocatedSplitPayment(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Production, costStage) ?? 0;
                res.TechnicalFeeCostPayment   = GetAllocatedSplitPayment(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TechnicalFee, costStage) ?? 0;
                res.OtherCostPayment          = GetAllocatedSplitPayment(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Other, costStage) ?? 0;

                res.TargetBudgetTotalPayment = 0m;

                // AIPE specific handling of target budget
                if (costStage == CostStages.Aipe)
                {
                    res.TargetBudgetTotalPayment = input.TargetBudgetTotalCost * (pgPaymentRuleData.GetSplitByNameAndStage(Constants.CostSection.TargetBudgetTotal, costStage) ?? 0);
                }

                totalCalculatedValue =
                    res.InsuranceCostPayment
                    + res.OtherCostPayment
                    + res.PostProductionCostPayment
                    + res.ProductionCostPayment
                    + res.TargetBudgetTotalPayment
                    + res.TechnicalFeeCostPayment;

                //ADC-2711: if DPV then exclude postproduction cost due to it has already included into Othercost in GetAllocation method
                if (paymentRule.Type == RuleType.VendorPayment)
                {
                    totalCalculatedValue = totalCalculatedValue - res.PostProductionCostPayment;
                }

                // we only need carry over amounts for detailed splits - for non detailed splits all payments are already contained within TotalCost
                if (costStage == CostStages.Aipe)
                {
                    // we store it as a carry over for other stages to deduct from
                    // only if the calculated value is > 0 - otherwise no payment is made in this stage -> no carry over amount
                    res.CostCarryOverAmount = totalCalculatedValue > 0 ? input.TotalCostAmount - totalCalculatedValue : 0;
                }
                else if (input.IsAIPE)
                {
                    if (input.CostCarryOverAmount > 0)
                    {
                        if (input.CostCarryOverAmount - totalCalculatedValue > 0)
                        {
                            // deduct current payment from the "pot" and store the remainder
                            res.CostCarryOverAmount = input.CostCarryOverAmount - totalCalculatedValue;
                        }
                        else
                        {
                            res.CostCarryOverAmount = 0;
                        }
                    }

                    // we need to subtract current payment from previous target budget total payments to never lose them
                    totalCalculatedValue = totalCalculatedValue - input.CostCarryOverAmount;
                }
                else
                {
                    if (totalCalculatedValue < 0)
                    {
                        // carry over amount will be negative in non-aipe overpayment cases
                        res.CostCarryOverAmount = totalCalculatedValue;
                    }
                }
            }
            else if (isAipeProjection && costStage == CostStages.FinalActual)
            {
                // we pretend to pay the rest on AIPE FA stage since detailed rules won't have a split for Cost Total
                totalCalculatedValue = input.TotalCost;
            }
            else
            {
                totalCalculatedValue = input.TotalCost * (pgPaymentRuleData.GetSplitByNameAndStage(Constants.CostSection.CostTotal, costStage) ?? 0);
            }

            // - if the stage = Final Actual - return whatever result (even if negative)
            // -  otherwise - if <=0 return 0
            if (totalCalculatedValue < 0 && costStage != CostStages.FinalActual && costStage != CostStages.FinalActualRevision)
            {
                res.TotalCostAmountPayment = 0;
            }
            else
            {
                if ((costStage == CostStages.FinalActual || costStage == CostStages.FinalActualRevision) && input.CostCarryOverAmount < 0)
                {
                    // previous stage resulted in a negative calculation, despite us reporting 0, we still need to subtract that negative value
                    totalCalculatedValue += input.CostCarryOverAmount;
                }

                res.TotalCostAmountPayment = totalCalculatedValue;
            }

            res.TotalCostAmount = totalCalculatedValue;

            res.MatchedPaymentRule = paymentRule;
            res.IsProjection       = isProjection;
            res.StageName          = input.CostStages;

            CostSectionTotals totals = null;

            if (rawTotals != null)
            {
                //ADC-2711 overwrite line_item_full_cost values to display on payment summary screen
                if (paymentRule.Type == RuleType.VendorPayment && isNextStageCalc == false)
                {
                    var insuranceCostPaymentAllocation      = GetAllocation(input, paymentRule.Type, Constants.CostSection.InsuranceTotal) ?? 0;
                    var postProductionCostPaymentAllocation = GetAllocation(input, paymentRule.Type, Constants.CostSection.PostProduction) ?? 0;
                    var productionCostPaymentAllocation     = GetAllocation(input, paymentRule.Type, Constants.CostSection.Production) ?? 0;
                    var technicalFeeCostPaymentAllocation   = GetAllocation(input, paymentRule.Type, Constants.CostSection.TechnicalFee) ?? 0;
                    var otherCostPaymentAllocation          = GetAllocation(input, paymentRule.Type, Constants.CostSection.Other) ?? 0;
                    var talentFeePaymentAllocation          = GetAllocation(input, paymentRule.Type, Constants.CostSection.TalentFees) ?? 0;


                    var insuranceCostTotal      = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.InsuranceTotal, costStage) ?? 0;
                    var postProductionCostTotal = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.PostProduction, costStage) ?? 0;
                    var productionCostTotal     = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Production, costStage) ?? 0;
                    var technicalFeeCostTotal   = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TechnicalFee, costStage) ?? 0;
                    var otherCostTotal          = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Other, costStage) ?? 0;
                    var talentFeeCostTotal      = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TalentFees, costStage) ?? 0;

                    totals = new CostSectionTotals
                    {
                        // Recalculate allocated amount because it depends on the rule
                        InsuranceCostTotal      = GetDisplayedValueOnPaymentSummaryPage(insuranceCostPaymentAllocation, insuranceCostTotal),
                        PostProductionCostTotal = GetDisplayedValueOnPaymentSummaryPage(postProductionCostPaymentAllocation, postProductionCostTotal),
                        ProductionCostTotal     = GetDisplayedValueOnPaymentSummaryPage(productionCostPaymentAllocation, productionCostTotal),
                        TechnicalFeeCostTotal   = GetDisplayedValueOnPaymentSummaryPage(technicalFeeCostPaymentAllocation, technicalFeeCostTotal),
                        OtherCostTotal          = GetDisplayedValueOnPaymentSummaryPage(otherCostPaymentAllocation - postProductionCostPaymentAllocation, otherCostTotal - postProductionCostTotal),
                        TalentFeeCostTotal      = GetDisplayedValueOnPaymentSummaryPage(talentFeePaymentAllocation, talentFeeCostTotal),
                        TargetBudgetTotal       = input.TargetBudgetTotalCost ?? 0 + previousPayments.TargetBudgetTotalCostPayments,
                        TotalCostAmountTotal    = input.TotalCostAmount
                    };
                }
                else
                {
                    totals = new CostSectionTotals
                    {
                        // Recalculate allocated amount because it depends on the rule
                        InsuranceCostTotal      = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.InsuranceTotal, costStage) ?? 0,
                        PostProductionCostTotal = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.PostProduction, costStage) ?? 0,
                        ProductionCostTotal     = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Production, costStage) ?? 0,
                        TechnicalFeeCostTotal   = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TechnicalFee, costStage) ?? 0,
                        OtherCostTotal          = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Other, costStage) ?? 0,
                        TalentFeeCostTotal      = GetAllocatedAmount(rawTotals, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TalentFees, costStage) ?? 0,

                        TargetBudgetTotal    = input.TargetBudgetTotalCost ?? 0 + previousPayments.TargetBudgetTotalCostPayments,
                        TotalCostAmountTotal = input.TotalCostAmount
                    };
                }
            }

            var paymentTotals = new CostSectionTotals
            {
                // Recalculate allocated amount because it depends on the rule
                TalentFeeCostTotal      = GetAllocatedAmount(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TalentFees, costStage) ?? 0,
                InsuranceCostTotal      = GetAllocatedAmount(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.InsuranceTotal, costStage) ?? 0,
                PostProductionCostTotal = GetAllocatedAmount(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.PostProduction, costStage) ?? 0,
                ProductionCostTotal     = GetAllocatedAmount(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Production, costStage) ?? 0,
                TechnicalFeeCostTotal   = GetAllocatedAmount(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.TechnicalFee, costStage) ?? 0,
                OtherCostTotal          = GetAllocatedAmount(input, paymentRule.Type, pgPaymentRuleData, Constants.CostSection.Other, costStage) ?? 0,

                TargetBudgetTotal    = input.TargetBudgetTotalCost ?? 0 + previousPayments.TargetBudgetTotalCostPayments,
                TotalCostAmountTotal = input.TotalCostAmount
            };

            res.TotalRemainingPayment = new PgPaymentRule
            {
                BudgetRegion          = input.BudgetRegion,
                ContentType           = input.ContentType,
                CostType              = input.CostType,
                CostStages            = input.CostStages,
                ProductionType        = input.ProductionType,
                DirectPaymentVendorId = input.DirectPaymentVendorId,
                IsAIPE                = input.IsAIPE,
                TotalCostAmount       = input.TotalCostAmount,
                TotalCost             = input.TotalCost,
                TargetBudgetTotalCost = input.TargetBudgetTotalCost,
                CostCarryOverAmount   = input.CostCarryOverAmount,

                // Recalculate remaining payment amount because allocated amount depends on macthed rule
                StageTotals        = totals,
                InsuranceCost      = paymentTotals.InsuranceCostTotal - previousPayments.InsuranceCostPayments,
                TalentFeeCost      = paymentTotals.TalentFeeCostTotal - previousPayments.TalentFeeCostPayments,
                PostProductionCost = paymentTotals.PostProductionCostTotal - previousPayments.PostProductionCostPayments,
                ProductionCost     = paymentTotals.ProductionCostTotal - previousPayments.ProductionCostPayments,
                TechnicalFeeCost   = paymentTotals.TechnicalFeeCostTotal - previousPayments.TechnicalFeeCostPayments,
                OtherCost          = paymentTotals.OtherCostTotal - previousPayments.OtherCostPayments
            };

            return(res);
        }
        public async Task <PaymentAmountResult> CalculatePaymentAmount(Guid costStageRevisionId, bool persist = true)
        {
            var revision = await _efContext.CostStageRevision
                           .Include(csr => csr.CostFormDetails)
                           .Include(r => r.CostStage).ThenInclude(cs => cs.Cost)
                           .Include(r => r.CostStage).ThenInclude(cs => cs.CostStageRevisions)
                           .Include(r => r.StageDetails)
                           .Include(r => r.ProductDetails)
                           .Include(r => r.CostStageRevisionPaymentTotals)
                           //.AsNoTracking()
                           .FirstOrDefaultAsync(csr => csr.Id == costStageRevisionId);

            var costStage = revision.CostStage;
            var cost      = costStage.Cost;

            var stageDetailsForm      = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(revision);
            var productionDetailsForm = _costStageRevisionService.GetProductionDetails <PgProductionDetailsForm>(revision);

            //ADC-2690 revises paymentCostTotal calculation for re-opened Final Actual stage
            var previousPaymentCalculations = new List <CostStageRevisionPaymentTotal>();
            //get the latest costtotal from last approved final actual
            var previousPaymentCostTotal = new CostStageRevisionPaymentTotal();
            //flag to determine if this calculation is for the first FA or subsequent FAs
            bool isFirstFA = true;
            //check if the current cost has any Final Actual stage approved
            var approvedFinalActualStage = cost.CostStages.Find(x => x.Key == CostStages.FinalActual.ToString())?.CostStageRevisions.Find(a => a.Status == CostStageRevisionStatus.Approved);

            //if there is no final actual stage approve, then keep the current calculation as is, which is working correctly.
            if (approvedFinalActualStage == null)
            {
                previousPaymentCalculations = await _costStageRevisionService.GetAllCostPaymentTotals(cost.Id, revision.CostStage.Id);
            }
            else
            {
                //here is the area we do the calculation for re-opened FAs
                //Get All Cost Payment Totals for the current Final Actual stage
                previousPaymentCalculations = await _costStageRevisionService.GetAllCostPaymentTotalsFinalActual(cost.Id, revision.CostStage.Id);

                //extract values of CostTotal rowns of approved FA and order by calculated datetime
                var previousPaymentCostTotals = previousPaymentCalculations.Where(x => x.LineItemTotalType == Constants.CostSection.CostTotal &&
                                                                                  x.CostStageRevision.Status == CostStageRevisionStatus.Approved)
                                                .OrderBy(x => x.CalculatedAt).ToList();
                //check if there is at least 1 approved FA
                if (previousPaymentCalculations.Any() && previousPaymentCostTotals.Any())
                {
                    //if there is an approved FA, it means there is an inprogress FA, and we shall need to get the last FA for subtraction later: Grand total at Final actual -II minus Grand total in Final actual -I
                    previousPaymentCostTotal = previousPaymentCostTotals[previousPaymentCostTotals.Count() - 1];
                    //flag up this is not the first FA
                    isFirstFA = false;
                }
                else
                {
                    //otherwise, keep the calculation as is
                    previousPaymentCalculations = await _costStageRevisionService.GetAllCostPaymentTotals(cost.Id, revision.CostStage.Id);
                }
            }

            var costLineItems = await _costStageRevisionService.GetCostLineItems(costStageRevisionId);

            var totals = _pgTotalsBuilder.Build(stageDetailsForm, costLineItems, costStage.Key);
            var previousPaymentTotals = _pgTotalPaymentsBuilder.Build(previousPaymentCalculations);

            // these are totals of remaining balance
            //changed for ADC-2690
            var totalRemainingPayment = new PgPaymentRule()
            {
                StageTotals           = totals,
                BudgetRegion          = stageDetailsForm.BudgetRegion?.Key,
                ContentType           = stageDetailsForm.ContentType?.Key,
                CostType              = cost.CostType.ToString(),
                CostStages            = costStage.Key,
                ProductionType        = Constants.ProductionType.ProductionTypeList.FirstOrDefault(a => a == stageDetailsForm.ProductionType?.Key),
                DirectPaymentVendorId = productionDetailsForm.DirectPaymentVendor?.Id,
                IsAIPE = stageDetailsForm.IsAIPE,

                // we need this to match with the rules' TotalCostAmount field
                TotalCostAmount = totals.TotalCostAmountTotal,

                // this is for detailed split
                InsuranceCost      = totals.InsuranceCostTotal - previousPaymentTotals.InsuranceCostPayments,
                TechnicalFeeCost   = totals.TechnicalFeeCostTotal - previousPaymentTotals.TechnicalFeeCostPayments,
                TalentFeeCost      = totals.TalentFeeCostTotal - previousPaymentTotals.TalentFeeCostPayments,
                PostProductionCost = totals.PostProductionCostTotal - previousPaymentTotals.PostProductionCostPayments,
                ProductionCost     = totals.ProductionCostTotal - previousPaymentTotals.ProductionCostPayments,
                OtherCost          = totals.OtherCostTotal - previousPaymentTotals.OtherCostPayments,

                TargetBudgetTotalCost = totals.TargetBudgetTotal - previousPaymentTotals.TargetBudgetTotalCostPayments,
                CostCarryOverAmount   = previousPaymentTotals.CarryOverAmount
            };

            //check if this is not the calculation for the first FA then do the subtraction: Grand total at Final actual -II minus Grand total in Final actual -I
            //if not keep as is
            if (!isFirstFA)
            {
                //if this is not the first FA, it means we would have to calculated TotalCost AKA CostTotal equal Grand total at Final actual -II minus Grand total in Final actual -I
                totalRemainingPayment.TotalCost = totals.TotalCostAmountTotal - previousPaymentCostTotal.LineItemFullCost;
            }
            else
            {
                // we use this to calculate the outstanding balance where there is no detailed split
                totalRemainingPayment.TotalCost = totals.TotalCostAmountTotal - previousPaymentTotals.TotalCostPayments;
            }
            _logger.Information($"Calculating payment amount for cost: {cost.CostNumber} at stage: {costStage.Key} revision: {revision.Id}");

            // these are actual payment splits (percentages of totals)
            var paymentAmount = await GetPaymentAmount(totalRemainingPayment, previousPaymentTotals, totals, productionDetailsForm.DirectPaymentVendor?.ProductionCategory);

            if (paymentAmount != null)
            {
                if (!persist)
                {
                    return(paymentAmount);
                }
                var totalRemainingAmount = (PgPaymentRule)paymentAmount.TotalRemainingPayment;

                var nextStages = await _stageService.GetAllUpcomingStages(costStage.Key, BuType.Pg, cost.Id);

                if (nextStages != null)
                {
                    paymentAmount.ProjectedPayments = GetNextStagesPaymentAmounts(paymentAmount, totalRemainingPayment, nextStages);
                }

                var alreadySaved = await _costStageRevisionService.GetCostStageRevisionPaymentTotals(revision);

                if (alreadySaved == null || !alreadySaved.Any())
                {
                    await SaveTotals(costStageRevisionId, paymentAmount, totalRemainingAmount, isFirstFA);

                    foreach (var projectedPayment in paymentAmount.ProjectedPayments)
                    {
                        await SaveTotals(costStageRevisionId, projectedPayment, totalRemainingAmount, isFirstFA);
                    }
                }
                // UserIdentity in the parameters of below method is used only to log an activity when IO number gets changed.
                // Therefore we can pass any not null object here
                await _customObjectDataService.Save(costStageRevisionId, CustomObjectDataKeys.PgPaymentRuleInput, totalRemainingPayment, new UserIdentity());

                await _customObjectDataService.Save(costStageRevisionId, CustomObjectDataKeys.PgMatchedPaymentRule, paymentAmount.MatchedPaymentRule, new UserIdentity());

                return(paymentAmount);
            }

            _logger.Error($"Payment amount NOT calculated for cost: {cost.CostNumber} at stage: {costStage.Key} revision: {revision.Id} using rule: {totalRemainingPayment}!");
            return(null);
        }