public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next) { var product = context.Product; var options = context.Options; // Ignore tier prices of bundle items (BundlePerItemPricing). var processTierPrices = !options.IgnoreTierPrices && !options.IgnoreDiscounts && product.HasTierPrices && context.BundleItem?.Item == null; if (processTierPrices) { var tierPrices = await context.GetTierPricesAsync(); // Put minimum tier price to context because it's required for discount calculation. context.MinTierPrice = GetMinimumTierPrice(product, tierPrices, context.Quantity); if (context.Options.DetermineLowestPrice && !context.HasPriceRange) { context.HasPriceRange = tierPrices.Any() && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1); } } // Process the whole pipeline. We need the result of discount calculation. await next(context); if (processTierPrices && context.MinTierPrice.HasValue) { // Apply the minimum tier price if it achieves a lower price than the discounted FinalPrice. context.FinalPrice = Math.Min(context.FinalPrice, context.MinTierPrice.Value); } }
public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next) { var product = context.Product; var options = context.Options; var processTierPrices = !options.IgnoreTierPrices && !options.IgnoreDiscounts && product.HasTierPrices && context.BundleItem == null; if (processTierPrices) { var tierPrices = await context.GetTierPricesAsync(); // Put minimum tier price to context because it's required for discount calculation. context.MinTierPrice = GetMinimumTierPrice(product, tierPrices, context.Quantity); if (context.Options.DetermineLowestPrice && !context.HasPriceRange) { context.HasPriceRange = tierPrices.Any() && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1); } } // Process the whole pipeline. We need the result of discount calculation. await next(context); if (processTierPrices && context.MinTierPrice.HasValue) { // Wrong result: //context.FinalPrice = Math.Min(context.FinalPrice, context.MinTierPrice.Value); // Apply the minimum tier price if it achieves a lower price than the discounted FinalPrice // but exclude additional charge from comparing. context.FinalPrice -= context.AdditionalCharge; if (context.MinTierPrice.Value < context.FinalPrice) { context.DiscountAmount += context.FinalPrice - context.MinTierPrice.Value; context.FinalPrice = context.MinTierPrice.Value; } context.FinalPrice += context.AdditionalCharge; } }
public override async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next) { var options = context.Options; var product = context.Product; if (options.IgnoreAttributes || !(context.Attributes?.Any() ?? false)) { // Proceed with pipeline and omit this calculator, it is made for attributes price calculation only. await next(context); return; } var processTierPrices = !options.IgnoreTierPrices && product.HasTierPrices && context.BundleItem?.Item == null && context.Quantity > 1 && _catalogSettings.ApplyTierPricePercentageToAttributePriceAdjustments; var attributes = await context.Options.BatchContext.Attributes.GetOrLoadAsync(product.Id); var attributeValues = GetSelectedAttributeValues(context, attributes); var linkedProductIds = attributeValues .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage && x.LinkedProductId != 0) .Select(x => x.LinkedProductId) .Distinct() .ToArray(); var linkedProducts = linkedProductIds.Any() ? await _db.Products.AsNoTracking().Where(x => linkedProductIds.Contains(x.Id)).ToDictionaryAsync(x => x.Id) : new Dictionary <int, Product>(); // Add attribute price adjustment to final price. foreach (var value in attributeValues) { if (value.LinkedProductId == 0) { if (processTierPrices && value.PriceAdjustment > decimal.Zero) { var tierPrices = await context.GetTierPricesAsync(); var priceAdjustment = GetTierPriceAttributeAdjustment(product, tierPrices, context.Quantity, value.PriceAdjustment); context.FinalPrice += priceAdjustment; } else { context.FinalPrice += value.PriceAdjustment; } } else if (linkedProducts.TryGetValue(value.LinkedProductId, out var linkedProduct)) { var childCalculation = await CalculateChildPriceAsync(linkedProduct, context, c => { c.Options.IgnoreDiscounts = false; c.Quantity = 1; c.AssociatedProducts = null; c.BundleItems = null; c.BundleItem = null; c.AdditionalCharge = decimal.Zero; c.MinTierPrice = null; }); // Add price of linked product to root final price (unit price * linked product quantity). context.FinalPrice += decimal.Multiply(childCalculation.FinalPrice, value.Quantity); } } await next(context); }
public override async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next) { var options = context.Options; var product = context.Product; if (!context.SelectedAttributes.Any() && !options.ApplyPreselectedAttributes && !options.DeterminePriceAdjustments) { // No selected attributes provided and no preselected attributes should be applied and no price adjustments should be determined, // then proceed with pipeline and omit this calculator. await next(context); return; } var includeTierPriceAttributePriceAdjustment = !options.IgnoreTierPrices && !options.IgnorePercentageTierPricesOnAttributePriceAdjustments && product.HasTierPrices && context.BundleItem == null && context.Quantity > 1; var attributes = await options.BatchContext.Attributes.GetOrLoadAsync(product.Id); var attributeValues = await GetSelectedAttributeValuesAsync(context, attributes); var hasSelectedValues = attributeValues.Any(); if (!hasSelectedValues && options.DeterminePriceAdjustments) { // Get price adjustments of ALL attribute values. Do not apply anything to FinalPrice, just return them via context.AttributePriceAdjustments. attributeValues = attributes.SelectMany(x => x.ProductVariantAttributeValues).ToList(); } // Ignore attributes that have no relevance for pricing. attributeValues = attributeValues .Where(x => x.PriceAdjustment != decimal.Zero || x.ValueType == ProductVariantAttributeValueType.ProductLinkage) .ToList(); var linkedProductIds = attributeValues .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage && x.LinkedProductId != 0) .Select(x => x.LinkedProductId) .Distinct() .ToArray(); var linkedProducts = linkedProductIds.Any() ? await _db.Products.AsNoTracking().Where(x => linkedProductIds.Contains(x.Id)).ToDictionaryAsync(x => x.Id) : new Dictionary <int, Product>(); foreach (var value in attributeValues) { // Calculate price adjustment. var adjustment = decimal.Zero; if (value.ValueType == ProductVariantAttributeValueType.Simple) { if (includeTierPriceAttributePriceAdjustment && value.PriceAdjustment > 0m) { var tierPrices = await context.GetTierPricesAsync(); adjustment = GetTierPriceAttributeAdjustment(product, tierPrices, context.Quantity, value.PriceAdjustment); } if (adjustment == 0m) { adjustment = value.PriceAdjustment; } } else if (value.ValueType == ProductVariantAttributeValueType.ProductLinkage && linkedProducts.TryGetValue(value.LinkedProductId, out var linkedProduct)) { var childCalculation = await CalculateChildPriceAsync(linkedProduct, context, c => { c.Quantity = 1; c.Options.IgnoreDiscounts = true; }); // Add price of linked product to root final price (unit price * linked product quantity). adjustment = decimal.Multiply(childCalculation.FinalPrice, value.Quantity); } if (adjustment != 0m) { // Apply the adjustment only if selected attributes have been provided. if (hasSelectedValues) { context.FinalPrice += adjustment; context.AdditionalCharge += adjustment; } if (options.DeterminePriceAdjustments) { context.AttributePriceAdjustments.Add(new CalculatedPriceAdjustment { RawPriceAdjustment = adjustment, AttributeValue = value, ProductId = product.Id, BundleItemId = context?.BundleItem?.Id }); } } } await next(context); }