private async Task <CostSectionTotals> GetTotals(Guid revisionId) { var revision = await _efContext.CostStageRevision.Include(x => x.CostStage) .FirstOrDefaultAsync(x => x.Id == revisionId); var costLineItems = await _costStageRevisionService.GetCostLineItems(revisionId); var stageDetailsForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(revisionId); return(_pgTotalsBuilder.Build(stageDetailsForm, costLineItems, revision.CostStage.Key)); }
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); }
public async Task UpdateTechnicalFeeLineItem(Guid costId, Guid latestRevisionId) { var currentRevision = await _efContext.CostStageRevision .Include(x => x.Approvals) .ThenInclude(x => x.ApprovalMembers) .ThenInclude(x => x.CostUser) .ThenInclude(x => x.UserBusinessRoles) .ThenInclude(a => a.BusinessRole) .FirstOrDefaultAsync(x => x.Id == latestRevisionId); var costConsultantApprovals = currentRevision.Approvals?.Where(x => x.Type == ApprovalType.IPM && x.ValidBusinessRoles?.Contains(Constants.BusinessRole.CostConsultant) == true) .ToList(); var costConsultantSelected = costConsultantApprovals .Any(x => x.ApprovalMembers .Any(m => m.CostUser.UserBusinessRoles.Any(a => a.BusinessRole.Value == Constants.BusinessRole.CostConsultant))); var costLineItems = await _efContext.CostLineItem.Where(x => x.CostStageRevisionId == latestRevisionId).ToListAsync(); var techFeeLineItem = costLineItems.FirstOrDefault(x => x.Name == Constants.CostSection.TechnicalFee); if (techFeeLineItem != null) { // tech fee is applicable, let's see if we need to recalculate the value if (costConsultantSelected && costConsultantApprovals.Any()) { // currently there is a cost consultant selected var fee = await GetTechnicalFee(costId); if (fee != null && fee.ConsultantRate != 0) { // we got a CC rate, but we only save it if current value is 0 if (techFeeLineItem.ValueInDefaultCurrency == 0) { // calculate values based on FX rate var feeCurrency = await _currencyService.GetCurrency(fee.CurrencyCode); if (feeCurrency != null && !feeCurrency.DefaultCurrency) { // only calculate fx rate for foreign currencies var defaultFxRate = await _costExchangeRateService.GetExchangeRateByCurrency(costId, feeCurrency.Id); techFeeLineItem.ValueInDefaultCurrency = fee.ConsultantRate * defaultFxRate.Rate; } else { techFeeLineItem.ValueInDefaultCurrency = fee.ConsultantRate; } var localCurrency = await _currencyService.GetCurrency(techFeeLineItem.LocalCurrencyId); if (localCurrency != null && !localCurrency.DefaultCurrency) { var localFxRate = await _costExchangeRateService.GetExchangeRateByCurrency(costId, techFeeLineItem.LocalCurrencyId); techFeeLineItem.ValueInLocalCurrency = techFeeLineItem.ValueInDefaultCurrency / localFxRate.Rate; // reverse conversion } else { techFeeLineItem.ValueInLocalCurrency = techFeeLineItem.ValueInDefaultCurrency; } _efContext.Update(techFeeLineItem); await _efContext.SaveChangesAsync(); } } } else if (techFeeLineItem.ValueInDefaultCurrency > 0) { // cost consultant is not selected, but there is a fee - let's set the rate to 0 if previously we don't have any approved revisions/stages with tech fee var previousRevision = await _costStageRevisionService.GetPreviousRevision(currentRevision.Id); if (previousRevision != null) { var previousCostLineItems = await _costStageRevisionService.GetCostLineItems(previousRevision.Id); var prevTechFeeLineItem = previousCostLineItems.FirstOrDefault(x => x.Name == Constants.CostSection.TechnicalFee); if (prevTechFeeLineItem == null || prevTechFeeLineItem.ValueInDefaultCurrency == 0) { // we don't have a value - let's set it to 0 await SetToZeroAndSave(techFeeLineItem); } } else { // we don't have a previous review - let's set it to 0 await SetToZeroAndSave(techFeeLineItem); } } } }