示例#1
0
        public virtual decimal GetFinalPrice(
            Product product,
            IEnumerable <ProductBundleItemData> bundleItems,
            Customer customer,
            decimal additionalCharge,
            bool includeDiscounts,
            int quantity,
            ProductBundleItemData bundleItem = null,
            PriceCalculationContext context  = null)
        {
            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                var result = decimal.Zero;
                var items  = bundleItems;

                if (items == null)
                {
                    items = context == null
                        ? _productService.GetBundleItems(product.Id)
                        : context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)).ToList();
                }

                foreach (var itemData in items.Where(x => x != null && x.Item != null))
                {
                    var itemPrice = GetFinalPrice(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(GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem, context));
        }
        public static ProductBundleItemOrderData ToOrderData(this ProductBundleItemData bundleItemData, decimal priceWithDiscount = decimal.Zero,
                                                             string attributesXml = null, string attributesInfo = null)
        {
            if (!bundleItemData.IsValid())
            {
                return(null);
            }

            var    item           = bundleItemData.Item;
            string bundleItemName = item.GetLocalized(x => x.Name);

            var bundleData = new ProductBundleItemOrderData()
            {
                BundleItemId        = item.Id,
                ProductId           = item.ProductId,
                Sku                 = item.Product.Sku,
                ProductName         = (bundleItemName ?? item.Product.GetLocalized(x => x.Name)),
                ProductSeName       = item.Product.GetSeName(),
                VisibleIndividually = item.Product.VisibleIndividually,
                Quantity            = item.Quantity,
                DisplayOrder        = item.DisplayOrder,
                PriceWithDiscount   = priceWithDiscount,
                AttributesXml       = attributesXml,
                AttributesInfo      = attributesInfo,
                PerItemShoppingCart = item.BundleProduct.BundlePerItemShoppingCart
            };

            return(bundleData);
        }
示例#3
0
        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 virtual decimal GetFinalPrice(
            Product product,
            IEnumerable <ProductBundleItemData> bundleItems,
            Customer customer,
            decimal additionalCharge,
            bool includeDiscounts,
            int quantity,
            ProductBundleItemData bundleItem = null,
            PriceCalculationContext context  = null)
        {
            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                decimal result = decimal.Zero;
                var     items  = bundleItems ?? _productService.GetBundleItems(product.Id);

                foreach (var itemData in items.Where(x => x.IsValid()))
                {
                    decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context);

                    result = result + decimal.Multiply(itemPrice, itemData.Item.Quantity);
                }

                return(result < decimal.Zero ? decimal.Zero : result);
            }
            return(GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem, context));
        }
示例#5
0
        public OrganizedShoppingCartItem(ShoppingCartItem item)
        {
            Guard.NotNull(item, nameof(item));

            Item           = item;
            BundleItemData = new ProductBundleItemData(item.BundleItem);
        }
示例#6
0
        public virtual decimal GetFinalPrice(
            Product product,
            Customer customer,
            decimal additionalCharge,
            bool includeDiscounts,
            int quantity,
            ProductBundleItemData bundleItem = null,
            PriceCalculationContext context  = null,
            bool isTierPrice = false)
        {
            // Initial price.
            decimal result = product.Price;

            // Special price.
            var specialPrice = GetSpecialPrice(product);

            if (specialPrice.HasValue)
            {
                result = specialPrice.Value;
            }

            if (isTierPrice)
            {
                includeDiscounts = true;
            }

            // Tier prices.
            if (product.HasTierPrices && includeDiscounts && !(bundleItem != null && bundleItem.Item != null))
            {
                var tierPrice           = GetMinimumTierPrice(product, customer, quantity, context);
                var discountAmountTest  = GetDiscountAmount(product, customer, additionalCharge, quantity, out var appliedDiscountTest, bundleItem);
                var discountProductTest = result - discountAmountTest;

                //decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity);
                if (tierPrice.HasValue && tierPrice < discountProductTest)
                {
                    includeDiscounts = false;
                    result           = Math.Min(result, tierPrice.Value);
                }
            }

            // Discount + additional charge.
            if (includeDiscounts)
            {
                var discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out var appliedDiscount, bundleItem, context);
                result = result + additionalCharge - discountAmount;
            }
            else
            {
                result = result + additionalCharge;
            }

            if (result < decimal.Zero)
            {
                result = decimal.Zero;
            }

            return(result);
        }
        public OrganizedShoppingCartItem(ShoppingCartItem item)
        {
            Guard.NotNull(item, nameof(item));

            Item             = item; // must not be null
            ChildItems       = new List <OrganizedShoppingCartItem>();
            BundleItemData   = new ProductBundleItemData(item.BundleItem);
            CustomProperties = new Dictionary <string, object>();
        }
        public static void ToOrderData(this ProductBundleItemData bundleItemData, IList <ProductBundleItemOrderData> bundleData, decimal priceWithDiscount = decimal.Zero,
                                       string attributesXml = null, string attributesInfo = null)
        {
            var item = bundleItemData.ToOrderData(priceWithDiscount, attributesXml, attributesInfo);

            if (item != null && item.ProductId != 0 && item.BundleItemId != 0)
            {
                bundleData.Add(item);
            }
        }
        public static bool FilterOut(this ProductBundleItemData bundleItemData, ProductVariantAttributeValue value, out ProductBundleItemAttributeFilter filter)
        {
            if (bundleItemData.IsValid() && value != null && bundleItemData.Item.FilterAttributes)
            {
                filter = bundleItemData.Item.AttributeFilters.FirstOrDefault(x => x.AttributeId == value.ProductVariantAttributeId && x.AttributeValueId == value.Id);

                return(filter == null);
            }
            filter = null;
            return(false);
        }
示例#10
0
        public OrganizedShoppingCartItem(ShoppingCartItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            Item           = item;      // must not be null
            ChildItems     = new List <OrganizedShoppingCartItem>();
            BundleItemData = new ProductBundleItemData(item.BundleItem);
        }
        public virtual decimal GetDiscountAmount(
            Product product,
            Customer customer,
            decimal additionalCharge,
            int quantity,
            out Discount appliedDiscount,
            ProductBundleItemData bundleItem = null,
            PriceCalculationContext context  = null)
        {
            appliedDiscount = null;
            decimal appliedDiscountAmount     = decimal.Zero;
            decimal finalPriceWithoutDiscount = decimal.Zero;

            if (bundleItem.IsValid())
            {
                if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing)
                {
                    appliedDiscount = new Discount
                    {
                        UsePercentage      = bundleItem.Item.DiscountPercentage,
                        DiscountPercentage = bundleItem.Item.Discount.Value,
                        DiscountAmount     = bundleItem.Item.Discount.Value
                    };

                    finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem, context);
                    appliedDiscountAmount     = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount);
                }
            }
            else
            {
                // dont't apply when customer entered price or discounts should be ignored completely
                if (product.CustomerEntersPrice || _catalogSettings.IgnoreDiscounts)
                {
                    return(appliedDiscountAmount);
                }

                var allowedDiscounts = GetAllowedDiscounts(product, customer, context);
                if (allowedDiscounts.Count == 0)
                {
                    return(appliedDiscountAmount);
                }

                finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem, context);
                appliedDiscount           = allowedDiscounts.GetPreferredDiscount(finalPriceWithoutDiscount);

                if (appliedDiscount != null)
                {
                    appliedDiscountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount);
                }
            }

            return(appliedDiscountAmount);
        }
示例#12
0
        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));
        }
        public virtual decimal GetFinalPrice(
            Product product,
            Customer customer,
            decimal additionalCharge,
            bool includeDiscounts,
            int quantity,
            ProductBundleItemData bundleItem = null,
            PriceCalculationContext context  = null)
        {
            //initial price
            decimal result = product.Price;

            //special price
            var specialPrice = GetSpecialPrice(product);

            if (specialPrice.HasValue)
            {
                result = specialPrice.Value;
            }

            //tier prices
            if (product.HasTierPrices && !bundleItem.IsValid())
            {
                decimal?tierPrice = GetMinimumTierPrice(product, customer, quantity, context);
                if (tierPrice.HasValue)
                {
                    result = Math.Min(result, tierPrice.Value);
                }
            }

            //discount + additional charge
            if (includeDiscounts)
            {
                Discount appliedDiscount = null;
                decimal  discountAmount  = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscount, bundleItem, context);
                result = result + additionalCharge - discountAmount;
            }
            else
            {
                result = result + additionalCharge;
            }

            if (result < decimal.Zero)
            {
                result = decimal.Zero;
            }

            return(result);
        }
 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);
        }
示例#16
0
        /// <summary>
        /// Gets discount amount
        /// </summary>
        /// <param name="product">Product</param>
        /// <param name="customer">The customer</param>
        /// <param name="additionalCharge">Additional charge</param>
        /// <param name="quantity">Product quantity</param>
        /// <param name="appliedDiscount">Applied discount</param>
        /// <param name="bundleItem">A product bundle item</param>
        /// <returns>Discount amount</returns>
        public virtual decimal GetDiscountAmount(Product product,
                                                 Customer customer,
                                                 decimal additionalCharge,
                                                 int quantity,
                                                 out Discount appliedDiscount,
                                                 ProductBundleItemData bundleItem = null)
        {
            appliedDiscount = null;
            decimal appliedDiscountAmount = decimal.Zero;

            if (bundleItem.IsValid())
            {
                if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing)
                {
                    appliedDiscount = new Discount()
                    {
                        UsePercentage      = bundleItem.Item.DiscountPercentage,
                        DiscountPercentage = bundleItem.Item.Discount.Value,
                        DiscountAmount     = bundleItem.Item.Discount.Value
                    };
                }
            }
            else
            {
                //we don't apply discounts to products with price entered by a customer
                if (product.CustomerEntersPrice)
                {
                    return(appliedDiscountAmount);
                }

                appliedDiscount = GetPreferredDiscount(product, customer, additionalCharge, quantity);
            }

            if (appliedDiscount != null)
            {
                decimal finalPriceWithoutDiscount = GetFinalPrice(product, customer, additionalCharge, false, quantity, bundleItem);
                appliedDiscountAmount = appliedDiscount.GetDiscountAmount(finalPriceWithoutDiscount);
            }

            return(appliedDiscountAmount);
        }
示例#17
0
        public ActionResult UpdateProductDetails(int productId, string itemType, int bundleItemId, ProductVariantQuery query, FormCollection form)
        {
            int    quantity          = 1;
            int    galleryStartIndex = -1;
            string galleryHtml       = null;
            string dynamicThumbUrl   = null;
            bool   isAssociated      = itemType.IsCaseInsensitiveEqual("associateditem");
            var    pictureModel      = new ProductDetailsPictureModel();
            var    m       = new ProductDetailsModel();
            var    product = _productService.GetProductById(productId);
            var    bItem   = _productService.GetBundleItemById(bundleItemId);
            IList <ProductBundleItemData> bundleItems = null;
            ProductBundleItemData         bundleItem  = (bItem == null ? null : new ProductBundleItemData(bItem));

            var warnings   = new List <string>();
            var attributes = _productAttributeService.GetProductVariantAttributesByProductId(productId);

            var attributeXml = query.CreateSelectedAttributesXml(productId, 0, attributes, _productAttributeParser,
                                                                 _localizationService, _downloadService, _catalogSettings, this.Request, warnings);

            var areAllAttributesForCombinationSelected = _shoppingCartService.AreAllAttributesForCombinationSelected(attributeXml, product);

            // quantity required for tier prices
            string quantityKey = form.AllKeys.FirstOrDefault(k => k.EndsWith("EnteredQuantity"));

            if (quantityKey.HasValue())
            {
                int.TryParse(form[quantityKey], out quantity);
            }

            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                bundleItems = _productService.GetBundleItems(product.Id);
                if (query.Variants.Count > 0)
                {
                    // may add elements to query object if they are preselected by bundle item filter
                    foreach (var itemData in bundleItems)
                    {
                        _helper.PrepareProductDetailsPageModel(itemData.Item.Product, query, false, itemData, null);
                    }
                }
            }

            // get merged model data
            _helper.PrepareProductDetailModel(m, product, query, isAssociated, bundleItem, bundleItems, quantity);

            if (bundleItem != null)             // update bundle item thumbnail
            {
                if (!bundleItem.Item.HideThumbnail)
                {
                    var picture = m.GetAssignedPicture(_pictureService, null, bundleItem.Item.ProductId);
                    dynamicThumbUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.BundledProductPictureSize, false);
                }
            }
            else if (isAssociated)             // update associated product thumbnail
            {
                var picture = m.GetAssignedPicture(_pictureService, null, productId);
                dynamicThumbUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.AssociatedProductPictureSize, false);
            }
            else if (product.ProductType != ProductType.BundledProduct)                 // update image gallery
            {
                var pictures = _pictureService.GetPicturesByProductId(productId);

                if (pictures.Count <= _catalogSettings.DisplayAllImagesNumber)                  // all pictures rendered... only index is required
                {
                    var picture = m.GetAssignedPicture(_pictureService, pictures);
                    galleryStartIndex = (picture == null ? 0 : pictures.IndexOf(picture));
                }
                else
                {
                    var allCombinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id);

                    _helper.PrepareProductDetailsPictureModel(
                        pictureModel,
                        pictures,
                        product.GetLocalized(x => x.Name),
                        allCombinationPictureIds,
                        false,
                        bundleItem,
                        m.SelectedCombination);

                    galleryStartIndex = pictureModel.GalleryStartIndex;
                    galleryHtml       = this.RenderPartialViewToString("Product.Picture", pictureModel);
                }
            }

            object partials = null;

            if (m.IsBundlePart)
            {
                partials = new
                {
                    BundleItemPrice = this.RenderPartialViewToString("Product.Offer.Price", m),
                    BundleItemStock = this.RenderPartialViewToString("Product.StockInfo", m)
                };
            }
            else
            {
                partials = new
                {
                    Attrs       = this.RenderPartialViewToString("Product.Attrs", m),
                    Price       = this.RenderPartialViewToString("Product.Offer.Price", m),
                    Stock       = this.RenderPartialViewToString("Product.StockInfo", m),
                    BundlePrice = product.ProductType == ProductType.BundledProduct ? this.RenderPartialViewToString("Product.Bundle.Price", m) : (string)null
                };
            }

            object data = new
            {
                Partials          = partials,
                DynamicThumblUrl  = dynamicThumbUrl,
                GalleryStartIndex = galleryStartIndex,
                GalleryHtml       = galleryHtml
            };

            return(new JsonResult {
                Data = data
            });
        }
示例#18
0
        public ActionResult UpdateProductDetails(int productId, string itemType, int bundleItemId, FormCollection form)
        {
            int    quantity          = 1;
            int    galleryStartIndex = -1;
            string galleryHtml       = null;
            string dynamicThumbUrl   = null;
            bool   isAssociated      = itemType.IsCaseInsensitiveEqual("associateditem");
            var    pictureModel      = new ProductDetailsPictureModel();
            var    m       = new ProductDetailsModel();
            var    product = _productService.GetProductById(productId);
            var    bItem   = _productService.GetBundleItemById(bundleItemId);
            IList <ProductBundleItemData> bundleItems = null;
            ProductBundleItemData         bundleItem  = (bItem == null ? null : new ProductBundleItemData(bItem));

            // quantity required for tier prices
            string quantityKey = form.AllKeys.FirstOrDefault(k => k.EndsWith("EnteredQuantity"));

            if (quantityKey.HasValue())
            {
                int.TryParse(form[quantityKey], out quantity);
            }

            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                bundleItems = _productService.GetBundleItems(product.Id);
                if (form.Count > 0)
                {
                    foreach (var itemData in bundleItems)
                    {
                        var tempModel = _helper.PrepareProductDetailsPageModel(itemData.Item.Product, false, itemData, null, form);
                    }
                }
            }

            // get merged model data
            _helper.PrepareProductDetailModel(m, product, isAssociated, bundleItem, bundleItems, form, quantity);

            if (bundleItem != null)                     // update bundle item thumbnail
            {
                if (!bundleItem.Item.HideThumbnail)
                {
                    var picture = m.GetAssignedPicture(_pictureService, null, bundleItem.Item.ProductId);
                    dynamicThumbUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.BundledProductPictureSize, false);
                }
            }
            else if (isAssociated)                      // update associated product thumbnail
            {
                var picture = m.GetAssignedPicture(_pictureService, null, productId);
                dynamicThumbUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.AssociatedProductPictureSize, false);
            }
            else if (product.ProductType != ProductType.BundledProduct)                         // update image gallery
            {
                var pictures = _pictureService.GetPicturesByProductId(productId);

                if (pictures.Count <= _catalogSettings.DisplayAllImagesNumber)                  // all pictures rendered... only index is required
                {
                    var picture = m.GetAssignedPicture(_pictureService, pictures);
                    galleryStartIndex = (picture == null ? 0 : pictures.IndexOf(picture));
                }
                else
                {
                    var allCombinationImageIds = new List <int>();

                    _productAttributeService
                    .GetAllProductVariantAttributeCombinations(product.Id)
                    .GetAllCombinationImageIds(allCombinationImageIds);

                    _helper.PrepareProductDetailsPictureModel(pictureModel, pictures, product.GetLocalized(x => x.Name), allCombinationImageIds,
                                                              false, bundleItem, m.CombinationSelected);

                    galleryStartIndex = pictureModel.GalleryStartIndex;
                    galleryHtml       = this.RenderPartialViewToString("_PictureGallery", pictureModel);
                }
            }

            #region data object
            object data = new
            {
                Delivery = new
                {
                    Id    = 0,
                    Name  = m.DeliveryTimeName,
                    Color = m.DeliveryTimeHexValue,
                    DisplayAccordingToStock = m.DisplayDeliveryTimeAccordingToStock
                },
                Measure = new
                {
                    Weight = new { Value = m.WeightValue, Text = m.Weight },
                    Height = new { Value = product.Height, Text = m.Height },
                    Width  = new { Value = product.Width, Text = m.Width },
                    Length = new { Value = product.Length, Text = m.Length }
                },
                Number = new
                {
                    Sku  = new { Value = m.Sku, Show = m.ShowSku },
                    Gtin = new { Value = m.Gtin, Show = m.ShowGtin },
                    Mpn  = new { Value = m.ManufacturerPartNumber, Show = m.ShowManufacturerPartNumber }
                },
                Price = new
                {
                    Base = new
                    {
                        Enabled = m.IsBasePriceEnabled,
                        Info    = m.BasePriceInfo
                    },
                    Old = new
                    {
                        Value = decimal.Zero,
                        Text  = m.ProductPrice.OldPrice
                    },
                    WithoutDiscount = new
                    {
                        Value = m.ProductPrice.PriceValue,
                        Text  = m.ProductPrice.Price
                    },
                    WithDiscount = new
                    {
                        Value = m.ProductPrice.PriceWithDiscountValue,
                        Text  = m.ProductPrice.PriceWithDiscount
                    }
                },
                Stock = new
                {
                    Quantity     = new { Value = product.StockQuantity, Show = product.DisplayStockQuantity },
                    Availability = new { Text = m.StockAvailability, Show = product.DisplayStockAvailability, Available = m.IsAvailable }
                },

                DynamicThumblUrl  = dynamicThumbUrl,
                GalleryStartIndex = galleryStartIndex,
                GalleryHtml       = galleryHtml
            };
            #endregion

            return(new JsonResult {
                Data = data
            });
        }
        protected virtual decimal GetPreselectedPrice(Product product, 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 selectedAttributes      = new NameValueCollection();
            var selectedAttributeValues = new List <ProductVariantAttributeValue>();
            var attributes = context.Attributes.Load(product.Id);

            // 1. fill selectedAttributes 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);
                    selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
                }
                else
                {
                    foreach (var value in pvaValues.Where(x => x.IsPreSelected))
                    {
                        selectedAttributeValues.Add(value);
                        selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, value.Id, product.Id, bundleItemId);
                    }
                }
            }

            // 2. find attribute combination for selected attributes and merge it
            if (!isBundle && selectedAttributes.Count > 0)
            {
                var attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization,
                                                                                  _downloadService, _catalogSettings, _httpRequestBase, new List <string>(), true, bundleItemId);

                var combinations = context.AttributeCombinations.Load(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 (_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, _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem, context);

            return(result);
        }
        public virtual async Task <Money> GetFinalPriceAsync(
            Product product,
            Money?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 currency     = _workContext.WorkingCurrency;
            var specialPrice = GetSpecialPrice(product);

            var result = specialPrice.HasValue
                ? specialPrice.Value.Amount
                : 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, null, customer, quantity, bundleItem, context, tierPrice.Value);

                        if (appliedDiscount != null && appliedDiscount.UsePercentage)
                        {
                            result = Math.Min(result, tierPrice.Value.Amount) + (additionalCharge?.Amount ?? decimal.Zero) - discountOnTierPrice.Amount;

                            return(currency.AsMoney(result, false, true));
                        }
                    }

                    var(discountAmountTest, _) = await GetDiscountAmountAsync(product, additionalCharge, customer, quantity, bundleItem);

                    var discountProductTest = result - discountAmountTest.Amount;

                    if (tierPrice < discountProductTest)
                    {
                        includeDiscounts = false;
                        result           = Math.Min(result, tierPrice.Value.Amount);
                    }
                }
            }

            // Discount + additional charge.
            result += additionalCharge?.Amount ?? decimal.Zero;

            if (includeDiscounts)
            {
                var(discountAmount, _) = await GetDiscountAmountAsync(product, additionalCharge, customer, quantity, bundleItem, context);

                result -= discountAmount.Amount;
            }

            return(currency.AsMoney(result, false, true));
        }
 public static bool IsValid(this ProductBundleItemData bundleItemData)
 {
     return(bundleItemData != null && bundleItemData.Item != null);
 }
示例#22
0
        protected virtual decimal GetPreselectedPrice(Product product, ProductBundleItemData bundleItem, IList <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 attributes          = (isBundle ? new List <ProductVariantAttribute>() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id));
            var selectedAttributes  = new NameValueCollection();
            List <ProductVariantAttributeValue> selectedAttributeValues = null;

            foreach (var attribute in attributes)
            {
                int preSelectedValueId = 0;
                ProductVariantAttributeValue defaultValue = null;

                if (attribute.ShouldHaveValues())
                {
                    var pvaValues = _productAttributeService.GetProductVariantAttributeValues(attribute.Id);
                    if (pvaValues.Count == 0)
                    {
                        continue;
                    }

                    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;
                    }

                    if (defaultValue == null)
                    {
                        defaultValue = pvaValues.FirstOrDefault(x => x.IsPreSelected);
                    }

                    if (defaultValue == null && attribute.IsRequired)
                    {
                        defaultValue = pvaValues.First();
                    }

                    if (defaultValue != null)
                    {
                        selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
                    }
                }
            }

            if (!isBundle && selectedAttributes.Count > 0)
            {
                string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _commonServices.Localization,
                                                                                     _downloadService, _catalogSettings, _httpRequestBase, new List <string>(), true, bundleItemId);

                selectedAttributeValues = _productAttributeParser.ParseProductVariantAttributeValues(attributeXml).ToList();

                var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id);

                var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml));

                if (selectedCombination != null && selectedCombination.IsActive)
                {
                    product.MergeWithCombination(selectedCombination);
                }
            }

            if (_catalogSettings.EnableDynamicPriceUpdate && !isBundlePricing)
            {
                if (selectedAttributeValues != null)
                {
                    selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x));
                }
                else
                {
                    attributesTotalPriceBase = preSelectedPriceAdjustmentBase;
                }
            }

            if (bundleItem != null)
            {
                bundleItem.AdditionalCharge = attributesTotalPriceBase;
            }

            var result = GetFinalPrice(product, bundleItems, _commonServices.WorkContext.CurrentUser, attributesTotalPriceBase, true, 1, bundleItem);

            return(result);
        }
示例#23
0
        public async Task <IActionResult> UpdateProductDetails(int productId, string itemType, int bundleItemId, ProductVariantQuery query)
        {
            // TODO: (core) UpdateProductDetails action needs some decent refactoring.
            var    form              = HttpContext.Request.Form;
            int    quantity          = 1;
            int    galleryStartIndex = -1;
            string galleryHtml       = null;
            string dynamicThumbUrl   = null;
            var    isAssociated      = itemType.EqualsNoCase("associateditem");

            var product = await _db.Products.FindByIdAsync(productId);

            var batchContext = _productService.CreateProductBatchContext(new[] { product }, includeHidden: false);
            var bItem        = await _db.ProductBundleItem
                               .Include(x => x.BundleProduct)
                               .FindByIdAsync(bundleItemId, false);

            IList <ProductBundleItemData> bundleItemDatas = null;
            ProductBundleItemData         bundleItem      = bItem == null ? null : new ProductBundleItemData(bItem);

            // Quantity required for tier prices.
            string quantityKey = form.Keys.FirstOrDefault(k => k.EndsWith("EnteredQuantity"));

            if (quantityKey.HasValue())
            {
                _ = int.TryParse(form[quantityKey], out quantity);
            }

            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                var bundleItemQuery = await _db.ProductBundleItem
                                      .AsNoTracking()
                                      .ApplyBundledProductsFilter(new[] { product.Id })
                                      .Include(x => x.Product)
                                      .Include(x => x.BundleProduct)
                                      .ToListAsync();

                if (bundleItemQuery.Count > 0)
                {
                    bundleItemDatas = new List <ProductBundleItemData>();
                    bundleItemQuery.Each(x => bundleItemDatas.Add(new ProductBundleItemData(x)));
                }

                if (query.Variants.Count > 0)
                {
                    batchContext.Collect(bundleItemDatas.Select(x => x.Item.Product.Id).ToArray());

                    // May add elements to query object if they are preselected by bundle item filter.
                    foreach (var itemData in bundleItemDatas)
                    {
                        await _helper.MapProductDetailsPageModelAsync(new ProductDetailsModelContext
                        {
                            Product           = itemData.Item.Product,
                            BatchContext      = batchContext,
                            VariantQuery      = query,
                            ProductBundleItem = itemData
                        });
                    }
                }
            }

            var modelContext = new ProductDetailsModelContext
            {
                Product             = product,
                BatchContext        = batchContext,
                VariantQuery        = query,
                IsAssociatedProduct = isAssociated,
                ProductBundleItem   = bundleItem,
                BundleItemDatas     = bundleItemDatas,
                Customer            = batchContext.Customer,
                Store    = batchContext.Store,
                Currency = Services.WorkContext.WorkingCurrency
            };

            // Get merged model data.
            var model = new ProductDetailsModel();
            await _helper.PrepareProductDetailModelAsync(model, modelContext, quantity);

            if (bundleItem != null)
            {
                // Update bundle item thumbnail.
                if (!bundleItem.Item.HideThumbnail)
                {
                    var assignedMediaIds = model.SelectedCombination?.GetAssignedMediaIds() ?? Array.Empty <int>();
                    var hasFile          = assignedMediaIds.Any() && await _db.MediaFiles.AnyAsync(x => x.Id == assignedMediaIds[0]);

                    if (assignedMediaIds.Any() && hasFile)
                    {
                        var file = await _db.ProductMediaFiles
                                   .AsNoTracking()
                                   .ApplyProductFilter(new[] { bundleItem.Item.ProductId }, 1)
                                   .Select(x => x.MediaFile)
                                   .FirstOrDefaultAsync();

                        dynamicThumbUrl = _mediaService.GetUrl(file, _mediaSettings.BundledProductPictureSize, null, false);
                    }
                }
            }
            else if (isAssociated)
            {
                // Update associated product thumbnail.
                var assignedMediaIds = model.SelectedCombination?.GetAssignedMediaIds() ?? new int[0];
                var hasFile          = await _db.MediaFiles.AnyAsync(x => x.Id == assignedMediaIds[0]);

                if (assignedMediaIds.Any() && hasFile)
                {
                    var file = await _db.ProductMediaFiles
                               .AsNoTracking()
                               .ApplyProductFilter(new[] { productId }, 1)
                               .Select(x => x.MediaFile)
                               .FirstOrDefaultAsync();

                    dynamicThumbUrl = _mediaService.GetUrl(file, _mediaSettings.AssociatedProductPictureSize, null, false);
                }
            }
            else if (product.ProductType != ProductType.BundledProduct)
            {
                // Update image gallery.
                var files = await _db.ProductMediaFiles
                            .AsNoTracking()
                            .ApplyProductFilter(new[] { productId })
                            .Select(x => _mediaService.ConvertMediaFile(x.MediaFile))
                            .ToListAsync();

                if (product.HasPreviewPicture && files.Count > 1)
                {
                    files.RemoveAt(0);
                }

                if (files.Count <= _catalogSettings.DisplayAllImagesNumber)
                {
                    // All pictures rendered... only index is required.
                    galleryStartIndex = 0;

                    var assignedMediaIds = model.SelectedCombination?.GetAssignedMediaIds() ?? new int[0];
                    if (assignedMediaIds.Any())
                    {
                        var file = files.FirstOrDefault(p => p.Id == assignedMediaIds[0]);
                        galleryStartIndex = file == null ? 0 : files.IndexOf(file);
                    }
                }
                else
                {
                    var allCombinationPictureIds = await _productAttributeService.GetAttributeCombinationFileIdsAsync(product.Id);

                    var mediaModel = _helper.PrepareProductDetailsMediaGalleryModel(
                        files,
                        product.GetLocalized(x => x.Name),
                        allCombinationPictureIds,
                        false,
                        bundleItem,
                        model.SelectedCombination);

                    galleryStartIndex = mediaModel.GalleryStartIndex;
                    galleryHtml       = (await this.InvokeViewAsync("Product.Media", mediaModel)).ToString();
                }

                model.PriceDisplayStyle        = _catalogSettings.PriceDisplayStyle;
                model.DisplayTextForZeroPrices = _catalogSettings.DisplayTextForZeroPrices;
            }

            object partials = null;

            if (model.IsBundlePart)
            {
                partials = new
                {
                    BundleItemPrice    = await this.InvokeViewAsync("Product.Offer.Price", model),
                    BundleItemStock    = await this.InvokeViewAsync("Product.StockInfo", model),
                    BundleItemVariants = await this.InvokeViewAsync("Product.Variants", model.ProductVariantAttributes)
                };
            }
            else
            {
                var dataDictAddToCart = new ViewDataDictionary(ViewData)
                {
                    Model = model
                };
                dataDictAddToCart.TemplateInfo.HtmlFieldPrefix = $"addtocart_{model.Id}";

                decimal adjustment = decimal.Zero;
                decimal taxRate    = decimal.Zero;

                // TODO: (mh) (core) Implement when pricing is available.
                //var finalPriceWithDiscountBase = _taxService.GetProductPrice(product, product.Price, Services.WorkContext.CurrentCustomer, out taxRate);

                //if (!_taxSettings.Value.PricesIncludeTax && Services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax)
                //{
                //    adjustment = (m.ProductPrice.PriceValue - finalPriceWithDiscountBase) / (taxRate / 100 + 1);
                //}
                //else if (_taxSettings.Value.PricesIncludeTax && Services.WorkContext.TaxDisplayType == TaxDisplayType.ExcludingTax)
                //{
                //    adjustment = (m.ProductPrice.PriceValue - finalPriceWithDiscountBase) * (taxRate / 100 + 1);
                //}
                //else
                //{
                //    adjustment = m.ProductPrice.PriceValue - finalPriceWithDiscountBase;
                //}

                partials = new
                {
                    Attrs        = await this.InvokeViewAsync("Product.Attrs", model),
                    Price        = await this.InvokeViewAsync("Product.Offer.Price", model),
                    Stock        = await this.InvokeViewAsync("Product.StockInfo", model),
                    Variants     = await this.InvokeViewAsync("Product.Variants", model.ProductVariantAttributes),
                    OfferActions = await this.InvokeViewAsync("Product.Offer.Actions", dataDictAddToCart),
                    TierPrices   = await this.InvokeViewAsync("Product.TierPrices", model.TierPrices),
                    BundlePrice  = product.ProductType == ProductType.BundledProduct ? await this.InvokeViewAsync("Product.Bundle.Price", model) : null
                };
            }

            object data = new
            {
                Partials          = partials,
                DynamicThumblUrl  = dynamicThumbUrl,
                GalleryStartIndex = galleryStartIndex,
                GalleryHtml       = galleryHtml
            };

            return(new JsonResult(new { Data = data }));
        }
示例#24
0
        protected virtual decimal GetPreselectedPrice(
            Product product,
            Customer customer,
            Currency currency,
            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.Any() && 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?.Item?.FilterOut(pvaValue, out attributeFilter) ?? false)
                    {
                        continue;
                    }

                    if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected)
                    {
                        preSelectedValueId = attributeFilter.AttributeValueId;
                    }

                    if (!isBundlePricing && pvaValue.IsPreSelected)
                    {
                        var includingTax = _services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax;
                        var attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue, product, customer, context, 1);
                        var priceAdjustmentBase           = _taxService.GetProductPrice(product, product.TaxCategoryId, attributeValuePriceAdjustment,
                                                                                        includingTax, customer, currency, _taxSettings.PricesIncludeTax, 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.Any())
            {
                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, product, customer, context, 1));
                }
                else
                {
                    attributesTotalPriceBase = preSelectedPriceAdjustmentBase;
                }
            }

            if (bundleItem != null)
            {
                bundleItem.AdditionalCharge = attributesTotalPriceBase;
            }

            var result = GetFinalPrice(product, bundleItems, customer, attributesTotalPriceBase, true, 1, bundleItem, context);

            return(result);
        }
        public ActionResult UpdateProductDetails(int productId, string itemType, int bundleItemId, ProductVariantQuery query, FormCollection form)
        {
            int    quantity          = 1;
            int    galleryStartIndex = -1;
            string galleryHtml       = null;
            string dynamicThumbUrl   = null;
            var    isAssociated      = itemType.IsCaseInsensitiveEqual("associateditem");
            var    pictureModel      = new ProductDetailsPictureModel();
            var    m       = new ProductDetailsModel();
            var    product = _productService.GetProductById(productId);
            var    bItem   = _productService.GetBundleItemById(bundleItemId);
            IList <ProductBundleItemData> bundleItems = null;
            ProductBundleItemData         bundleItem  = (bItem == null ? null : new ProductBundleItemData(bItem));

            // Quantity required for tier prices.
            string quantityKey = form.AllKeys.FirstOrDefault(k => k.EndsWith("EnteredQuantity"));

            if (quantityKey.HasValue())
            {
                int.TryParse(form[quantityKey], out quantity);
            }

            if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)
            {
                bundleItems = _productService.GetBundleItems(product.Id);
                if (query.Variants.Count > 0)
                {
                    // May add elements to query object if they are preselected by bundle item filter.
                    foreach (var itemData in bundleItems)
                    {
                        _helper.PrepareProductDetailsPageModel(itemData.Item.Product, query, false, itemData, null);
                    }
                }
            }

            // Get merged model data.
            _helper.PrepareProductDetailModel(m, product, query, isAssociated, bundleItem, bundleItems, quantity);

            if (bundleItem != null)
            {
                // Update bundle item thumbnail.
                if (!bundleItem.Item.HideThumbnail)
                {
                    var picture = m.GetAssignedPicture(_pictureService, null, bundleItem.Item.ProductId);
                    dynamicThumbUrl = _pictureService.GetUrl(picture, _mediaSettings.BundledProductPictureSize, false);
                }
            }
            else if (isAssociated)
            {
                // Update associated product thumbnail.
                var picture = m.GetAssignedPicture(_pictureService, null, productId);
                dynamicThumbUrl = _pictureService.GetUrl(picture, _mediaSettings.AssociatedProductPictureSize, false);
            }
            else if (product.ProductType != ProductType.BundledProduct)
            {
                // Update image gallery.
                var pictures = _pictureService.GetPicturesByProductId(productId);

                if (pictures.Count <= _catalogSettings.DisplayAllImagesNumber)
                {
                    // All pictures rendered... only index is required.
                    var picture = m.GetAssignedPicture(_pictureService, pictures);
                    galleryStartIndex = (picture == null ? 0 : pictures.IndexOf(picture));
                }
                else
                {
                    var allCombinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id);

                    _helper.PrepareProductDetailsPictureModel(
                        pictureModel,
                        pictures,
                        product.GetLocalized(x => x.Name),
                        allCombinationPictureIds,
                        false,
                        bundleItem,
                        m.SelectedCombination);

                    galleryStartIndex = pictureModel.GalleryStartIndex;
                    galleryHtml       = this.RenderPartialViewToString("Product.Picture", pictureModel);
                }
            }

            object partials = null;

            if (m.IsBundlePart)
            {
                partials = new
                {
                    BundleItemPrice = this.RenderPartialViewToString("Product.Offer.Price", m),
                    BundleItemStock = this.RenderPartialViewToString("Product.StockInfo", m)
                };
            }
            else
            {
                var dataDictAddToCart = new ViewDataDictionary();
                dataDictAddToCart.TemplateInfo.HtmlFieldPrefix = string.Format("addtocart_{0}", m.Id);

                decimal adjustment = decimal.Zero;
                decimal taxRate    = decimal.Zero;
                var     finalPriceWithDiscountBase = _taxService.GetProductPrice(product, product.Price, _services.WorkContext.CurrentCustomer, out taxRate);

                if (!_taxSettings.Value.PricesIncludeTax && _services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax)
                {
                    adjustment = (m.ProductPrice.PriceValue - finalPriceWithDiscountBase) / (taxRate / 100 + 1);
                }
                else if (_taxSettings.Value.PricesIncludeTax && _services.WorkContext.TaxDisplayType == TaxDisplayType.ExcludingTax)
                {
                    adjustment = (m.ProductPrice.PriceValue - finalPriceWithDiscountBase) * (taxRate / 100 + 1);
                }
                else
                {
                    adjustment = m.ProductPrice.PriceValue - finalPriceWithDiscountBase;
                }

                partials = new
                {
                    Attrs        = this.RenderPartialViewToString("Product.Attrs", m),
                    Price        = this.RenderPartialViewToString("Product.Offer.Price", m),
                    Stock        = this.RenderPartialViewToString("Product.StockInfo", m),
                    OfferActions = this.RenderPartialViewToString("Product.Offer.Actions", m, dataDictAddToCart),
                    TierPrices   = this.RenderPartialViewToString("Product.TierPrices", _helper.CreateTierPriceModel(product, adjustment)),
                    BundlePrice  = product.ProductType == ProductType.BundledProduct ? this.RenderPartialViewToString("Product.Bundle.Price", m) : (string)null
                };
            }

            object data = new
            {
                Partials          = partials,
                DynamicThumblUrl  = dynamicThumbUrl,
                GalleryStartIndex = galleryStartIndex,
                GalleryHtml       = galleryHtml
            };

            return(new JsonResult {
                Data = data
            });
        }
        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);
        }