public async Task <TechnicalFee> GetTechnicalFee(Guid costId) { var cost = await _efContext.Cost.FirstAsync(x => x.Id == costId); var stageDetailsForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(cost.LatestCostStageRevisionId.Value); //var productionDetailsForm = await _costStageRevisionService.GetProductionDetails<PgProductionDetailsForm>(cost.LatestCostStageRevisionId.Value); var budgetRegion = stageDetailsForm.BudgetRegion?.Key; var contentType = stageDetailsForm.ContentType?.Key; var costType = cost.CostType; var productionType = Constants.ProductionType.ProductionTypeList.FirstOrDefault(a => a == stageDetailsForm.ProductionType?.Key); var queriable = _efContext.TechnicalFee.Where(x => x.CostType == cost.CostType.ToString() && x.RegionName == budgetRegion); // content type is not applicable to Usage&Buyout and Trafficking/Distribution if (costType != CostType.Buyout && costType != CostType.Trafficking) { queriable = queriable.Where(x => x.ContentType == contentType); } if (costType != CostType.Buyout && costType != CostType.Trafficking && contentType != Constants.ContentType.Digital) { queriable = queriable.Where(x => x.ProductionType == productionType); } return(await queriable.FirstOrDefaultAsync()); }
public bool DoesUserHaveRoleForCost(CostUser costUser, CostStageRevision costStageRevision, string businessRole) { if (costUser == null) { throw new ArgumentNullException(nameof(costUser)); } if (costUser.UserBusinessRoles == null) { throw new ArgumentException($"{nameof(costUser.UserBusinessRoles)} are missing"); } if (costStageRevision == null) { throw new ArgumentException($"{nameof(costStageRevision)} is missing"); } if (costStageRevision.StageDetails == null) { throw new ArgumentException($"{nameof(costStageRevision.StageDetails)} is missing"); } var stageDetails = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevision); var smoName = stageDetails.SmoName; var hasRole = costUser.UserBusinessRoles.Any(ubr => ubr.BusinessRole != null && ubr.BusinessRole.Key == businessRole && (ubr.ObjectId != null || ubr.Labels.Contains(smoName)) ); return(hasRole); }
public async Task <ICollection <CostLineItem> > InterpolateCostLineItems( Guid costId, Guid costStageRevisionId, BillingExpenseData billingExpense) { /* * Base Compensation Total feeds into "Base Compensation" line of Cost Section * Pension & Health Total feeds into "Pension & Health" line of Cost Section * Bonus Total feeds into “Bonus" line of Cost Section * Agency Fee Total feeds into "Negotiation/broker agency fee * Other incurred Costs Total feeds into "Other services and fees" */ var interpolatedItems = new List <CostLineItem>(); var stageForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId); string contentType = stageForm.GetContentType(); string production = stageForm.GetProductionType(); var cost = await _efContext.Cost .Include(c => c.CostTemplateVersion) .ThenInclude(ctv => ctv.CostTemplate) .ThenInclude(ct => ct.FieldDefinitions) .FirstOrDefaultAsync(c => c.Id == costId); var costStageRevision = await _efContext.CostStageRevision .Include(c => c.CostLineItems) .FirstOrDefaultAsync(c => c.Id == costStageRevisionId); var paymentCurrency = await _efContext.Currency.FirstOrDefaultAsync(c => c.Code == stageForm.AgencyCurrency); var templateModel = await _costTemplateVersionService.GetCostTemplateVersionModel(cost.CostTemplateVersionId); var form = _costSectionFinder.GetCostSection(templateModel, contentType, production).Result; var costLineItems = costStageRevision.CostLineItems; var baseCompensation = GetOrCreateCostLineItem(form, costLineItems, "baseCompensation", paymentCurrency); var pensionAndHealth = GetOrCreateCostLineItem(form, costLineItems, "pensionAndHealth", paymentCurrency); var bonusCelebrityOnly = GetOrCreateCostLineItem(form, costLineItems, "bonusCelebrityOnly", paymentCurrency); var negotiationBrokerAgencyFee = GetOrCreateCostLineItem(form, costLineItems, "negotiationBrokerAgencyFee", paymentCurrency); var otherServicesAndFees = GetOrCreateCostLineItem(form, costLineItems, "otherServicesAndFees", paymentCurrency); interpolatedItems.AddRange(new[] { baseCompensation, pensionAndHealth, bonusCelebrityOnly, negotiationBrokerAgencyFee, otherServicesAndFees }); baseCompensation.ValueInLocalCurrency = GetBillingExpenseItemValue(billingExpense, Constants.BillingExpenseItem.UsageBuyoutFee); pensionAndHealth.ValueInLocalCurrency = GetBillingExpenseItemValue(billingExpense, Constants.BillingExpenseItem.PensionAndHealth); bonusCelebrityOnly.ValueInLocalCurrency = GetBillingExpenseItemValue(billingExpense, Constants.BillingExpenseItem.Bonus); negotiationBrokerAgencyFee.ValueInLocalCurrency = GetBillingExpenseItemValue(billingExpense, Constants.BillingExpenseItem.AgencyFee); otherServicesAndFees.ValueInLocalCurrency = GetBillingExpenseItemValue(billingExpense, Constants.BillingExpenseItem.OtherCosts); var exchangeRates = await _costExchangeRateService.GetExchangeRatesByDefaultCurrency(costStageRevision.CostStage.CostId); UpdateDefaultCurrency(interpolatedItems, exchangeRates); return(interpolatedItems); }
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 AddUserToExistingCosts(CostUser user, UserBusinessRole userBusinessRole) { if (userBusinessRole.BusinessRole.Key == Constants.BusinessRole.RegionalAgencyUser && userBusinessRole.ObjectType == core.Constants.AccessObjectType.Region) { var costs = await _efContext.Cost .Where(c => c.Owner.Agency.GlobalAgencyRegionId != null) .Where(c => userBusinessRole.LocationIds.Contains(c.Owner.Agency.GlobalAgencyRegion.Id.ToString())) .Select(c => new { costId = c.Id, agencyRegion = c.Owner.Agency.GlobalAgencyRegion.Region }) .ToListAsync(); foreach (var cost in costs) { await _permissionService.GrantUserAccess <Cost>( userBusinessRole.BusinessRole.RoleId, cost.costId, user, BuType.Pg, null, cost.agencyRegion, false); } } else { var queryableCosts = GetQueryableCostsByUserObjectTypeAndLabels(userBusinessRole.ObjectType, userBusinessRole.Labels); if (queryableCosts == null) { return; } var costs = await queryableCosts .Include(c => c.LatestCostStageRevision) .ThenInclude(csr => csr.StageDetails) .ToArrayAsync(); foreach (var cost in costs) { var costStageDetails = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(cost.LatestCostStageRevision); switch (userBusinessRole.ObjectType) { case core.Constants.AccessObjectType.Smo: if (!user.UserUserGroups.Any() || !user.UserUserGroups.Any(uug => uug.UserGroup.Label == costStageDetails.SmoName && uug.UserGroup.ObjectId == cost.Id)) { await _permissionService.GrantUserAccess <Cost>( userBusinessRole.BusinessRole.RoleId, cost.Id, user, BuType.Pg, null, costStageDetails.SmoName, false); } break; case core.Constants.AccessObjectType.Region: if (!user.UserUserGroups.Any() || user.UserUserGroups.Any(uug => uug.UserGroup.Label != costStageDetails.BudgetRegion.Name)) { await _permissionService.GrantUserAccess <Cost>( userBusinessRole.BusinessRole.RoleId, cost.Id, user, BuType.Pg, null, costStageDetails.BudgetRegion.Name, false); } break; } } } }
public async Task <ICollection <MetadataItem> > Provide(Guid costId) { var metadataItems = new List <MetadataItem>(); if (costId == Guid.Empty) { Logger.Warning("EN001: CostId is empty Guid."); return(metadataItems); } var cost = await _efContext.Cost .AsNoTracking() .Include(c => c.Owner) .ThenInclude(cu => cu.Agency) .ThenInclude(a => a.Country) .Include(c => c.LatestCostStageRevision) .ThenInclude(csr => csr.Approvals) .ThenInclude(a => a.ApprovalMembers) .ThenInclude(am => am.CostUser) .Include(c => c.LatestCostStageRevision) .ThenInclude(csr => csr.StageDetails) .Include(c => c.Project) .ThenInclude(p => p.Brand) .FirstOrDefaultAsync(c => c.Id == costId); if (cost == null) { Logger.Warning($"EN002: Cost with Id {costId} not found."); return(metadataItems); } var costOwner = cost.Owner; if (costOwner == null) { Logger.Warning($"EN003: CostOwner with Id {cost.OwnerId} not found."); return(metadataItems); } var stageDetails = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(cost.LatestCostStageRevision); if (stageDetails == null) { Logger.Warning($"EN004: Stage Details for Cost Id {costId} not found."); return(metadataItems); } AddMetadataItems(cost, costOwner, stageDetails, metadataItems); return(metadataItems); }
protected void PopulateOtherFields(EmailNotificationMessage <CostNotificationObject> message, string parent, DateTime timestamp, Guid costId, Guid costStageRevisionId, CostUser approvalCostUser = null) { message.Timestamp = timestamp; CostNotificationObject obj = message.Object; core.Models.Notifications.Cost cost = obj.Cost; PgStageDetailsForm stageForm = CostStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId).Result; cost.Title = stageForm.Title; switch (cost.CostType) { case core.Models.CostTemplate.CostType.Production: cost.ProductionType = stageForm.ProductionType?.Key; cost.ContentType = stageForm.ContentType.Key; break; case core.Models.CostTemplate.CostType.Buyout: cost.ProductionType = stageForm.UsageBuyoutType.Key; cost.ContentType = stageForm.UsageType?.Key; break; } cost.Url = _uriHelper.GetLink(ApplicationUriName.CostRevisionReview, costStageRevisionId.ToString(), costId.ToString()); AssignApproverType(message, approvalCostUser); if (!string.IsNullOrEmpty(parent)) { obj.Parents.Add(parent); } if (!string.IsNullOrEmpty(AppSettings.EnvironmentEmailSubjectPrefix)) { obj.EnvironmentEmailSubjectPrefix = AppSettings.EnvironmentEmailSubjectPrefix; } }
public async Task <IEnumerable <DictionaryEntry> > GetMediaTypes(Guid costStageRevisionId) { var costStageRevision = await _efContext.CostStageRevision .Include(csr => csr.StageDetails) .Include(csr => csr.CostStage) .ThenInclude(cs => cs.Cost) .FirstOrDefaultAsync(csr => csr.Id == costStageRevisionId); if (costStageRevision == null) { throw new Exception($"Couldn't find costStageRevision with Id {costStageRevisionId}"); } var cost = costStageRevision.CostStage.Cost; var stageDetails = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevision); List <DictionaryEntry> entries; switch (cost.CostType) { case CostType.Production: entries = await _efContext.DependentItem .Where(di => di.ParentId == stageDetails.ContentType.Id) .Join(_efContext.DictionaryEntry, di => di.ChildId, de => de.Id, (di, de) => de) .ToListAsync(); break; case CostType.Buyout: entries = await _efContext.DependentItem .Where(di => di.ParentId == stageDetails.UsageBuyoutType.Id) .Join(_efContext.DictionaryEntry, di => di.ChildId, de => de.Id, (di, de) => de) .ToListAsync(); break; default: entries = await _efContext.DictionaryEntry .Where(de => de.Dictionary.Name == Constants.DictionaryNames.MediaType && de.Key == Constants.MediaType.NA) .ToListAsync(); break; } return(entries); }
private async Task PopulateOtherFieldsForRecall(EmailNotificationMessage <CostNotificationObject> message, string parent, DateTime timestamp, Guid costId, Guid costStageRevisionId) { PopulateOtherFields(message, parent, timestamp, costId, costStageRevisionId); // Add fields specific to Recall notification message var obj = message.Object; var cost = obj.Cost; var stageForm = _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId).Result; var buyoutDetails = _costFormService.GetCostFormDetails <BuyoutDetails>(costStageRevisionId).Result; cost.AgencyTrackingNumber = stageForm.AgencyTrackingNumber; cost.Region = stageForm.BudgetRegion?.Name; cost.AiringCountries = string.Join(";", (buyoutDetails?.AiringCountries ?? new BuyoutDetails.Country[0]).Select(c => c.Name).ToArray()); cost.Requisition = (await _customObjectDataService.GetCustomData <PgPurchaseOrderResponse>(costStageRevisionId, CustomObjectDataKeys.PgPurchaseOrderResponse))?.Requisition; }
public async Task <CostStageRevisionStatus> GetNextStatus(Guid costId, CostAction action) { var cost = await _efContext.Cost .Include(c => c.LatestCostStageRevision) .ThenInclude(csr => csr.Approvals) .ThenInclude(a => a.ApprovalMembers) .Include(c => c.Project) .Include(c => c.Parent) .ThenInclude(p => p.Agency) .FirstOrDefaultAsync(c => c.Id == costId); _logger.Information($"Working out next status for the cost {cost.CostNumber} {cost.Id}. Current status: {cost.Status} Action: {action}"); var stageDetails = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(cost.LatestCostStageRevision.Id); var rules = await _ruleService.GetCompiledByRuleType <PgStatusRule>(RuleType.Status); var testRule = new PgStatusRule { Status = cost.LatestCostStageRevision.Status.ToString(), BudgetRegion = stageDetails.BudgetRegion?.Key, Action = action.ToString(), CostType = cost.CostType.ToString(), IsCyclone = cost.Parent.Agency.IsCyclone(), HasTechnicalApproval = cost.LatestCostStageRevision.Approvals.Any(a => a.Type == ApprovalType.IPM), HasBrandApproval = cost.LatestCostStageRevision.Approvals.Any(a => a.Type == ApprovalType.Brand), CostStage = cost.LatestCostStageRevision.Name }; if (_ruleService.TryMatchRule(rules, testRule, (r, dr) => JsonConvert.DeserializeObject <PgStatusRuleDefinition>(dr.Definition).Status, out var status)) { _logger.Information($"Next cost status for cost {cost.CostNumber} is {status}. Previous status {cost.Status}"); return(status); } throw new Exception($"Couldn't find status transition for cost {cost.CostNumber} rule: {JsonConvert.SerializeObject(testRule)}"); }
public async Task <ServiceResult <BillingExpenseViewModel> > Get(Guid costStageRevisionId) { //Get the contract period var usageForm = await _costFormService.GetCostFormDetails <BuyoutDetails>(costStageRevisionId); var contract = usageForm.Contract; var startDate = contract.StartDate; var endDate = contract.EndDate ?? DateTime.UtcNow; //Build the financial years based on start and end date var financialYears = await _financialYearService.Calculate(BuType.Pg, startDate, endDate); var model = new BillingExpenseViewModel(); //Get the payment currency from the stage details form var stageForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId); var paymentCurrency = await _efContext.Currency.FirstOrDefaultAsync(c => c.Code == stageForm.AgencyCurrency); model.PaymentCurrency = paymentCurrency; var billingExpenses = await _efContext.BillingExpense.Where(be => be.CostStageRevisionId == costStageRevisionId).ToListAsync(); //Build the billing expenses var costStage = await _efContext.CostStageRevision .Where(c => c.Id == costStageRevisionId) .Select(c => c.CostStage).FirstOrDefaultAsync(); model.Data = _builder.BuildExpenses(costStage, billingExpenses, financialYears.Result); model.Modified = billingExpenses.Count > 0 ? billingExpenses.Max(b => b.Modified) : null; model.Saved = model.Modified != null; //Perform any calculations await _calculator.CalculateExpenses(usageForm.Contract.ContractTotal, model.Data, billingExpenses); return(ServiceResult <BillingExpenseViewModel> .CreateSuccessfulResult(model)); }
public async Task <ServiceResult <BudgetFormUploadResult> > UploadBudgetForm(Guid costId, Guid costStageId, Guid costStageRevisionId, UserIdentity userIdentity, IFormFile file) { //Read the properties from the Properties excel sheet. ExcelProperties properties; using (var fileStream = file.OpenReadStream()) { properties = _excelCellService.ReadProperties(file.FileName, fileStream); } //Validate the uploaded budget form is the correct format for the cost being updated. var stageForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId); string contentType = stageForm.GetContentType(); string production = stageForm.GetProductionType(); var validationResult = _budgetFormValidator.IsValid(properties, contentType, production); if (!validationResult.Success) { return(ServiceResult <BudgetFormUploadResult> .CloneFailedResult(validationResult)); } //Read the cost line items ExcelCellValueLookup entries; using (var fileStream = file.OpenReadStream()) { entries = await _excelCellService.ReadEntries(properties[core.Constants.BudgetFormExcelPropertyNames.LookupGroup], file.FileName, fileStream); } //Update the currencies in the Cost var currencyResult = await _costCurrencyUpdater.Update(entries, userIdentity.Id, costId, costStageRevisionId); if (!currencyResult.Success) { return(ServiceResult <BudgetFormUploadResult> .CloneFailedResult(currencyResult)); } //Update the cost var costLineItemResult = await _costLineItemUpdater.Update(entries, userIdentity, costId, costStageRevisionId); if (!costLineItemResult.Success) { return(ServiceResult <BudgetFormUploadResult> .CloneFailedResult(costLineItemResult)); } //Save updates made by _costLineItemUpdater and _costCurrencyUpdater. await _efContext.SaveChangesAsync(); //Add Budget form to GDN using Gdam Core. var supportingDocument = await _supportingDocumentsService.GetSupportingDocument(costStageRevisionId, core.Constants.SupportingDocuments.BudgetForm); await _supportingDocumentsService.UploadSupportingDocumentRevision(costId, supportingDocument, userIdentity, file); var supportingDocumentViewModel = _mapper.Map <SupportingDocumentViewModel>(supportingDocument); var costLineItemModels = _mapper.Map <List <CostLineItemViewModel> >(costLineItemResult.Result); var budgetFormUploadResult = new BudgetFormUploadResult { Currencies = currencyResult.Result, CostLineItems = costLineItemModels, SupportingDocument = supportingDocumentViewModel }; var costNumber = await _efContext.Cost.Where(c => c.Id == costId).Select(c => c.CostNumber).SingleAsync(); await _activityLogService.Log(new BudgetFormUploaded(costNumber, file.FileName, supportingDocument.Id, userIdentity)); return(new ServiceResult <BudgetFormUploadResult>(budgetFormUploadResult)); }
public async Task <ServiceResult <List <CostLineItem> > > Update(ExcelCellValueLookup entries, UserIdentity userIdentity, Guid costId, Guid costStageRevisionId) { if (entries == null) { Logger.Warning("Param entries is null reference. This means the uploaded budget form was not read correctly."); return(ServiceResult <List <CostLineItem> > .CreateFailedResult(TechnicalErrorMessage)); } if (userIdentity == null) { Logger.Warning("Param userIdentity is null reference."); return(ServiceResult <List <CostLineItem> > .CreateFailedResult(TechnicalErrorMessage)); } if (costId == Guid.Empty) { Logger.Warning("Param costId is Guid.Empty."); return(ServiceResult <List <CostLineItem> > .CreateFailedResult(TechnicalErrorMessage)); } if (costStageRevisionId == Guid.Empty) { Logger.Warning("Param costStageRevisionId is Guid.Empty."); return(ServiceResult <List <CostLineItem> > .CreateFailedResult(TechnicalErrorMessage)); } if (entries.Count == 0) { Logger.Warning("Param entries is empty collection. This means the uploaded budget form was not read correctly."); return(ServiceResult <List <CostLineItem> > .CreateFailedResult(TechnicalErrorMessage)); } var updatedItems = new List <CostLineItem>(); var serviceResult = new ServiceResult <List <CostLineItem> >(updatedItems); var stageForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId); var contentType = stageForm.GetContentType(); var production = stageForm.GetProductionType(); //Get the form var cost = await _efContext.Cost .Include(c => c.CostTemplateVersion) .ThenInclude(ctv => ctv.CostTemplate) .ThenInclude(ct => ct.FieldDefinitions) .FirstOrDefaultAsync(c => c.Id == costId); var costStageRevision = await _efContext.CostStageRevision .Include(c => c.CostLineItems) .FirstOrDefaultAsync(c => c.Id == costStageRevisionId); var templateModel = await _costTemplateVersionService.GetCostTemplateVersionModel(cost.CostTemplateVersionId); var formResult = _costSectionFinder.GetCostSection(templateModel, contentType, production); if (!formResult.Success) { serviceResult.AddErrorRange(formResult.Errors); return(serviceResult); } var form = formResult.Result; // SPB-3005 --> ADC-2892 var exchangeRates = await _costExchangeRateService.GetExchangeRatesByDefaultCurrency(costId); //Iterate through the cost line items foreach (var costSection in form.CostLineItemSections) { foreach (var item in costSection.Items) { //Build a lookup key that will look in the LookupKey column for an exact match. // lookupKey format is form.costSection.item.currency or costSection.item.currency var defaultLookupKey = GetDefaultCurrencyLookupKey(form, costSection, item); var localLookupKey = GetLocalCurrencyLookupKey(form, costSection, item); if (!entries.ContainsKey(defaultLookupKey)) { // if the lookupKey does not exist with the form.Name included, try without the form. var message = $"BF001: {form.Name}:{costSection.Name}:{item.Name} not found for cost: {costId} in uploaded budget form. Trying {costSection.Name}:{item.Name}..."; Logger.Warning(message); defaultLookupKey = GetDefaultCurrencyLookupKey(costSection, item); } if (!entries.ContainsKey(localLookupKey)) { localLookupKey = GetLocalCurrencyLookupKey(costSection, item); } if (entries.ContainsKey(defaultLookupKey) && entries.ContainsKey(localLookupKey)) { var defaultCurrencyStringValue = entries[defaultLookupKey].Value; var localCurrencyStringValue = entries[localLookupKey].Value; var updateCell = true; if (!decimal.TryParse(localCurrencyStringValue, out var localCurrencyValue)) { var warning = $"BF002: Invalid entry '{localCurrencyStringValue}' for {costSection.Name}:{item.Name} for cost: {costId} in uploaded budget form."; Logger.Warning(warning); var userError = $"Invalid entry '{localCurrencyStringValue}' in cell '{entries[localLookupKey].Name}'."; serviceResult.AddError(userError); updateCell = false; } if (!decimal.TryParse(defaultCurrencyStringValue, out var defaultCurrencyValue)) { var warning = $"BF003: Invalid entry '{defaultCurrencyStringValue}' for {costSection.Name}:{item.Name} for cost: {costId} in uploaded budget form."; Logger.Warning(warning); var userError = $"Invalid entry '{defaultCurrencyStringValue}' in cell '{entries[defaultLookupKey].Name}'."; serviceResult.AddError(userError); updateCell = false; } if (updateCell) { var cli = GetOrCreateCostLineItem(costStageRevision, userIdentity.Id, costSection, item); var currency = await GetCurrency(entries, costSection, item); if (HasCurrencyChanged(currency, cli) && !CanUpdateCurrency(costStageRevision)) { var costSectionCurrency = await GetCurrencyCode(cli.LocalCurrencyId); var error = new FeedbackMessage( $"The currency you have chosen in the budget form, {currency.Code} does not match the cost section currency of {costSectionCurrency}. There are two options to progress this:"); error.AddSuggestion("You may change the currency of your budget form to match the cost section currency and re-upload the Budget form."); error.AddSuggestion( "You may cancel this datasheet, create a new cost and select the required currency. After cancelling your cost please contact P&G. They will raise and issue credit for any monies you have received against the original PO."); serviceResult.AddError(error); return(serviceResult); } //Update the cost line item cli.LocalCurrencyId = currency.Id; var exchangeRate = exchangeRates.FirstOrDefault(ex => ex.FromCurrency == cli.LocalCurrencyId); cli.ValueInDefaultCurrency = (exchangeRate?.Rate * localCurrencyValue) ?? 0; //we calculate it because we can't trust the sent value from front-end cli.ValueInLocalCurrency = localCurrencyValue; cli.SetModifiedNow(); updatedItems.Add(cli); } } else { var userError = $"BF004: {costSection.Name}:{item.Name} not found for cost: {costId} in uploaded budget form. Adding Zero as default value."; Logger.Warning(userError); serviceResult.AddWarning(userError); //Create a default value if (item.Mandatory.HasValue && item.Mandatory.Value) { var cli = GetOrCreateCostLineItem(costStageRevision, userIdentity.Id, costSection, item); var currency = await GetCurrency(entries, costSection, item); //Update the cost line item cli.LocalCurrencyId = currency.Id; cli.ValueInDefaultCurrency = 0; cli.ValueInLocalCurrency = 0; cli.SetModifiedNow(); updatedItems.Add(cli); } } } } return(serviceResult); }
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 <ServiceResult <Dictionary <string, Guid> > > Update(ExcelCellValueLookup entries, Guid userId, Guid costId, Guid costStageRevisionId) { if (entries == null) { Logger.Warning("Param entries is null reference. This means the uploaded budget form was not read correctly."); return(ServiceResult <Dictionary <string, Guid> > .CreateFailedResult(TechnicalErrorMessage)); } if (userId == Guid.Empty) { Logger.Warning("Param userId is Guid.Empty."); return(ServiceResult <Dictionary <string, Guid> > .CreateFailedResult(TechnicalErrorMessage)); } if (costId == Guid.Empty) { Logger.Warning("Param costId is Guid.Empty."); return(ServiceResult <Dictionary <string, Guid> > .CreateFailedResult(TechnicalErrorMessage)); } if (costStageRevisionId == Guid.Empty) { Logger.Warning("Param costStageRevisionId is Guid.Empty."); return(ServiceResult <Dictionary <string, Guid> > .CreateFailedResult(TechnicalErrorMessage)); } if (entries.Count == 0) { Logger.Warning("Param entries is empty collection. This means the uploaded budget form was not read correctly."); return(ServiceResult <Dictionary <string, Guid> > .CreateFailedResult(TechnicalErrorMessage)); } var stageForm = await _costStageRevisionService.GetStageDetails <PgStageDetailsForm>(costStageRevisionId); var productionForm = await _costStageRevisionService.GetProductionDetails <PgProductionDetailsForm>(costStageRevisionId); var costStageRevision = await _efContext.CostStageRevision .Include(csr => csr.CostStage) .ThenInclude(csr => csr.CostStageRevisions) .FirstOrDefaultAsync(c => c.Id == costStageRevisionId); //Update the Agency Currency, if different var agencyCurrencyCode = GetAgencyCurrencyCode(entries); // return a dictionary of the currencies that have been modified i.e. agency, dpv, section. // Key is agency, dpv or section name and value is the currencyId. The FE has all the Ids and can update itself. var currencies = new Dictionary <string, Guid>(); var serviceResult = new ServiceResult <Dictionary <string, Guid> >(currencies); if (stageForm.AgencyCurrency != agencyCurrencyCode) { if (CanUpdateAgencyCurrency(costStageRevision)) { var stageDetails = await _efContext.CustomFormData.FirstOrDefaultAsync(c => c.Id == costStageRevision.StageDetailsId); UpdateAgencyCurrency(stageDetails, agencyCurrencyCode); var agencyCurrencyId = await GetCurrencyId(agencyCurrencyCode); currencies[AgencyCurrencyKey] = agencyCurrencyId; } else { var error = new FeedbackMessage( $"The agency currency you have chosen in the budget form, {agencyCurrencyCode} does not match your datasheet of {stageForm.AgencyCurrency}. There are two options to progress this:"); error.AddSuggestion("You may change the currency of your budget form to match the datasheet currency and re-upload the Budget form."); error.AddSuggestion( "You may cancel this datasheet, create a new cost and select the required agency currency. After cancelling your cost please contact P&G. They will raise and issue credit for any monies you have received against the original PO."); serviceResult.AddError(error); return(serviceResult); } } // The Cost Section Currencies done by the ICostLineItemUpdater because each CostLineItem has a currency. // On the UI, the Cost Section currency is set by rolling up from the first CostLineItem. // Here we extrapolate the currencies for the DPV and the cost line item sections in order to simplify the // front end code. At this present time, the FE calculates the CLI Section currency based on the first item it finds. if (productionForm.DirectPaymentVendor != null) { var dpvCurrencyCode = GetDpvCurrencyCode(entries, stageForm); var dpvCurrencyId = await GetCurrencyId(dpvCurrencyCode); var productionDetails = await _efContext.CustomFormData.FirstOrDefaultAsync(c => c.Id == costStageRevision.ProductDetailsId); UpdateDpvCurrency(productionDetails, dpvCurrencyId); currencies[DirectPaymentVendorCurrencyKey] = dpvCurrencyId; } return(serviceResult); }