Ejemplo n.º 1
0
        public virtual Task <IList <OrganizedShoppingCartItem> > GetCartItemsAsync(
            Customer customer         = null,
            ShoppingCartType cartType = ShoppingCartType.ShoppingCart,
            int storeId = 0)
        {
            customer ??= _workContext.CurrentCustomer;

            var cacheKey = CartItemsKey.FormatInvariant(customer.Id, (int)cartType, storeId);
            var result   = _requestCache.Get(cacheKey, async() =>
            {
                var cartItems = new List <ShoppingCartItem>();
                // TODO: (ms) (core) Do we need to check for ShoppingCartItems.Product.ProductVariantAttribute is loaded too? Would this direct access be even possible then?
                if (_db.IsCollectionLoaded(customer, x => x.ShoppingCartItems))
                {
                    var filteredCartItems = customer.ShoppingCartItems
                                            .Where(x => x.CustomerId == customer.Id && x.ShoppingCartTypeId == (int)cartType);

                    if (storeId > 0)
                    {
                        filteredCartItems = cartItems.Where(x => x.StoreId == storeId);
                    }

                    cartItems = filteredCartItems.ToList();
                }
                else
                {
                    // TODO: (core) Re-apply data to Customer.ShoppingCartItems collection to prevent reloads.
                    cartItems = await _db.ShoppingCartItems
                                .Include(x => x.Product)
                                .ThenInclude(x => x.ProductVariantAttributes)
                                .ApplyStandardFilter(cartType, storeId, customer)
                                .ToListAsync();
                }

                // Prefetch all product variant attributes
                var allAttributes    = new ProductVariantAttributeSelection(string.Empty);
                var allAttributeMaps = cartItems.SelectMany(x => x.AttributeSelection.AttributesMap);

                foreach (var attribute in allAttributeMaps)
                {
                    if (allAttributes.AttributesMap.Contains(attribute))
                    {
                        continue;
                    }

                    allAttributes.AddAttribute(attribute.Key, attribute.Value);
                }

                // TODO: (ms) (core) Check if this is sufficient and good prefetch -> what about caching or skipping already loaded?
                await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(allAttributes);

                return(await OrganizeCartItemsAsync(cartItems));
            });

            return(result);
        }
Ejemplo n.º 2
0
        public virtual Task <List <OrganizedShoppingCartItem> > GetCartItemsAsync(
            Customer customer         = null,
            ShoppingCartType cartType = ShoppingCartType.ShoppingCart,
            int storeId = 0)
        {
            customer ??= _workContext.CurrentCustomer;

            var cacheKey = CartItemsKey.FormatInvariant(customer.Id, (int)cartType, storeId);
            var result   = _requestCache.Get(cacheKey, async() =>
            {
                var cartItems = new List <ShoppingCartItem>();
                if (_db.IsCollectionLoaded(customer, x => x.ShoppingCartItems))
                {
                    var filteredCartItems = customer.ShoppingCartItems
                                            .Where(x => x.CustomerId == customer.Id && x.ShoppingCartTypeId == (int)cartType);

                    if (storeId > 0)
                    {
                        filteredCartItems = filteredCartItems.Where(x => x.StoreId == storeId);
                    }

                    cartItems = filteredCartItems.ToList();
                }
                else
                {
                    cartItems = await _db.ShoppingCartItems
                                .Include(x => x.Product)
                                .ThenInclude(x => x.ProductVariantAttributes)
                                .ApplyStandardFilter(cartType, storeId, customer)
                                .ToListAsync();

                    customer.ShoppingCartItems = cartItems;
                }

                // Prefetch all product variant attributes
                var allAttributes    = new ProductVariantAttributeSelection(string.Empty);
                var allAttributeMaps = cartItems.SelectMany(x => x.AttributeSelection.AttributesMap);

                foreach (var attribute in allAttributeMaps)
                {
                    if (allAttributes.AttributesMap.Contains(attribute))
                    {
                        continue;
                    }

                    allAttributes.AddAttribute(attribute.Key, attribute.Value);
                }

                await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(allAttributes);

                return(await OrganizeCartItemsAsync(cartItems));
            });

            return(result);
        }
Ejemplo n.º 3
0
        public virtual Task <List <OrganizedShoppingCartItem> > GetCartItemsAsync(
            Customer customer         = null,
            ShoppingCartType cartType = ShoppingCartType.ShoppingCart,
            int storeId = 0)
        {
            customer ??= _workContext.CurrentCustomer;

            var cacheKey = CartItemsKey.FormatInvariant(customer.Id, (int)cartType, storeId);
            var result   = _requestCache.Get(cacheKey, async() =>
            {
                await _db.LoadCollectionAsync(customer, x => x.ShoppingCartItems, false, x =>
                {
                    return(x
                           .Include(x => x.Product)
                           .ThenInclude(x => x.ProductVariantAttributes));
                });

                var cartItems = customer.ShoppingCartItems.FilterByCartType(cartType, storeId);

                // Prefetch all product variant attributes
                var allAttributes    = new ProductVariantAttributeSelection(string.Empty);
                var allAttributeMaps = cartItems.SelectMany(x => x.AttributeSelection.AttributesMap);

                foreach (var attribute in allAttributeMaps)
                {
                    if (allAttributes.AttributesMap.Contains(attribute))
                    {
                        continue;
                    }

                    allAttributes.AddAttribute(attribute.Key, attribute.Value);
                }

                await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(allAttributes);

                return(await OrganizeCartItemsAsync(cartItems));
            });

            return(result);
        }
Ejemplo n.º 4
0
        // TODO: (ms) (core) TESTING! Make sure it works in any case - Works for ReOrder().
        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)
            {
                // Create attribute selection from product attributes
                var attributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.Item.AttributeSelection);

                ctx.RawAttributes = ctx.Item.RawAttributes;

                // 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.AutomaticallyAddRequiredProductsIfEnabled)
            {
                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.AutomaticallyAddBundleProductsIfEnabled &&
                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,
                        AutomaticallyAddRequiredProductsIfEnabled = ctx.AutomaticallyAddRequiredProductsIfEnabled,
                    };

                    // If bundleItem could not be added to the shopping cart, remove child items
                    if (!await AddToCartAsync(bundleItemContext))
                    {
                        ctx.ChildItems.Clear();
                        break;
                    }
                }
            }

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

            return(true);
        }
Ejemplo n.º 5
0
        public virtual async Task <string> FormatAttributesAsync(
            ProductVariantAttributeSelection selection,
            Product product,
            Customer customer                = null,
            string separator                 = "<br />",
            bool htmlEncode                  = true,
            bool includePrices               = true,
            bool includeProductAttributes    = true,
            bool includeGiftCardAttributes   = true,
            bool includeHyperlinks           = true,
            ProductBatchContext batchContext = null)
        {
            Guard.NotNull(selection, nameof(selection));
            Guard.NotNull(product, nameof(product));

            customer ??= _workContext.CurrentCustomer;

            using var pool = StringBuilderPool.Instance.Get(out var result);

            if (includeProductAttributes)
            {
                var languageId = _workContext.WorkingLanguage.Id;
                var attributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(selection);

                var attributesDic = attributes.ToDictionary(x => x.Id);

                // Key: ProductVariantAttributeValue.Id, value: calculated attribute price adjustment.
                var priceAdjustments = includePrices && _catalogSettings.ShowVariantCombinationPriceAdjustment
                    ? await _priceCalculationService.CalculateAttributePriceAdjustmentsAsync(product, selection, 1, _priceCalculationService.CreateDefaultOptions(false, customer, null, batchContext))
                    : new Dictionary <int, CalculatedPriceAdjustment>();

                foreach (var kvp in selection.AttributesMap)
                {
                    if (!attributesDic.TryGetValue(kvp.Key, out var pva))
                    {
                        continue;
                    }

                    foreach (var value in kvp.Value)
                    {
                        var valueStr     = value.ToString().EmptyNull();
                        var pvaAttribute = string.Empty;

                        if (pva.IsListTypeAttribute())
                        {
                            var pvaValue = pva.ProductVariantAttributeValues.FirstOrDefault(x => x.Id == valueStr.ToInt());
                            if (pvaValue != null)
                            {
                                pvaAttribute = "{0}: {1}".FormatInvariant(
                                    pva.ProductAttribute.GetLocalized(x => x.Name, languageId),
                                    pvaValue.GetLocalized(x => x.Name, languageId));

                                if (includePrices)
                                {
                                    if (_shoppingCartSettings.ShowLinkedAttributeValueQuantity &&
                                        pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage &&
                                        pvaValue.Quantity > 1)
                                    {
                                        pvaAttribute = pvaAttribute + " × " + pvaValue.Quantity;
                                    }

                                    if (priceAdjustments.TryGetValue(pvaValue.Id, out var adjustment))
                                    {
                                        if (adjustment.Price > 0)
                                        {
                                            pvaAttribute += $" (+{adjustment.Price})";
                                        }
                                        else if (adjustment.Price < 0)
                                        {
                                            pvaAttribute += $" (-{adjustment.Price * -1})";
                                        }
                                    }
                                }

                                if (htmlEncode)
                                {
                                    pvaAttribute = pvaAttribute.HtmlEncode();
                                }
                            }
                        }
                        else if (pva.AttributeControlType == AttributeControlType.MultilineTextbox)
                        {
                            string attributeName = pva.ProductAttribute.GetLocalized(x => x.Name, languageId);

                            pvaAttribute = "{0}: {1}".FormatInvariant(
                                htmlEncode ? attributeName.HtmlEncode() : attributeName,
                                HtmlUtils.ConvertPlainTextToHtml(valueStr.HtmlEncode()));
                        }
                        else if (pva.AttributeControlType == AttributeControlType.FileUpload)
                        {
                            if (Guid.TryParse(valueStr, out var downloadGuid) && downloadGuid != Guid.Empty)
                            {
                                var download = await _db.Downloads
                                               .AsNoTracking()
                                               .Include(x => x.MediaFile)
                                               .Where(x => x.DownloadGuid == downloadGuid)
                                               .FirstOrDefaultAsync();

                                if (download?.MediaFile != null)
                                {
                                    var attributeText = string.Empty;
                                    var fileName      = htmlEncode
                                        ? download.MediaFile.Name.HtmlEncode()
                                        : download.MediaFile.Name;

                                    if (includeHyperlinks)
                                    {
                                        // TODO: (core) add a method for getting URL (use routing because it handles all SEO friendly URLs).
                                        var downloadLink = _webHelper.GetStoreLocation(false) + "download/getfileupload/?downloadId=" + download.DownloadGuid;
                                        attributeText = $"<a href=\"{downloadLink}\" class=\"fileuploadattribute\">{fileName}</a>";
                                    }
                                    else
                                    {
                                        attributeText = fileName;
                                    }

                                    string attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId);

                                    pvaAttribute = "{0}: {1}".FormatInvariant(
                                        htmlEncode ? attributeName.HtmlEncode() : attributeName,
                                        attributeText);
                                }
                            }
                        }
                        else
                        {
                            // TextBox, Datepicker
                            pvaAttribute = "{0}: {1}".FormatInvariant(pva.ProductAttribute.GetLocalized(x => x.Name, languageId), valueStr);

                            if (htmlEncode)
                            {
                                pvaAttribute = pvaAttribute.HtmlEncode();
                            }
                        }

                        result.Grow(pvaAttribute, separator);
                    }
                }
            }

            if (includeGiftCardAttributes && product.IsGiftCard)
            {
                var gci = selection.GiftCardInfo;
                if (gci != null)
                {
                    // Sender.
                    var giftCardFrom = product.GiftCardType == GiftCardType.Virtual
                        ? (await _localizationService.GetResourceAsync("GiftCardAttribute.From.Virtual")).FormatInvariant(gci.SenderName, gci.SenderEmail)
                        : (await _localizationService.GetResourceAsync("GiftCardAttribute.From.Physical")).FormatInvariant(gci.SenderName);

                    // Recipient.
                    var giftCardFor = product.GiftCardType == GiftCardType.Virtual
                        ? (await _localizationService.GetResourceAsync("GiftCardAttribute.For.Virtual")).FormatInvariant(gci.RecipientName, gci.RecipientEmail)
                        : (await _localizationService.GetResourceAsync("GiftCardAttribute.For.Physical")).FormatInvariant(gci.RecipientName);

                    if (htmlEncode)
                    {
                        giftCardFrom = giftCardFrom.HtmlEncode();
                        giftCardFor  = giftCardFor.HtmlEncode();
                    }

                    result.Grow(giftCardFrom, separator);
                    result.Grow(giftCardFor, separator);
                }
            }

            return(result.ToString());
        }
Ejemplo n.º 6
0
        // was GetShoppingCartItemAttributeWarnings
        public virtual async Task <IList <string> > ValidateProductAttributesAsync(AddToCartContext ctx, IEnumerable <OrganizedShoppingCartItem> shoppingCart)
        {
            Guard.NotNull(ctx, nameof(ctx));

            var warnings = new List <string>();

            // Customer cannot select anything cause bundles have no attributes
            if (ctx.Product.ProductType == ProductType.BundledProduct ||
                ctx.BundleItem != null && !ctx.BundleItem.BundleProduct.BundlePerItemPricing)
            {
                return(warnings);
            }

            // Get selected product variant attributes
            var selectedAttributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.AttributeSelection);

            foreach (var attribute in selectedAttributes)
            {
                if (attribute.Product == null || attribute.Product.Id != ctx.Product.Id)
                {
                    warnings.Add(T("ShoppingCart.AttributeError"));
                    ctx.Warnings.AddRange(warnings);
                    return(warnings);
                }
            }

            // Get existing product variant attributes
            foreach (var existingAttribute in ctx.Product.ProductVariantAttributes)
            {
                if (!existingAttribute.IsRequired)
                {
                    continue;
                }

                var found = false;
                // Selected product attributes
                foreach (var selectedAttribute in selectedAttributes)
                {
                    if (selectedAttribute.Id == existingAttribute.Id)
                    {
                        var values = ctx.AttributeSelection.GetAttributeValues(selectedAttribute.Id).Select(x => x.ToString()).ToList();
                        found = values.Find(x => x.HasValue()).HasValue();

                        if (found)
                        {
                            break;
                        }
                    }
                }

                // If attribute is filtered out by bundle item, it cannot be selected by the customer
                found = !found && (ctx.BundleItem?.FilterAttributes ?? false)
                    ? !ctx.BundleItem.AttributeFilters.Any(x => x.AttributeId == existingAttribute.ProductAttributeId)
                    : true;

                if (!found)
                {
                    warnings.Add(T(
                                     "ShoppingCart.SelectAttribute",
                                     existingAttribute.TextPrompt.IsEmpty()
                            ? existingAttribute.ProductAttribute.GetLocalized(x => x.Name)
                            : existingAttribute.GetLocalized(x => x.TextPrompt)
                                     ));
                }
            }

            if (warnings.Count > 0)
            {
                ctx.Warnings.AddRange(warnings);
                return(warnings);
            }

            // Checks whether there is an active selected attribute combination
            if (ctx.AttributeSelection.AttributesMap.Any())
            {
                var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(ctx.Product.Id, ctx.AttributeSelection);

                if (combination != null && !combination.IsActive)
                {
                    warnings.Add(T("ShoppingCart.NotAvailable"));
                }
            }

            var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(ctx.AttributeSelection);

            var linkedProductIds = attributeValues
                                   .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                                   .Select(x => x.LinkedProductId)
                                   .Distinct();

            // Get products linked to attributes
            var linkedProducts = await _db.Products.GetManyAsync(linkedProductIds);

            // Filter products which could not be loaded
            var notFoundProductIds = linkedProductIds.Except(linkedProducts.Select(x => x.Id));

            foreach (var productId in notFoundProductIds)
            {
                warnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", productId));
            }

            // Validate each linkedProduct, create shopping cart item from linkedProduct and run validation
            foreach (var attributeValue in attributeValues)
            {
                var linkedProduct = linkedProducts.FirstOrDefault(x => x.Id == attributeValue.LinkedProductId);
                if (linkedProduct == null)
                {
                    warnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", attributeValue.LinkedProductId));
                    continue;
                }

                var newCtx = new AddToCartContext
                {
                    Product  = linkedProduct,
                    Customer = ctx.Customer,
                    CartType = ctx.CartType,
                    StoreId  = ctx.StoreId,
                    Quantity = ctx.Quantity * attributeValue.Quantity
                };

                var linkageWarnings = await ValidateCartItemAsync(newCtx, shoppingCart);

                foreach (var linkageWarning in linkageWarnings)
                {
                    warnings.Add(
                        T("ShoppingCart.ProductLinkageAttributeWarning",
                          attributeValue.ProductVariantAttribute.ProductAttribute.GetLocalized(x => x.Name),
                          attributeValue.GetLocalized(x => x.Name),
                          linkageWarning)
                        );
                }
            }

            ctx.Warnings.AddRange(warnings);
            return(warnings);
        }
Ejemplo n.º 7
0
        public virtual async Task <bool> ValidateProductAttributesAsync(ShoppingCartItem cartItem, IEnumerable <OrganizedShoppingCartItem> cartItems, IList <string> warnings)
        {
            Guard.NotNull(cartItem, nameof(cartItem));

            // Check if the product is a bundle. Since bundles have no attributes, the customer has nothing to select
            if (cartItem.Product.ProductType == ProductType.BundledProduct ||
                cartItem.BundleItem?.BundleProduct != null && !cartItem.BundleItem.BundleProduct.BundlePerItemPricing)
            {
                if (cartItem.RawAttributes.HasValue())
                {
                    warnings.Add(T("ShoppingCart.Bundle.NoAttributes"));
                }

                return(true);
            }

            // Get selected product variant attributes and check for product errors
            var selectedAttributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(cartItem.AttributeSelection);

            foreach (var attribute in selectedAttributes)
            {
                if (attribute.Product == null || attribute.Product.Id != cartItem.Product.Id)
                {
                    warnings.Add(T("ShoppingCart.AttributeError"));
                    return(false);
                }
            }

            await _db.LoadCollectionAsync(cartItem.Product, x => x.ProductVariantAttributes, false, q => q.Include(x => x.ProductAttribute));

            var currentWarnings = new List <string>();

            // Get existing product variant attributes
            foreach (var existingAttribute in cartItem.Product.ProductVariantAttributes)
            {
                if (!existingAttribute.IsRequired)
                {
                    continue;
                }

                var found = false;
                // Selected product attributes
                foreach (var selectedAttribute in selectedAttributes)
                {
                    if (selectedAttribute.Id == existingAttribute.Id)
                    {
                        var values = cartItem.AttributeSelection.GetAttributeValues(selectedAttribute.Id)
                                     .Select(x => x.ToString())
                                     .ToList();

                        found = values.Find(x => x.HasValue()).HasValue();

                        if (found)
                        {
                            break;
                        }
                    }
                }

                // If attribute is filtered out by bundle item, it cannot be selected by the customer
                if (!found &&
                    (cartItem.BundleItem?.FilterAttributes ?? false) &&
                    !cartItem.BundleItem.AttributeFilters.Any(x => x.AttributeId == existingAttribute.ProductAttributeId))
                {
                    found = true;
                }

                if (!found)
                {
                    currentWarnings.Add(T(
                                            "ShoppingCart.SelectAttribute",
                                            existingAttribute.TextPrompt.IsEmpty()
                            ? existingAttribute.ProductAttribute.GetLocalized(x => x.Name)
                            : existingAttribute.GetLocalized(x => x.TextPrompt)
                                            ));
                }
            }

            if (currentWarnings.Any())
            {
                warnings.AddRange(currentWarnings);
                return(false);
            }

            // Checks whether there is an active selected attribute combination
            if (cartItem.AttributeSelection.AttributesMap.Any())
            {
                var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(cartItem.Product.Id, cartItem.AttributeSelection);

                if (combination != null && !combination.IsActive)
                {
                    currentWarnings.Add(T("ShoppingCart.NotAvailable"));
                }
            }

            var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(cartItem.AttributeSelection);

            var linkedProductIds = attributeValues
                                   .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                                   .Select(x => x.LinkedProductId)
                                   .Distinct();

            // Get products linked to attributes
            var linkedProducts = await _db.Products.GetManyAsync(linkedProductIds);

            // Filter products which could not be loaded
            var notFoundProductIds = linkedProductIds.Except(linkedProducts.Select(x => x.Id));

            foreach (var productId in notFoundProductIds)
            {
                currentWarnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", productId));
            }

            // Validate each linkedProduct, create shopping cart item from linkedProduct and run validation
            foreach (var linkedProductId in linkedProductIds)
            {
                var linkedProduct        = linkedProducts.FirstOrDefault(x => x.Id == linkedProductId);
                var linkedAttributeValue = attributeValues.FirstOrDefault(x => x.LinkedProductId == linkedProductId);

                if (linkedProduct == null || linkedAttributeValue == null)
                {
                    currentWarnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", linkedProductId));
                    continue;
                }

                var item = new ShoppingCartItem
                {
                    ProductId        = linkedProduct.Id,
                    Product          = linkedProduct,
                    ShoppingCartType = cartItem.ShoppingCartType,
                    Customer         = cartItem.Customer,
                    StoreId          = cartItem.StoreId,
                    Quantity         = cartItem.Quantity * linkedAttributeValue.Quantity
                };

                var ctx = new AddToCartContext
                {
                    Product  = linkedProduct,
                    Customer = cartItem.Customer,
                    CartType = cartItem.ShoppingCartType,
                    StoreId  = cartItem.StoreId,
                    Quantity = cartItem.Quantity * linkedAttributeValue.Quantity
                };

                // Get product linkage warnings
                await ValidateAddToCartItemAsync(ctx, item, cartItems);

                foreach (var linkageWarning in ctx.Warnings)
                {
                    currentWarnings.Add(
                        T("ShoppingCart.ProductLinkageAttributeWarning",
                          linkedAttributeValue.ProductVariantAttribute.ProductAttribute.GetLocalized(x => x.Name),
                          linkedAttributeValue.GetLocalized(x => x.Name),
                          linkageWarning)
                        );
                }
            }

            warnings.AddRange(currentWarnings);
            return(!currentWarnings.Any());
        }
        // TODO: (ms) (core) AddToCartContext needs to have bundleItem and childItems already included correctly, they get just added...
        public virtual async Task <IList <string> > AddToCartAsync(AddToCartContext ctx)
        {
            Guard.NotNull(ctx, nameof(ctx));

            // This is called when customer adds a product to cart
            var warnings = new List <string>();

            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)
            {
                // Create attribute selection from  product attributes
                var attributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.Item.AttributeSelection);

                ctx.RawAttributes = ctx.Item.AttributeSelection.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(ctx.Warnings);
                    }
                }
            }

            warnings.AddRange(await _cartValidator.ValidateAccessPermissionsAsync(ctx));
            if (warnings.Count > 0)
            {
                return(warnings);
            }

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

            // Adds required products automatically if it is enabled
            if (ctx.AutomaticallyAddRequiredProductsIfEnabled)
            {
                var requiredProductIds = ctx.Product.ParseRequiredProductIds();
                if (requiredProductIds.Any())
                {
                    var cartProductIds            = shoppingCart.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,
                            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
            warnings.AddRange(await _cartValidator.ValidateRequiredProductsAsync(ctx, shoppingCart));

            OrganizedShoppingCartItem existingCartItem = null;

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

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

                warnings.AddRange(await _cartValidator.ValidateCartItemAsync(ctx, shoppingCart));
                if (warnings.Count > 0)
                {
                    return(warnings);
                }

                // Update cart item
                existingCartItem.Item.Quantity      = ctx.Quantity;
                existingCartItem.Item.UpdatedOnUtc  = DateTime.UtcNow;
                existingCartItem.Item.RawAttributes = ctx.AttributeSelection.AsJson();
                _db.TryUpdate(ctx.Customer);
                await _db.SaveChangesAsync();
            }
            else
            {
                warnings.AddRange(await _cartValidator.ValidateCartItemAsync(ctx, shoppingCart));
                if (warnings.Count > 0)
                {
                    return(warnings);
                }

                var warning = _cartValidator.ValidateCartItemsMaximum(ctx.CartType, shoppingCart.Count);
                if (warning.Count > 0)
                {
                    ctx.Warnings.AddRange(warning);
                    warnings.AddRange(warning);
                    return(warnings);
                }

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

                // If product is no bundle, add it as cartItem
                if (ctx.BundleItem == null)
                {
                    Debug.Assert(ctx.Item == null, "Add to cart item already specified");
                    ctx.Item = cartItem;
                }
                else
                {
                    ctx.ChildItems.Add(cartItem);
                }
            }

            _requestCache.RemoveByPattern(CartItemsPatternKey);

            // If ctx.Product is a bundle product, try adding all corresponding bundleItems
            if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.BundleItem == null && warnings.Count == 0)
            {
                // Get all bundle items and add each to the cart
                var bundleItems = _db.IsCollectionLoaded(ctx.Product, x => x.ProductBundleItems)
                    ? ctx.Product.ProductBundleItems
                    : await _db.ProductBundleItem
                                  .ApplyBundledProductsFilter(new[] { ctx.Product.Id })
                                  .ToListAsync();

                foreach (var bundleItem in bundleItems)
                {
                    // Try add each bundleItem to the cart
                    warnings.AddRange(
                        await AddToCartAsync(
                            new AddToCartContext
                    {
                        Item         = ctx.Item,
                        Customer     = ctx.Customer,
                        BundleItem   = bundleItem,
                        Warnings     = ctx.Warnings,
                        CartType     = ctx.CartType,
                        StoreId      = ctx.StoreId.Value,
                        ChildItems   = ctx.ChildItems,
                        Product      = bundleItem.Product,
                        Quantity     = bundleItem.Quantity,
                        VariantQuery = ctx.VariantQuery,
                        AutomaticallyAddRequiredProductsIfEnabled = ctx.AutomaticallyAddRequiredProductsIfEnabled
                    })
                        );

                    // If bundleItem could not be added to the shopping cart, remove child items
                    if (warnings.Count > 0)
                    {
                        ctx.ChildItems.Clear();
                        break;
                    }
                }
            }

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

            return(warnings);
        }
Ejemplo n.º 9
0
        private async Task ProcessAttributes(DbContextScope scope, Product product, Product clone, IEnumerable <Language> languages)
        {
            var localizedKeySelectors = new List <Expression <Func <ProductVariantAttributeValue, string> > >
            {
                x => x.Name,
                x => x.Alias
            };

            await _db.LoadCollectionAsync(product, x => x.ProductVariantAttributes);

            await _db.LoadCollectionAsync(product, x => x.ProductVariantAttributeCombinations);

            // Former attribute id > clone.
            var attributeMap = new Dictionary <int, ProductVariantAttribute>();
            // Former attribute value id > clone.
            var valueMap        = new Dictionary <int, ProductVariantAttributeValue>();
            var newCombinations = new List <ProductVariantAttributeCombination>();

            // Product attributes.
            foreach (var attribute in product.ProductVariantAttributes)
            {
                // Save associated value (used for combinations copying).
                attributeMap[attribute.Id] = new ProductVariantAttribute
                {
                    ProductId              = clone.Id,
                    ProductAttributeId     = attribute.ProductAttributeId,
                    TextPrompt             = attribute.TextPrompt,
                    IsRequired             = attribute.IsRequired,
                    AttributeControlTypeId = attribute.AttributeControlTypeId,
                    DisplayOrder           = attribute.DisplayOrder
                };
            }

            // Reverse tracking order to have the clones in the same order in the database as the originals.
            _db.ProductVariantAttributes.AddRange(attributeMap.Select(x => x.Value).Reverse());

            // >>>>>> Commit attributes.
            await scope.CommitAsync();

            // Product variant attribute values.
            foreach (var attribute in product.ProductVariantAttributes)
            {
                var attributeClone = attributeMap[attribute.Id];

                foreach (var value in attribute.ProductVariantAttributeValues)
                {
                    // Save associated value (used for combinations copying).
                    valueMap.Add(value.Id, new ProductVariantAttributeValue
                    {
                        ProductVariantAttributeId = attributeClone.Id,
                        Name             = value.Name,
                        Color            = value.Color,
                        PriceAdjustment  = value.PriceAdjustment,
                        WeightAdjustment = value.WeightAdjustment,
                        IsPreSelected    = value.IsPreSelected,
                        DisplayOrder     = value.DisplayOrder,
                        ValueTypeId      = value.ValueTypeId,
                        LinkedProductId  = value.LinkedProductId,
                        Quantity         = value.Quantity,
                        MediaFileId      = value.MediaFileId
                    });
                }
            }

            // Reverse tracking order to have the clones in the same order in the database as the originals.
            _db.ProductVariantAttributeValues.AddRange(valueMap.Select(x => x.Value).Reverse());

            // >>>>>> Commit attribute values.
            await scope.CommitAsync();

            // Attribute value localization.
            var allValues = product.ProductVariantAttributes
                            .Reverse()
                            .SelectMany(x => x.ProductVariantAttributeValues.Reverse())
                            .ToArray();

            foreach (var value in allValues)
            {
                if (valueMap.TryGetValue(value.Id, out var newValue))
                {
                    await ProcessLocalizations(value, newValue, localizedKeySelectors, languages);
                }
            }

            // >>>>>> Commit localized values.
            await scope.CommitAsync();

            // Attribute combinations.
            foreach (var combination in product.ProductVariantAttributeCombinations)
            {
                var oldAttributesMap = combination.AttributeSelection.AttributesMap;
                var oldAttributes    = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(combination.AttributeSelection);

                var newSelection = new ProductVariantAttributeSelection(null);

                foreach (var oldAttribute in oldAttributes)
                {
                    if (attributeMap.TryGetValue(oldAttribute.Id, out var newAttribute))
                    {
                        var item = oldAttributesMap.FirstOrDefault(x => x.Key == oldAttribute.Id);
                        if (item.Key != 0)
                        {
                            foreach (var value in item.Value)
                            {
                                if (newAttribute.IsListTypeAttribute())
                                {
                                    var oldValueId = value.ToString().EmptyNull().ToInt();
                                    if (valueMap.TryGetValue(oldValueId, out var newValue))
                                    {
                                        newSelection.AddAttributeValue(newAttribute.Id, newValue.Id);
                                    }
                                }
                                else
                                {
                                    newSelection.AddAttributeValue(newAttribute.Id, value);
                                }
                            }
                        }
                    }
                }

                newCombinations.Add(new ProductVariantAttributeCombination
                {
                    ProductId             = clone.Id,
                    RawAttributes         = newSelection.AsJson(),
                    StockQuantity         = combination.StockQuantity,
                    AllowOutOfStockOrders = combination.AllowOutOfStockOrders,
                    Sku  = combination.Sku,
                    Gtin = combination.Gtin,
                    ManufacturerPartNumber = combination.ManufacturerPartNumber,
                    Price = combination.Price,
                    AssignedMediaFileIds = combination.AssignedMediaFileIds,
                    Length              = combination.Length,
                    Width               = combination.Width,
                    Height              = combination.Height,
                    BasePriceAmount     = combination.BasePriceAmount,
                    BasePriceBaseAmount = combination.BasePriceBaseAmount,
                    DeliveryTimeId      = combination.DeliveryTimeId,
                    QuantityUnitId      = combination.QuantityUnitId,
                    IsActive            = combination.IsActive
                                          //IsDefaultCombination = combination.IsDefaultCombination
                });
            }

            // Reverse tracking order to have the clones in the same order in the database as the originals.
            _db.ProductVariantAttributeCombinations.AddRange(newCombinations.AsEnumerable().Reverse());

            // >>>>>> Commit combinations.
            await scope.CommitAsync();
        }