/// <summary> /// Creates a product URL including variant query string. /// </summary> /// <param name="helper">Product URL helper.</param> /// <param name="productId">Product identifier.</param> /// <param name="productSlug">Product URL slug.</param> /// <param name="bundleItemId">Bundle item identifier. 0 if it's not a bundle item.</param> /// <param name="variantValues">Variant values<./param> /// <returns>Product URL.</returns> public static string GetProductUrl( this ProductUrlHelper helper, int productId, string productSlug, int bundleItemId, params ProductVariantAttributeValue[] variantValues) { Guard.NotNull(helper, nameof(helper)); Guard.NotZero(productId, nameof(productId)); var query = new ProductVariantQuery(); foreach (var value in variantValues) { var attribute = value.ProductVariantAttribute; query.AddVariant(new ProductVariantQueryItem(value.Id.ToString()) { ProductId = productId, BundleItemId = bundleItemId, AttributeId = attribute.ProductAttributeId, VariantAttributeId = attribute.Id, Alias = attribute.ProductAttribute.Alias, ValueAlias = value.Alias }); } return(helper.GetProductUrl(productSlug, query)); }
/// <summary> /// Deserializes attributes XML into a product variant query /// </summary> /// <param name="query">Product variant query</param> /// <param name="productId">Product identifier</param> /// <param name="bundleItemId">Bundle item identifier</param> /// <param name="attributesXml">XML formatted attributes</param> public virtual void DeserializeQuery(ProductVariantQuery query, int productId, string attributesXml, int bundleItemId = 0) { Guard.NotNull(query, nameof(query)); Guard.NotZero(productId, nameof(productId)); if (attributesXml.IsEmpty() || productId == 0) { return; } var attributeMap = _productAttributeParser.DeserializeProductVariantAttributes(attributesXml); var attributes = _productAttributeService.GetProductVariantAttributesByIds(attributeMap.Keys); foreach (var attribute in attributes) { foreach (var originalValue in attributeMap[attribute.Id]) { var value = originalValue; DateTime?date = null; if (attribute.AttributeControlType == AttributeControlType.Datepicker) { date = originalValue.ToDateTime(new string[] { "D" }, CultureInfo.CurrentCulture, DateTimeStyles.None, null); if (date == null) { continue; } value = string.Join("-", date.Value.Year, date.Value.Month, date.Value.Day); } var queryItem = new ProductVariantQueryItem(value); queryItem.ProductId = productId; queryItem.BundleItemId = bundleItemId; queryItem.AttributeId = attribute.ProductAttributeId; queryItem.VariantAttributeId = attribute.Id; queryItem.Alias = _catalogSearchQueryAliasMapper.Value.GetVariantAliasById(attribute.ProductAttributeId, _languageId); queryItem.Date = date; queryItem.IsFile = attribute.AttributeControlType == AttributeControlType.FileUpload; queryItem.IsText = attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox; if (attribute.ShouldHaveValues()) { queryItem.ValueAlias = _catalogSearchQueryAliasMapper.Value.GetVariantOptionAliasById(value.ToInt(), _languageId); } query.AddVariant(queryItem); } } }
/// <summary> /// Deserializes attributes XML into a product variant query /// </summary> /// <param name="query">Product variant query</param> /// <param name="productId">Product identifier</param> /// <param name="bundleItemId">Bundle item identifier</param> /// <param name="attributesXml">XML formatted attributes</param> public virtual void DeserializeQuery(ProductVariantQuery query, int productId, string attributesXml, int bundleItemId = 0) { Guard.NotNull(query, nameof(query)); Guard.NotZero(productId, nameof(productId)); if (attributesXml.HasValue() && productId != 0) { var attributeValues = _productAttributeParser.ParseProductVariantAttributeValues(attributesXml).ToList(); foreach (var value in attributeValues) { query.AddVariant(new ProductVariantQueryItem(value.Id.ToString()) { ProductId = productId, BundleItemId = bundleItemId, AttributeId = value.ProductVariantAttribute.ProductAttributeId, VariantAttributeId = value.ProductVariantAttributeId, Alias = value.ProductVariantAttribute.ProductAttribute.Alias, ValueAlias = value.Alias }); } } }
protected virtual decimal GetPreselectedPrice( Product product, Customer customer, PriceCalculationContext context, ProductBundleItemData bundleItem, IEnumerable <ProductBundleItemData> bundleItems) { var taxRate = decimal.Zero; var attributesTotalPriceBase = decimal.Zero; var preSelectedPriceAdjustmentBase = decimal.Zero; var isBundle = (product.ProductType == ProductType.BundledProduct); var isBundleItemPricing = (bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing); var isBundlePricing = (bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing); var bundleItemId = (bundleItem == null ? 0 : bundleItem.Item.Id); var query = new ProductVariantQuery(); var selectedAttributeValues = new List <ProductVariantAttributeValue>(); var attributes = context.Attributes.GetOrLoad(product.Id); // 1. Fill query with initially selected attributes. foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Count > 0 && x.ShouldHaveValues())) { int preSelectedValueId = 0; ProductVariantAttributeValue defaultValue = null; var selectedValueIds = new List <int>(); var pvaValues = attribute.ProductVariantAttributeValues; foreach (var pvaValue in pvaValues) { ProductBundleItemAttributeFilter attributeFilter = null; if (bundleItem.FilterOut(pvaValue, out attributeFilter)) { continue; } if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) { preSelectedValueId = attributeFilter.AttributeValueId; } if (!isBundlePricing && pvaValue.IsPreSelected) { decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue); decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate); preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase); } } // 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.Count > 0) { var attributeXml = query.CreateSelectedAttributesXml(product.Id, bundleItemId, attributes, _productAttributeParser, _services.Localization, _downloadService, _catalogSettings, _httpRequestBase, new List <string>()); var combinations = context.AttributeCombinations.GetOrLoad(product.Id); var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml)); 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) { selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x)); } else { attributesTotalPriceBase = preSelectedPriceAdjustmentBase; } } if (bundleItem != null) { bundleItem.AdditionalCharge = attributesTotalPriceBase; } var result = GetFinalPrice(product, bundleItems, customer, attributesTotalPriceBase, true, 1, bundleItem, context); return(result); }
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); }
/// <summary> /// Adds selected product variant attributes to a product variant query. /// </summary> /// <param name="query">Target product variant query.</param> /// <param name="source">Selected attributes.</param> /// <param name="productId">Product identifier.</param> /// <param name="bundleItemId">Bundle item identifier.</param> /// <param name="attributes">Product variant attributes.</param> public virtual async Task AddAttributesToQueryAsync( ProductVariantQuery query, ProductVariantAttributeSelection source, int productId, int bundleItemId = 0, ICollection <ProductVariantAttribute> attributes = null) { Guard.NotNull(query, nameof(query)); if (productId == 0 || !(source?.AttributesMap?.Any() ?? false)) { return; } if (attributes == null) { var ids = source.AttributesMap.Select(x => x.Key); attributes = await _db.ProductVariantAttributes.GetManyAsync(ids); } var languageId = _workContext.WorkingLanguage.Id; foreach (var attribute in attributes) { var item = source.AttributesMap.FirstOrDefault(x => x.Key == attribute.Id); if (item.Key != 0) { foreach (var originalValue in item.Value) { var value = originalValue.ToString(); DateTime?date = null; if (attribute.AttributeControlType == AttributeControlType.Datepicker) { date = value.ToDateTime(new[] { "D" }, CultureInfo.CurrentCulture, DateTimeStyles.None, null); if (date == null) { continue; } value = string.Join("-", date.Value.Year, date.Value.Month, date.Value.Day); } var queryItem = new ProductVariantQueryItem(value) { ProductId = productId, BundleItemId = bundleItemId, AttributeId = attribute.ProductAttributeId, VariantAttributeId = attribute.Id, Alias = _catalogSearchQueryAliasMapper.Value.GetVariantAliasById(attribute.ProductAttributeId, languageId), Date = date, IsFile = attribute.AttributeControlType == AttributeControlType.FileUpload, IsText = attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox }; if (attribute.IsListTypeAttribute()) { queryItem.ValueAlias = _catalogSearchQueryAliasMapper.Value.GetVariantOptionAliasById(value.ToInt(), languageId); } query.AddVariant(queryItem); } } } }
public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next) { var options = context.Options; if (!options.DeterminePreselectedPrice) { // Proceed with pipeline and omit this calculator, it is made for preselected price calculation only. await next(context); return; } var selectedValues = (await context.GetPreSelectedAttributeValuesAsync()) .Where(x => x.ProductVariantAttribute.IsListTypeAttribute()) .ToList(); if (selectedValues.Any()) { // Create attribute selection of preselected values. var query = new ProductVariantQuery(); var product = context.Product; var bundleItemId = context.BundleItem?.Item?.Id ?? 0; var attributes = await options.BatchContext.Attributes.GetOrLoadAsync(product.Id); var combinations = await options.BatchContext.AttributeCombinations.GetOrLoadAsync(product.Id); foreach (var value in selectedValues) { var productAttribute = value.ProductVariantAttribute; query.AddVariant(new ProductVariantQueryItem(value.Id.ToString()) { ProductId = product.Id, BundleItemId = bundleItemId, AttributeId = productAttribute.ProductAttributeId, VariantAttributeId = productAttribute.Id, Alias = productAttribute.ProductAttribute.Alias, ValueAlias = value.Alias }); } var(selection, _) = await _productAttributeMaterializer.CreateAttributeSelectionAsync(query, attributes, product.Id, bundleItemId, false); var selectedCombination = combinations.FirstOrDefault(x => x.AttributeSelection.Equals(selection)); // Apply attribute combination price. if ((selectedCombination?.IsActive ?? false) && selectedCombination.Price.HasValue) { context.FinalPrice = selectedCombination.Price.Value; // That comes too late because regular price has already been passed to child CalculatorContext: //product.MergedDataValues = new Dictionary<string, object> { { "Price", selectedCombination.Price.Value } }; } } // The product page is always loaded with the default quantity of 1. context.Quantity = 1; await next(context); context.PreselectedPrice = context.FinalPrice; }