private async Task <VendorRule> GetVendorRule(VendorRuleModel ruleModel, string ruleDefinition, Guid userId, RuleType ruleType, Vendor vendor) { var criterion = ruleModel.Criteria != null ? new RuleCriterion { Operator = ExpressionType.And.ToString(), Children = await GetRuleCriteria(ruleModel.Criteria) } : null; var ruleEntity = new Rule(userId) { Name = $"{vendor.Name}_{ruleType}_{ruleModel.Name}", Criterion = criterion, Definition = ruleDefinition, Type = ruleType }; var vendorRule = new VendorRule { Name = ruleModel.Name, Rule = ruleEntity }; return(vendorRule); }
public async Task SkipFirstStagePresentation_When_SkipFirstPresentation_ShouldAddSkipFirstPresentationStageRule() { // Arrange var rule = new PgVendorRuleDefinition { Splits = new PgVendorRuleSplit[0] }; var ruleStr = JsonConvert.SerializeObject(rule); var ruleMode = new VendorRuleModel { Name = "Vendor payment rule 1", Definition = JsonConvert.DeserializeObject <Dictionary <string, dynamic> >(ruleStr), SkipFirstPresentation = true }; var vendor = new Vendor { Name = "Vendor 1" }; var skipFirstPresentationRule = new Rule { Name = Constants.Rules.Stage.SkipFirstPresentation, Type = RuleType.Stage, Definition = JsonConvert.SerializeObject(new PgStageRuleDefinition()) }; _efContext.Rule.Add(skipFirstPresentationRule); _efContext.SaveChanges(); // Act var vendorRules = await _pgVendorRuleBuilder.ValidateAndGetVendorRules(ruleMode, vendor, It.IsAny <Guid>()); var parsedResults = vendorRules.Select(r => JsonConvert.DeserializeObject <PgPaymentRuleDefinition>(r.Rule.Definition)) .ToArray(); //Assert parsedResults.Should().HaveCount(2); vendorRules.Count(r => r.Rule.Type == RuleType.VendorStage).Should().Be(1); vendorRules.Count(r => r.Rule.Type == RuleType.VendorPayment).Should().Be(1); var stageRule = vendorRules.First(vr => vr.Rule.Type == RuleType.VendorStage); stageRule.Rule.Definition.Should().Be(skipFirstPresentationRule.Definition); }
protected CompiledRule <TestRule> GetBudgetRegionCriterion(string regionName, decimal split) { const string regionFieldName = "BudgetRegion"; var rule = new Rule { Type = RuleType.CommonPayment, Criterion = new RuleCriterion { FieldName = regionFieldName, Operator = "Equal", TargetValue = regionName }, Definition = JsonConvert.SerializeObject(new Dictionary <string, dynamic> { { "split", split } }) }; return(Service.GetCompiledRule <TestRule>(rule)); }
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); }