private static void AddSelectedAttributes(this PriceCalculationContext context, ShoppingCartItem item) { if (item != null) { context.AddSelectedAttributes(item.AttributeSelection, item.ProductId, item.BundleItemId); } }
public virtual async Task <(decimal Amount, Discount AppliedDiscount)> GetDiscountAmountAsync( Product product, Customer customer = null, decimal additionalCharge = decimal.Zero, int quantity = 1, ProductBundleItemData bundleItem = null, PriceCalculationContext context = null, decimal?finalPrice = null) { Guard.NotNull(product, nameof(product)); customer ??= _workContext.CurrentCustomer; var discountAmount = decimal.Zero; Discount appliedDiscount = null; if (bundleItem != null && bundleItem.Item != null) { var bi = bundleItem.Item; if (bi.Discount.HasValue && bi.BundleProduct.BundlePerItemPricing) { appliedDiscount = new Discount { UsePercentage = bi.DiscountPercentage, DiscountPercentage = bi.Discount.Value, DiscountAmount = bi.Discount.Value }; var finalPriceWithoutDiscount = finalPrice ?? await GetFinalPriceAsync(product, customer, additionalCharge, false, quantity, bundleItem, context); discountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount); } } else { // Don't apply when customer entered price or discounts should be ignored in any case. if (!product.CustomerEntersPrice && _catalogSettings.IgnoreDiscounts) { return(discountAmount, appliedDiscount); } var allowedDiscounts = await GetAllowedDiscountsAsync(product, customer, context); if (!allowedDiscounts.Any()) { return(discountAmount, appliedDiscount); } var finalPriceWithoutDiscount = finalPrice ?? await GetFinalPriceAsync(product, customer, additionalCharge, false, quantity, bundleItem, context); appliedDiscount = allowedDiscounts.GetPreferredDiscount(finalPriceWithoutDiscount); if (appliedDiscount != null) { discountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount); } } return(discountAmount, appliedDiscount); }
public static void AddAttributes(this PriceCalculationContext context, ShoppingCartItem item) { Guard.NotNull(context, nameof(context)); if (item != null) { context.AddAttributes(item.AttributeSelection, item.ProductId, item.BundleItemId); } }
public virtual async Task <Money> GetProductVariantAttributeValuePriceAdjustmentAsync( ProductVariantAttributeValue attributeValue, Product product, Customer customer, PriceCalculationContext context, int quantity = 1) { return(new(await GetVariantPriceAdjustmentAsync(attributeValue, product, customer, context, quantity), _primaryCurrency)); }
protected PriceCalculationContext(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); Product = context.Product; Quantity = context.Quantity; Options = context.Options; Metadata = context.Metadata; AssociatedProducts = context.AssociatedProducts; // [...] }
protected PriceCalculationContext(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); // For reasons of practicability, we only copy the data provided by the caller // but no intermediate data determined by the pipeline itself. Product = context.Product; Quantity = context.Quantity; Options = context.Options; Metadata = context.Metadata; SelectedAttributes = context.SelectedAttributes; }
/// <summary> /// Adds selected product attributes to be taken into account in the price calculation. /// For example required for price adjustments of attributes selected by the customer. /// </summary> /// <param name="context">The target product calculation context.</param> /// <param name="selection">The selected product attributes.</param> /// <param name="productId">Product identifier.</param> /// <param name="bundleItemId">Bundle item identifier if the related product is a bundle item.</param> public static void AddSelectedAttributes(this PriceCalculationContext context, ProductVariantAttributeSelection selection, int productId, int?bundleItemId = null) { Guard.NotNull(context, nameof(context)); if (selection?.AttributesMap?.Any() ?? false) { context.SelectedAttributes.Add(new PriceCalculationAttributes(selection, productId) { BundleItemId = bundleItemId }); } }
public virtual async Task <decimal> GetFinalPriceAsync( Product product, IEnumerable <ProductBundleItemData> bundleItems, Customer customer = null, decimal additionalCharge = decimal.Zero, bool includeDiscounts = true, int quantity = 1, ProductBundleItemData bundleItem = null, PriceCalculationContext context = null) { Guard.NotNull(product, nameof(product)); customer ??= _workContext.CurrentCustomer; if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { var result = decimal.Zero; var items = bundleItems; if (items == null) { IEnumerable <ProductBundleItem> loadedBundleItems; if (context == null) { loadedBundleItems = await _db.ProductBundleItem .AsNoTracking() .Include(x => x.Product) .ApplyBundledProductsFilter(new int[] { product.Id }) .ToListAsync(); } else { loadedBundleItems = await context.ProductBundleItems.GetOrLoadAsync(product.Id); } items = loadedBundleItems.Select(x => new ProductBundleItemData(x)).ToList(); } foreach (var itemData in items.Where(x => x?.Item != null)) { var itemPrice = await GetFinalPriceAsync(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); result += decimal.Multiply(itemPrice, itemData.Item.Quantity); } return(result < decimal.Zero ? decimal.Zero : result); } return(await GetFinalPriceAsync(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem, context)); }
protected PriceCalculationContext(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); Product = context.Product; CartItem = context.CartItem; AssociatedProducts = context.AssociatedProducts; BundleItems = context.BundleItems; BundleItem = context.BundleItem; Quantity = context.Quantity; Options = context.Options; Metadata = context.Metadata; SelectedAttributes = context.SelectedAttributes; }
public virtual async Task <CalculatedPrice> CalculatePriceAsync(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); // Remember source product. var product = context.Product; var calculatorContext = context.CartItem != null && product.CustomerEntersPrice ? new CalculatorContext(context, context.CartItem.Item.CustomerEnteredPrice) : await RunCalculators(context); var unitPrice = await CreateCalculatedPrice(calculatorContext, product); return(unitPrice); }
protected PriceCalculationContext(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); Product = context.Product; Quantity = context.Quantity; Options = context.Options; Metadata = context.Metadata; AssociatedProducts = context.AssociatedProducts; BundleItems = context.BundleItems; BundleItem = context.BundleItem; Attributes = context.Attributes; AdditionalCharge = context.AdditionalCharge; // [...] }
/// <summary> /// Adds selected product attributes of a shopping cart item to be taken into account in the price calculation. /// For example required for price adjustments of attributes selected by the customer. /// Also adds selected attributes of bundle items if <see cref="Product.BundlePerItemPricing"/> is activated. /// </summary> /// <param name="context">The target product calculation context.</param> /// <param name="item">Shopping cart item.</param> public static void AddSelectedAttributes(this PriceCalculationContext context, OrganizedShoppingCartItem cartItem) { Guard.NotNull(context, nameof(context)); if (cartItem != null) { var item = cartItem.Item; context.AddSelectedAttributes(item); if (item.Product.ProductType == ProductType.BundledProduct && item.Product.BundlePerItemPricing) { cartItem.ChildItems.Each(x => context.AddSelectedAttributes(x.Item)); } } }
private async Task <CalculatorContext> RunCalculators(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); // Remember source product. var product = context.Product; // Collect calculators var calculators = _calculatorFactory.GetCalculators(context); var calculatorContext = new CalculatorContext(context, product.Price); // Run all collected calculators await _calculatorFactory.RunCalculators(calculators, calculatorContext); return(calculatorContext); }
public static void AddAttributes(this PriceCalculationContext context, IEnumerable <OrganizedShoppingCartItem> cart) { Guard.NotNull(context, nameof(context)); var item = cart?.FirstOrDefault(x => x.Item.ProductId == context.Product.Id); if (item?.Item != null) { context.AddAttributes(item.Item); if (item.Item.Product.ProductType == ProductType.BundledProduct && item.Item.Product.BundlePerItemPricing) { item.ChildItems.Each(x => context.AddAttributes(x.Item)); } } }
/// <summary> /// Calculates the unit price for a given shopping cart item. /// </summary> /// <param name="priceCalculationService">Price calculation service.</param> /// <param name="cartItem">Shopping cart item.</param> /// <param name="ignoreDiscounts">A value indicating whether to ignore discounts.</param> /// <param name="targetCurrency">The target currency to use for money conversion. Obtained from <see cref="IWorkContext.WorkingCurrency"/> if <c>null</c>.</param> /// <returns>Calculated unit price.</returns> public static async Task <CalculatedPrice> CalculateUnitPriceAsync( this IPriceCalculationService2 priceCalculationService, OrganizedShoppingCartItem cartItem, bool ignoreDiscounts = false, Currency targetCurrency = null) { Guard.NotNull(priceCalculationService, nameof(priceCalculationService)); Guard.NotNull(cartItem, nameof(cartItem)); var options = priceCalculationService.CreateDefaultOptions(false, cartItem.Item.Customer, targetCurrency); options.IgnoreDiscounts = ignoreDiscounts; var context = new PriceCalculationContext(cartItem, options); return(await priceCalculationService.CalculatePriceAsync(context)); }
/// <summary> /// Gets the base price info for a product. /// </summary> /// <param name="priceCalculationService">Price calculation service.</param> /// <param name="product">The product to get the base price info for.</param> /// <param name="options">Price calculation options. The default options are used if <c>null</c>.</param> /// <returns>Base price info.</returns> public static async Task <string> GetBasePriceInfoAsync(this IPriceCalculationService priceCalculationService, Product product, PriceCalculationOptions options = null) { Guard.NotNull(priceCalculationService, nameof(priceCalculationService)); Guard.NotNull(product, nameof(product)); if (!product.BasePriceHasValue || product.BasePriceAmount == 0) { return(string.Empty); } options ??= priceCalculationService.CreateDefaultOptions(false); var context = new PriceCalculationContext(product, options); var price = await priceCalculationService.CalculatePriceAsync(context); return(priceCalculationService.GetBasePriceInfo(product, price.FinalPrice, options.TargetCurrency)); }
public virtual async Task <PriceCalculationContext> CreateCalculationContextAsync(OrganizedShoppingCartItem cartItem, PriceCalculationOptions options) { Guard.NotNull(cartItem, nameof(cartItem)); Guard.NotNull(options, nameof(options)); var product = cartItem.Item.Product; var context = new PriceCalculationContext(product, cartItem.Item.Quantity, options) { CartItem = cartItem }; // Include attributes selected for this cart item in price calculation. context.AddSelectedAttributes(cartItem); // Include bundle item data if the cart item is a bundle item. if (cartItem.Item.BundleItem != null) { context.BundleItem = cartItem.Item.BundleItem; } // Perf: we already have the bundle items of a bundled product. No need to load them again during calculation. if (cartItem.ChildItems?.Any() ?? false) { context.BundleItems = cartItem.ChildItems .Where(x => x.Item.BundleItem != null) .Select(x => x.Item.BundleItem) .ToList(); } if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { Guard.NotNull(cartItem.ChildItems, nameof(cartItem.ChildItems)); foreach (var bundleItem in cartItem.ChildItems) { await _productAttributeMaterializer.MergeWithCombinationAsync(bundleItem.Item.Product, bundleItem.Item.AttributeSelection); } } else { await _productAttributeMaterializer.MergeWithCombinationAsync(product, cartItem.Item.AttributeSelection); } return(context); }
protected virtual bool MatchCalculator(PriceCalculationContext context, PriceCalculatorMetadata metadata) { var productType = context.Product.ProductType; if (productType == ProductType.SimpleProduct) { return(metadata.ValidTargets.HasFlag(CalculatorTargets.Product)); } else if (productType == ProductType.BundledProduct) { return(metadata.ValidTargets.HasFlag(CalculatorTargets.Bundle)); } else if (productType == ProductType.GroupedProduct) { return(metadata.ValidTargets.HasFlag(CalculatorTargets.GroupedProduct)); } return(false); }
public virtual async Task <(Money?LowestPrice, Product LowestPriceProduct)> GetLowestPriceAsync( Product product, Customer customer, PriceCalculationContext context, IEnumerable <Product> associatedProducts) { Guard.NotNull(product, nameof(product)); Guard.NotNull(associatedProducts, nameof(associatedProducts)); if (product.ProductType != ProductType.GroupedProduct) { throw Error.InvalidOperation("Choose the other override for products not of type grouped product."); } decimal?lowestPrice = null; Product lowestPriceProduct = null; context ??= new PriceCalculationContext(null, _services, _storeContext.CurrentStore, customer ?? _workContext.CurrentCustomer, true); foreach (var associatedProduct in associatedProducts) { var tmpPrice = await GetFinalPriceAmountAsync(associatedProduct, decimal.Zero, customer, true, int.MaxValue, null, context); if (associatedProduct.LowestAttributeCombinationPrice.HasValue && associatedProduct.LowestAttributeCombinationPrice.Value < tmpPrice) { tmpPrice = associatedProduct.LowestAttributeCombinationPrice.Value; } if (!lowestPrice.HasValue || tmpPrice < lowestPrice.Value) { lowestPrice = tmpPrice; lowestPriceProduct = associatedProduct; } } if (lowestPriceProduct == null) { lowestPriceProduct = associatedProducts.FirstOrDefault(); } return(lowestPrice.HasValue ? new(lowestPrice.Value, _primaryCurrency) : null, lowestPriceProduct); }
public virtual async Task <Money> GetFinalPriceAsync( Product product, IEnumerable <ProductBundleItemData> bundleItems, Money?additionalCharge, Customer customer = null, bool includeDiscounts = true, int quantity = 1, ProductBundleItemData bundleItem = null, PriceCalculationContext context = null) { return(new(await GetFinalPriceAmountAsync( product, bundleItems, additionalCharge?.Amount ?? decimal.Zero, customer, includeDiscounts, quantity, bundleItem, context), _primaryCurrency)); }
public virtual async Task <(Money Amount, Discount AppliedDiscount)> GetDiscountAmountAsync( Product product, Money?additionalCharge, Customer customer = null, int quantity = 1, ProductBundleItemData bundleItem = null, PriceCalculationContext context = null, Money?finalPrice = null) { var(discountAmount, appliedDiscount) = await GetDiscountAmountAsync( product, additionalCharge?.Amount ?? decimal.Zero, customer, quantity, bundleItem, context, finalPrice?.Amount); return(new(discountAmount, _primaryCurrency), appliedDiscount); }
/// <summary> /// Calculates the unit price for a given shopping cart item. /// </summary> /// <param name="priceCalculationService">Price calculation service.</param> /// <param name="cartItem">Shopping cart item.</param> /// <param name="ignoreDiscounts">A value indicating whether to ignore discounts.</param> /// <param name="targetCurrency">The target currency to use for money conversion. Obtained from <see cref="IWorkContext.WorkingCurrency"/> if <c>null</c>.</param> /// <returns>Calculated unit price.</returns> //public static async Task<CalculatedPrice> CalculateUnitPriceAsync( // this IPriceCalculationService2 priceCalculationService, // OrganizedShoppingCartItem cartItem, // bool ignoreDiscounts = false, // Currency targetCurrency = null) //{ // Guard.NotNull(priceCalculationService, nameof(priceCalculationService)); // Guard.NotNull(cartItem, nameof(cartItem)); // var options = priceCalculationService.CreateDefaultOptions(false, cartItem.Item.Customer, targetCurrency); // options.IgnoreDiscounts = ignoreDiscounts; // var context = new PriceCalculationContext(cartItem, options); // return await priceCalculationService.CalculatePriceAsync(context); //} /// <summary> /// Calculates both the unit price and the subtotal for a given shopping cart item. /// The subtotal is calculated by multiplying the unit price by <see cref="ShoppingCartItem.Quantity"/>. /// </summary> /// <param name="priceCalculationService">Price calculation service.</param> /// <param name="cartItem">Shopping cart item.</param> /// <param name="ignoreDiscounts">A value indicating whether to ignore discounts.</param> /// <param name="targetCurrency">The target currency to use for money conversion. Obtained from <see cref="IWorkContext.WorkingCurrency"/> if <c>null</c>.</param> /// <returns>Calculated subtotal.</returns> //public static async Task<(CalculatedPrice UnitPrice, CalculatedPrice Subtotal)> CalculateSubtotalAsync( // this IPriceCalculationService2 priceCalculationService, // OrganizedShoppingCartItem cartItem, // bool ignoreDiscounts = false, // Currency targetCurrency = null) //{ // Guard.NotNull(priceCalculationService, nameof(priceCalculationService)); // Guard.NotNull(cartItem, nameof(cartItem)); // var options = priceCalculationService.CreateDefaultOptions(false, cartItem.Item.Customer, targetCurrency); // options.IgnoreDiscounts = ignoreDiscounts; // var context = new PriceCalculationContext(cartItem, options); // return await priceCalculationService.CalculateSubtotalAsync(context); //} /// <summary> /// Calculates the price adjustments of product attributes, usually <see cref="ProductVariantAttributeValue.PriceAdjustment"/>. /// Typically used to display price adjustments of selected attributes on the cart page. /// The calculated adjustment is always a unit price. /// </summary> /// <param name="priceCalculationService">Price calculation service.</param> /// <param name="product">The product.</param> /// <param name="selection">Attribute selection.</param> /// <param name="quantity"> /// The product quantity. May have impact on the price, e.g. if tier prices are applied to price adjustments. /// Note that the calculated price is always the unit price. /// </param> /// <param name="options">Price calculation options. The default options are used if <c>null</c>.</param> /// <returns>Price adjustments of selected attributes. Key: <see cref="ProductVariantAttributeValue.Id"/>, value: attribute price adjustment.</returns> public static async Task <IDictionary <int, CalculatedPriceAdjustment> > CalculateAttributePriceAdjustmentsAsync( this IPriceCalculationService2 priceCalculationService, Product product, ProductVariantAttributeSelection selection, int quantity = 1, PriceCalculationOptions options = null) { Guard.NotNull(priceCalculationService, nameof(priceCalculationService)); Guard.NotNull(selection, nameof(selection)); options ??= priceCalculationService.CreateDefaultOptions(false); options.DeterminePriceAdjustments = true; var pricingContext = new PriceCalculationContext(product, quantity, options); pricingContext.AddSelectedAttributes(selection, product.Id); var price = await priceCalculationService.CalculatePriceAsync(pricingContext); return(price.AttributePriceAdjustments.ToDictionarySafe(x => x.AttributeValue.Id)); }
public virtual async Task <Money> GetProductVariantAttributeValuePriceAdjustmentAsync( ProductVariantAttributeValue attributeValue, Product product, Customer customer, PriceCalculationContext context, int quantity = 1) { Guard.NotNull(attributeValue, nameof(attributeValue)); var currency = _workContext.WorkingCurrency; if (attributeValue.ValueType == ProductVariantAttributeValueType.Simple) { if (quantity > 1 && attributeValue.PriceAdjustment > 0) { var tierPriceAttributeAdjustment = await GetTierPriceAttributeAdjustmentAsync(product, customer, quantity, context, attributeValue.PriceAdjustment); if (tierPriceAttributeAdjustment != 0) { return(tierPriceAttributeAdjustment); } } return(new Money(attributeValue.PriceAdjustment, currency)); } if (attributeValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) { var linkedProduct = await _db.Products.FindByIdAsync(attributeValue.LinkedProductId); if (linkedProduct != null) { var productPrice = await GetFinalPriceAsync(linkedProduct, null) * attributeValue.Quantity; return(productPrice); } } return(new Money(currency)); }
public IPriceCalculator[] GetCalculators(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); if (context.Calculators != null && context.Calculators.Length > 0) { // Don't resolve calculators if context has a specific calculators list. return(context.Calculators); } var cacheKey = "PriceCalculators:" + GenerateHashCode(context).ToString(); var calculators = _requestCache.Get(cacheKey, () => { return(_lazyCalculators .Where(x => MatchCalculator(context, x.Metadata)) .Select(x => x.Value) .ToArray()); }); return(calculators); }
protected virtual async Task <decimal> GetVariantPriceAdjustmentAsync( ProductVariantAttributeValue attributeValue, Product product, Customer customer, PriceCalculationContext context, int quantity = 1) { Guard.NotNull(attributeValue, nameof(attributeValue)); if (attributeValue.ValueType == ProductVariantAttributeValueType.Simple) { if (quantity > 1 && attributeValue.PriceAdjustment > decimal.Zero) { var tierPriceAttributeAdjustment = await GetTierPriceAttributeAdjustmentAsync(product, customer, quantity, context, attributeValue.PriceAdjustment); if (tierPriceAttributeAdjustment != decimal.Zero) { return(tierPriceAttributeAdjustment); } } return(attributeValue.PriceAdjustment); } else if (attributeValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) { var linkedProduct = await _db.Products.FindByIdAsync(attributeValue.LinkedProductId); if (linkedProduct != null) { var productPrice = await GetFinalPriceAmountAsync(linkedProduct, decimal.Zero, includeDiscounts : true); return(productPrice * attributeValue.Quantity); } } return(decimal.Zero); }
public virtual async Task <(CalculatedPrice UnitPrice, CalculatedPrice Subtotal)> CalculateSubtotalAsync(PriceCalculationContext context) { Guard.NotNull(context, nameof(context)); // Remember source product. var product = context.Product; var calculatorContext = context.CartItem != null && product.CustomerEntersPrice ? new CalculatorContext(context, context.CartItem.Item.CustomerEnteredPrice) : await RunCalculators(context); var unitPrice = await CreateCalculatedPrice(calculatorContext, product); if (context.Quantity > 1) { var options = context.Options; var subtotal = await CreateCalculatedPrice(calculatorContext, product, context.Quantity); // Avoid rounding differences between unit price and line subtotal when calculating with net prices. // ... but this produces a rounding difference between subtotal and line subtotals which can only be solved by a hack. var priceAmount = options.RoundingCurrency.RoundIfEnabledFor(unitPrice.FinalPrice.Amount) * context.Quantity; subtotal.FinalPrice = new(priceAmount, unitPrice.FinalPrice.Currency); return(unitPrice, subtotal); } return(unitPrice, unitPrice); //var subtotal = context.Quantity > 1 // ? await CreateCalculatedPrice(calculatorContext, product, context.Quantity) // : unitPrice; //return (unitPrice, subtotal); }
public CalculatorContext(PriceCalculationContext context, decimal regularPrice) : base(context) { RegularPrice = regularPrice; FinalPrice = regularPrice; }
protected virtual async Task <decimal> GetFinalPriceAmountAsync( Product product, decimal additionalCharge, Customer customer = null, bool includeDiscounts = true, int quantity = 1, ProductBundleItemData bundleItem = null, PriceCalculationContext context = null, bool isTierPrice = false) { Guard.NotNull(product, nameof(product)); customer ??= _workContext.CurrentCustomer; if (isTierPrice) { includeDiscounts = true; } var result = GetSpecialPriceAmount(product) ?? product.Price; // Tier price. if (product.HasTierPrices && includeDiscounts && !(bundleItem != null && bundleItem.Item != null)) { var tierPrice = await GetMinimumTierPriceAsync(product, customer, quantity, context); if (tierPrice.HasValue) { if (_catalogSettings.ApplyPercentageDiscountOnTierPrice && !isTierPrice) { var(discountOnTierPrice, appliedDiscount) = await GetDiscountAmountAsync(product, decimal.Zero, customer, quantity, bundleItem, context, tierPrice.Value); if (appliedDiscount != null && appliedDiscount.UsePercentage) { result = Math.Min(result, tierPrice.Value) + additionalCharge - discountOnTierPrice; return(Math.Max(result, decimal.Zero)); } } var(discountAmountTest, _) = await GetDiscountAmountAsync(product, additionalCharge, customer, quantity, bundleItem); var discountProductTest = result - discountAmountTest; if (tierPrice < discountProductTest) { includeDiscounts = false; result = Math.Min(result, tierPrice.Value); } } } result += additionalCharge; if (includeDiscounts) { var(discountAmount, _) = await GetDiscountAmountAsync(product, additionalCharge, customer, quantity, bundleItem, context); result -= discountAmount; } return(Math.Max(result, decimal.Zero)); }
protected virtual async Task <decimal> GetPreselectedPriceAmountAsync( Product product, Customer customer, PriceCalculationContext context, ProductBundleItemData bundleItem, IEnumerable <ProductBundleItemData> bundleItems) { var attributesTotalPriceBase = decimal.Zero; var preSelectedPriceAdjustmentBase = decimal.Zero; var isBundle = product.ProductType == ProductType.BundledProduct; var isBundleItemPricing = bundleItem?.Item?.BundleProduct?.BundlePerItemPricing ?? false; var isBundlePricing = bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing; var bundleItemId = bundleItem?.Item?.Id ?? 0; var query = new ProductVariantQuery(); var selectedAttributeValues = new List <ProductVariantAttributeValue>(); var attributes = await context.Attributes.GetOrLoadAsync(product.Id); // 1. Fill query with initially selected attributes. foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Any() && x.IsListTypeAttribute())) { await _db.LoadCollectionAsync(attribute, x => x.ProductVariantAttributeValues); var preSelectedValueId = 0; var selectedValueIds = new List <int>(); ProductVariantAttributeValue defaultValue = null; var pvaValues = attribute.ProductVariantAttributeValues; foreach (var pvaValue in pvaValues) { ProductBundleItemAttributeFilter attributeFilter = null; if (bundleItem?.Item?.IsFilteredOut(pvaValue, out attributeFilter) ?? false) { continue; } if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) { preSelectedValueId = attributeFilter.AttributeValueId; } if (!isBundlePricing && pvaValue.IsPreSelected) { var attributeValuePriceAdjustment = await GetVariantPriceAdjustmentAsync(pvaValue, product, customer, context, 1); // We cannot avoid money usage in calls between interfaces. var(priceAdjustmentBase, _) = await _taxService.GetProductPriceAsync(product, new(attributeValuePriceAdjustment, _primaryCurrency), customer : customer); preSelectedPriceAdjustmentBase += priceAdjustmentBase.Amount; } } // Value pre-selected by a bundle item filter discards the default pre-selection. if (preSelectedValueId != 0 && (defaultValue = pvaValues.FirstOrDefault(x => x.Id == preSelectedValueId)) != null) { //defaultValue.IsPreSelected = true; selectedAttributeValues.Add(defaultValue); query.AddVariant(new ProductVariantQueryItem(defaultValue.Id.ToString()) { ProductId = product.Id, BundleItemId = bundleItemId, AttributeId = attribute.ProductAttributeId, VariantAttributeId = attribute.Id, Alias = attribute.ProductAttribute.Alias, ValueAlias = defaultValue.Alias }); } else { foreach (var value in pvaValues.Where(x => x.IsPreSelected)) { selectedAttributeValues.Add(value); query.AddVariant(new ProductVariantQueryItem(value.Id.ToString()) { ProductId = product.Id, BundleItemId = bundleItemId, AttributeId = attribute.ProductAttributeId, VariantAttributeId = attribute.Id, Alias = attribute.ProductAttribute.Alias, ValueAlias = value.Alias }); } } } // 2. Find attribute combination for selected attributes and merge it. if (!isBundle && query.Variants.Any()) { var(selection, warnings) = await _productAttributeMaterializer.CreateAttributeSelectionAsync(query, attributes, product.Id, bundleItemId, true); var combinations = await context.AttributeCombinations.GetOrLoadAsync(product.Id); var selectedCombination = combinations.FirstOrDefault(x => x.AttributeSelection.Equals(selection)); if (selectedCombination != null && selectedCombination.IsActive && selectedCombination.Price.HasValue) { product.MergedDataValues = new Dictionary <string, object> { { "Price", selectedCombination.Price.Value } }; if (selectedCombination.BasePriceAmount.HasValue) { product.MergedDataValues.Add("BasePriceAmount", selectedCombination.BasePriceAmount.Value); } if (selectedCombination.BasePriceBaseAmount.HasValue) { product.MergedDataValues.Add("BasePriceBaseAmount", selectedCombination.BasePriceBaseAmount.Value); } } } if (_catalogSettings.EnableDynamicPriceUpdate && !isBundlePricing) { if (selectedAttributeValues.Count > 0) { foreach (var value in selectedAttributeValues) { attributesTotalPriceBase += await GetVariantPriceAdjustmentAsync(value, product, customer, context, 1); } } else { attributesTotalPriceBase = preSelectedPriceAdjustmentBase; } } if (bundleItem != null) { bundleItem.AdditionalCharge = new(attributesTotalPriceBase, _primaryCurrency); } var result = await GetFinalPriceAmountAsync(product, bundleItems, attributesTotalPriceBase, customer, true, 1, bundleItem, context); return(result); }
protected virtual async Task <decimal> GetTierPriceAttributeAdjustmentAsync(Product product, Customer customer, int quantity, PriceCalculationContext context = null, decimal adjustment = 0) { var result = decimal.Zero; var tierPrices = await LoadTierPrices(product, customer, context); if (tierPrices.Any()) { var previousQty = 1; foreach (var tierPrice in tierPrices) { if (quantity < tierPrice.Quantity || tierPrice.Quantity < previousQty) { continue; } if (tierPrice.CalculationMethod == TierPriceCalculationMethod.Percental && _catalogSettings.ApplyTierPricePercentageToAttributePriceAdjustments) { result = adjustment - (adjustment / 100m * tierPrice.Price); } previousQty = tierPrice.Quantity; } } return(result); }