Пример #1
0
        // TODO: (ms) (core) Test this for bundle & grouped products.
        public virtual async Task <bool> AddToCartAsync(AddToCartContext ctx)
        {
            Guard.NotNull(ctx, nameof(ctx));

            // This is called when customer adds a product to cart
            ctx.Customer ??= _workContext.CurrentCustomer;
            ctx.StoreId ??= _storeContext.CurrentStore.Id;

            ctx.Customer.ResetCheckoutData(ctx.StoreId.Value);

            // Checks whether attributes have been selected
            if (ctx.VariantQuery != null || ctx.RawAttributes.HasValue())
            {
                if (!ctx.RawAttributes.HasValue())
                {
                    await _db.LoadCollectionAsync(ctx.Product, x => x.ProductVariantAttributes, false);

                    var(Selection, Warnings) = await _productAttributeMaterializer.CreateAttributeSelectionAsync(
                        ctx.VariantQuery,
                        ctx.Product.ProductVariantAttributes,
                        ctx.Product.Id,
                        ctx.BundleItemId);

                    ctx.RawAttributes = Selection.AttributesMap.Any() ? Selection.AsJson() : string.Empty;
                }

                // Check context for bundle item errors
                if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.RawAttributes.HasValue())
                {
                    ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes"));

                    if (ctx.BundleItem != null)
                    {
                        return(false);
                    }
                }
            }

            if (!await _cartValidator.ValidateAccessPermissionsAsync(ctx.Customer, ctx.CartType, ctx.Warnings))
            {
                return(false);
            }

            var cartItems = await GetCartItemsAsync(ctx.Customer, ctx.CartType, ctx.StoreId.Value);

            // Adds required products automatically if it is enabled
            if (ctx.AutomaticallyAddRequiredProducts)
            {
                var requiredProductIds = ctx.Product.ParseRequiredProductIds();
                if (requiredProductIds.Any())
                {
                    var cartProductIds            = cartItems.Select(x => x.Item.ProductId);
                    var missingRequiredProductIds = requiredProductIds.Except(cartProductIds);
                    var missingRequiredProducts   = await _db.Products.GetManyAsync(missingRequiredProductIds, false);

                    foreach (var product in missingRequiredProducts)
                    {
                        var item = new ShoppingCartItem
                        {
                            CustomerEnteredPrice = ctx.CustomerEnteredPrice.Amount,
                            RawAttributes        = ctx.AttributeSelection.AsJson(),
                            ShoppingCartType     = ctx.CartType,
                            StoreId      = ctx.StoreId.Value,
                            Quantity     = ctx.Quantity,
                            Customer     = ctx.Customer,
                            Product      = product,
                            BundleItemId = ctx.BundleItem?.Id
                        };

                        await AddItemToCartAsync(new AddToCartContext
                        {
                            Item       = item,
                            ChildItems = ctx.ChildItems,
                            Customer   = ctx.Customer
                        });
                    }
                }
            }

            // Checks whether required products are still missing
            await _cartValidator.ValidateRequiredProductsAsync(ctx.Product, cartItems, ctx.Warnings);

            ShoppingCartItem existingCartItem = null;

            if (ctx.BundleItem == null)
            {
                existingCartItem = cartItems.FindItemInCart(ctx.CartType, ctx.Product, ctx.AttributeSelection, ctx.CustomerEnteredPrice)?.Item;
            }

            // Add item to cart (if no warnings accured)
            if (existingCartItem != null)
            {
                // Product is already in cart, find existing item
                var newQuantity = ctx.Quantity + existingCartItem.Quantity;

                if (!await _cartValidator.ValidateAddToCartItemAsync(ctx, existingCartItem, cartItems))
                {
                    return(false);
                }

                // Update cart item
                existingCartItem.Quantity      = newQuantity;
                existingCartItem.UpdatedOnUtc  = DateTime.UtcNow;
                existingCartItem.RawAttributes = ctx.AttributeSelection.AsJson();

                await _db.SaveChangesAsync();

                return(true);
            }
            else
            {
                if (!_cartValidator.ValidateItemsMaximumCartQuantity(ctx.CartType, cartItems.Count, ctx.Warnings))
                {
                    return(false);
                }

                // Product is not in cart yet, create new item
                var cartItem = new ShoppingCartItem
                {
                    CustomerEnteredPrice = ctx.CustomerEnteredPrice.Amount,
                    RawAttributes        = ctx.RawAttributes,
                    ShoppingCartType     = ctx.CartType,
                    StoreId      = ctx.StoreId.Value,
                    Quantity     = ctx.Quantity,
                    Customer     = ctx.Customer,
                    Product      = ctx.Product,
                    ProductId    = ctx.Product.Id,
                    ParentItemId = null,
                    BundleItemId = ctx.BundleItem?.Id,
                    BundleItem   = ctx.BundleItem
                };

                // TODO: (core) (ms) Fix bundle attributes of child items & customer selected price

                if (!await _cartValidator.ValidateAddToCartItemAsync(ctx, cartItem, cartItems))
                {
                    return(false);
                }

                // Checks whether the product is the parent item of a bundle, or just a simple product.
                if (ctx.BundleItem == null)
                {
                    // Set cart item as item for simple & bundle products, only if its not set by the caller
                    ctx.Item ??= cartItem;
                }
                else
                {
                    // Add item as child of bundle
                    ctx.ChildItems.Add(cartItem);
                }
            }

            _requestCache.RemoveByPattern(CartItemsPatternKey);

            // If ctx.Product is a bundle product and the setting to automatically add bundle products is true, try to add all corresponding BundleItems.

            if (ctx.AutomaticallyAddBundleProducts &&
                ctx.Product.ProductType == ProductType.BundledProduct &&
                ctx.BundleItem == null &&
                ctx.Warnings.Count == 0)
            {
                var bundleItems = await _db.ProductBundleItem
                                  .ApplyBundledProductsFilter(new[] { ctx.Product.Id }, true)
                                  .Include(x => x.Product)
                                  .ToListAsync();

                foreach (var bundleItem in bundleItems)
                {
                    bundleItem.BundleProduct = ctx.Item.Product;

                    var bundleItemContext = new AddToCartContext
                    {
                        StoreId                          = ctx.StoreId,
                        Customer                         = ctx.Customer,
                        CartType                         = ctx.CartType,
                        BundleItem                       = bundleItem,
                        ChildItems                       = ctx.ChildItems,
                        Product                          = bundleItem.Product,
                        Quantity                         = bundleItem.Quantity,
                        VariantQuery                     = ctx.VariantQuery,
                        RawAttributes                    = ctx.RawAttributes,
                        CustomerEnteredPrice             = ctx.CustomerEnteredPrice,
                        AutomaticallyAddRequiredProducts = ctx.AutomaticallyAddRequiredProducts,
                    };

                    if (!await AddToCartAsync(bundleItemContext))
                    {
                        ctx.Warnings.AddRange(bundleItemContext.Warnings);
                    }
                }
            }

            // Add item and its children (if active) to the cart, when it is either a simple product or
            // if it is the parent item of its bundle (bundleItem = null) and no warnings occurred.
            if (ctx.BundleItem == null && ctx.Warnings.Count == 0)
            {
                await AddItemToCartAsync(ctx);
            }

            return(!ctx.Warnings.Any());
        }
        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);
        }
Пример #3
0
        // TODO: (ms) (core) TESTING! Make sure it works in any case - Works for ReOrder().
        // TODO: (ms) (core) Test this for bundle & grouped products. Test for items with variants!
        public virtual async Task <bool> AddToCartAsync(AddToCartContext ctx)
        {
            Guard.NotNull(ctx, nameof(ctx));

            // This is called when customer adds a product to cart
            ctx.Customer ??= _workContext.CurrentCustomer;
            ctx.StoreId ??= _storeContext.CurrentStore.Id;

            ctx.Customer.ResetCheckoutData(ctx.StoreId.Value);

            // Checks whether attributes have been selected
            if (ctx.VariantQuery != null)
            {
                // TODO: (ms) (core) fix wrong porting of attribute selection processing in AddToCartAsync.
                // Use _productAttributeMaterializer.CreateAttributeSelectionAsync to process them in context of VariantQuery.

                var attributes = await _db.ProductVariantAttributes
                                 //.Include(x => x.ProductAttribute)
                                 .ApplyProductFilter(new[] { ctx.Product.Id })
                                 .ToListAsync();

                var attributeSelection = await _productAttributeMaterializer.CreateAttributeSelectionAsync(
                    ctx.VariantQuery,
                    attributes,
                    ctx.Product.Id,
                    ctx.BundleItemId);

                ctx.RawAttributes = attributeSelection.Selection.AsJson();

                // Check context for bundle item errors
                if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.RawAttributes.HasValue())
                {
                    ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes"));

                    if (ctx.BundleItem != null)
                    {
                        return(false);
                    }
                }
            }

            if (!await _cartValidator.ValidateAccessPermissionsAsync(ctx.Customer, ctx.CartType, ctx.Warnings))
            {
                return(false);
            }

            var cartItems = await GetCartItemsAsync(ctx.Customer, ctx.CartType, ctx.StoreId.Value);

            // Adds required products automatically if it is enabled
            if (ctx.AutomaticallyAddRequiredProducts)
            {
                var requiredProductIds = ctx.Product.ParseRequiredProductIds();
                if (requiredProductIds.Any())
                {
                    var cartProductIds            = cartItems.Select(x => x.Item.ProductId);
                    var missingRequiredProductIds = requiredProductIds.Except(cartProductIds);
                    var missingRequiredProducts   = await _db.Products.GetManyAsync(missingRequiredProductIds);

                    foreach (var product in missingRequiredProducts)
                    {
                        var item = new ShoppingCartItem
                        {
                            CustomerEnteredPrice = ctx.CustomerEnteredPrice.Amount,
                            RawAttributes        = ctx.AttributeSelection.AsJson(),
                            ShoppingCartType     = ctx.CartType,
                            StoreId      = ctx.StoreId.Value,
                            Quantity     = ctx.Quantity,
                            Customer     = ctx.Customer,
                            Product      = product,
                            ParentItemId = product.ParentGroupedProductId,
                            BundleItemId = ctx.BundleItem?.Id
                        };

                        await AddItemToCartAsync(new AddToCartContext
                        {
                            Item       = item,
                            ChildItems = ctx.ChildItems,
                            Customer   = ctx.Customer
                        });
                    }
                }
            }

            // Checks whether required products are still missing
            await _cartValidator.ValidateRequiredProductsAsync(ctx.Product, cartItems, ctx.Warnings);

            ShoppingCartItem existingCartItem = null;

            if (ctx.BundleItem == null)
            {
                existingCartItem = cartItems.FindItemInCart(ctx.CartType, ctx.Product, ctx.AttributeSelection, ctx.CustomerEnteredPrice)?.Item;
            }

            // Add item to cart (if no warnings accured)
            if (existingCartItem != null)
            {
                // Product is already in cart, find existing item
                var newQuantity = ctx.Quantity + existingCartItem.Quantity;

                if (!await _cartValidator.ValidateAddToCartItemAsync(ctx, existingCartItem, cartItems))
                {
                    return(false);
                }

                // Update cart item
                existingCartItem.Quantity      = newQuantity;
                existingCartItem.UpdatedOnUtc  = DateTime.UtcNow;
                existingCartItem.RawAttributes = ctx.AttributeSelection.AsJson();
                _db.TryUpdate(ctx.Customer);
                await _db.SaveChangesAsync();
            }
            else
            {
                if (!_cartValidator.ValidateItemsMaximumCartQuantity(ctx.CartType, cartItems.Count, ctx.Warnings))
                {
                    return(false);
                }

                // Product is not in cart yet, create new item
                var cartItem = new ShoppingCartItem
                {
                    CustomerEnteredPrice = ctx.CustomerEnteredPrice.Amount,
                    RawAttributes        = ctx.RawAttributes,
                    ShoppingCartType     = ctx.CartType,
                    StoreId      = ctx.StoreId.Value,
                    Quantity     = ctx.Quantity,
                    Customer     = ctx.Customer,
                    Product      = ctx.Product,
                    ProductId    = ctx.Product.Id,
                    ParentItemId = null,
                    BundleItemId = ctx.BundleItem?.Id
                };

                if (!await _cartValidator.ValidateAddToCartItemAsync(ctx, cartItem, cartItems))
                {
                    return(false);
                }

                // Check whether the product is part of a bundle, the bundle item or just any item.
                // If product is no child of bundle or no bundle at all
                if (ctx.BundleItem == null)
                {
                    // Set cart item as item for simple & bundle products, only if its not set by the caller
                    ctx.Item ??= cartItem;
                }
                else
                {
                    ctx.ChildItems.Add(cartItem);
                }
            }

            _requestCache.RemoveByPattern(CartItemsPatternKey);

            // If ctx.Product is a bundle product and the setting to automatically add bundle products is true, try to add all corresponding BundleItems.

            if (ctx.AutomaticallyAddBundleProducts &&
                ctx.Product.ProductType == ProductType.BundledProduct &&
                ctx.BundleItem == null &&
                ctx.Warnings.Count == 0)
            {
                var bundleItems = await _db.ProductBundleItem
                                  .Include(x => x.Product)
                                  .Include(x => x.BundleProduct)
                                  .ApplyBundledProductsFilter(new[] { ctx.Product.Id }, true)
                                  .ToListAsync();

                foreach (var bundleItem in bundleItems)
                {
                    var bundleItemContext = new AddToCartContext
                    {
                        Warnings                         = new(),
                        Item                             = ctx.Item,
                        StoreId                          = ctx.StoreId,
                        Customer                         = ctx.Customer,
                        CartType                         = ctx.CartType,
                        BundleItem                       = bundleItem,
                        ChildItems                       = ctx.ChildItems,
                        Product                          = bundleItem.Product,
                        Quantity                         = bundleItem.Quantity,
                        VariantQuery                     = ctx.VariantQuery,
                        RawAttributes                    = ctx.RawAttributes,
                        CustomerEnteredPrice             = ctx.CustomerEnteredPrice,
                        AutomaticallyAddRequiredProducts = ctx.AutomaticallyAddRequiredProducts,
                    };

                    // If bundleItem could not be added to the shopping cart, remove child items
                    if (!await AddToCartAsync(bundleItemContext))
                    {
                        ctx.ChildItems.Clear();
                        // TODO: (ms) (core) Add warning for bundle products that are unable to be added to the cart.
                        break;
                    }
                }
            }

            // If context is no bundleItem, add item (parent) and its children (grouped product)
            if ((ctx.Product.ProductType == ProductType.SimpleProduct || ctx.AutomaticallyAddBundleProducts) &&
                ctx.BundleItem == null && ctx.Warnings.Count == 0)
            {
                await AddItemToCartAsync(ctx);
            }

            return(true);
        }
Пример #4
0
        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;
        }