Пример #1
0
        protected virtual async Task <IList <OrganizedShoppingCartItem> > OrganizeCartItemsAsync(ICollection <ShoppingCartItem> cart)
        {
            var result = new List <OrganizedShoppingCartItem>();

            if (cart.IsNullOrEmpty())
            {
                return(result);
            }

            var parents = cart.Where(x => x.ParentItemId is null);

            // TODO: (ms) (core) to reduce db roundtrips -> load and filter children by parents (id and so on) into lists and try to get from db as batch request

            foreach (var parent in parents)
            {
                var parentItem = new OrganizedShoppingCartItem(parent);

                var children = cart.Where(x => x.ParentItemId != null &&
                                          x.ParentItemId == parent.Id &&
                                          x.Id != parent.Id &&
                                          x.ShoppingCartTypeId == parent.ShoppingCartTypeId &&
                                          x.Product.CanBeBundleItem());

                // TODO: (ms) (core) Reduce database roundtrips in OrganizeCartItemsAsync
                foreach (var child in children)
                {
                    var childItem = new OrganizedShoppingCartItem(child);

                    if (child.RawAttributes.HasValue() &&
                        (parent.Product?.BundlePerItemPricing ?? false) &&
                        child.BundleItem != null)
                    {
                        var selection = new ProductVariantAttributeSelection(child.RawAttributes);

                        await _productAttributeMaterializer.MergeWithCombinationAsync(child.Product, selection);

                        var attributeValues = await _productAttributeMaterializer
                                              .MaterializeProductVariantAttributeValuesAsync(selection);

                        if (!attributeValues.IsNullOrEmpty())
                        {
                            childItem.BundleItemData.AdditionalCharge += attributeValues.Sum(x => x.PriceAdjustment);
                        }
                    }

                    parentItem.ChildItems.Add(childItem);
                }

                result.Add(parentItem);
            }

            return(result);
        }
        public virtual async Task <Money> GetProductCostAsync(Product product, ProductVariantAttributeSelection selection)
        {
            Guard.NotNull(product, nameof(product));
            Guard.NotNull(selection, nameof(selection));

            var productCost     = product.ProductCost;
            var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(selection);

            var productLinkageValues = attributeValues
                                       .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage && x.LinkedProductId != 0)
                                       .ToList();
            var linkedProductIds = productLinkageValues
                                   .Select(x => x.LinkedProductId)
                                   .Distinct()
                                   .ToArray();

            if (linkedProductIds.Any())
            {
                var linkedProducts = await _db.Products
                                     .AsNoTracking()
                                     .Where(x => linkedProductIds.Contains(x.Id))
                                     .Select(x => new { x.Id, x.ProductCost })
                                     .ToListAsync();

                var linkedProductsDic = linkedProducts.ToDictionarySafe(x => x.Id, x => x.ProductCost);

                foreach (var value in productLinkageValues)
                {
                    if (linkedProductsDic.TryGetValue(value.LinkedProductId, out var linkedProductCost))
                    {
                        productCost += linkedProductCost * value.Quantity;
                    }
                }
            }

            return(new(productCost, _primaryCurrency));
        }
Пример #3
0
        public virtual async Task <AdjustInventoryResult> AdjustInventoryAsync(Product product, ProductVariantAttributeSelection selection, bool decrease, int quantity)
        {
            Guard.NotNull(product, nameof(product));
            Guard.NotNull(selection, nameof(selection));

            var result = new AdjustInventoryResult();

            switch (product.ManageInventoryMethod)
            {
            case ManageInventoryMethod.ManageStock:
            {
                result.StockQuantityOld = product.StockQuantity;

                result.StockQuantityNew = decrease
                            ? product.StockQuantity - quantity
                            : product.StockQuantity + quantity;

                var newPublished             = product.Published;
                var newDisableBuyButton      = product.DisableBuyButton;
                var newDisableWishlistButton = product.DisableWishlistButton;

                // Check if the minimum quantity is reached.
                switch (product.LowStockActivity)
                {
                case LowStockActivity.DisableBuyButton:
                    newDisableBuyButton      = product.MinStockQuantity >= result.StockQuantityNew;
                    newDisableWishlistButton = product.MinStockQuantity >= result.StockQuantityNew;
                    break;

                case LowStockActivity.Unpublish:
                    newPublished = product.MinStockQuantity <= result.StockQuantityNew;
                    break;
                }

                product.StockQuantity         = result.StockQuantityNew;
                product.DisableBuyButton      = newDisableBuyButton;
                product.DisableWishlistButton = newDisableWishlistButton;
                product.Published             = newPublished;

                // Commit required because of store owner notification.
                await _db.SaveChangesAsync();

                if (decrease && product.NotifyAdminForQuantityBelow > result.StockQuantityNew)
                {
                    await _messageFactory.SendQuantityBelowStoreOwnerNotificationAsync(product, _localizationSettings.DefaultAdminLanguageId);
                }
            }
            break;

            case ManageInventoryMethod.ManageStockByAttributes:
            {
                var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(product.Id, selection);

                if (combination != null)
                {
                    result.StockQuantityOld = combination.StockQuantity;

                    result.StockQuantityNew = decrease
                                ? combination.StockQuantity - quantity
                                : combination.StockQuantity + quantity;

                    combination.StockQuantity = result.StockQuantityNew;
                }
            }
            break;

            case ManageInventoryMethod.DontManageStock:
            default:
                // Do nothing.
                break;
            }

            var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(selection);

            var productLinkageValues = attributeValues
                                       .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                                       .ToList();

            foreach (var chunk in productLinkageValues.Slice(100))
            {
                var linkedProductIds = chunk.Select(x => x.LinkedProductId).Distinct().ToArray();
                var linkedProducts   = await _db.Products.GetManyAsync(linkedProductIds, true);

                var linkedProductsDic = linkedProducts.ToDictionarySafe(x => x.Id);

                foreach (var value in chunk)
                {
                    if (linkedProductsDic.TryGetValue(value.LinkedProductId, out var linkedProduct))
                    {
                        await AdjustInventoryAsync(linkedProduct, null, decrease, quantity *value.Quantity);
                    }
                }
            }

            await _db.SaveChangesAsync();

            return(result);
        }
Пример #4
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);
        }
        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());
        }
Пример #6
0
        public virtual async Task <AdjustInventoryResult> AdjustInventoryAsync(Product product, ProductVariantAttributeSelection selection, bool decrease, int quantity)
        {
            Guard.NotNull(product, nameof(product));
            Guard.NotNull(selection, nameof(selection));

            var result = new AdjustInventoryResult();

            switch (product.ManageInventoryMethod)
            {
            case ManageInventoryMethod.ManageStock:
            {
                result.StockQuantityOld = product.StockQuantity;

                result.StockQuantityNew = decrease
                            ? product.StockQuantity - quantity
                            : product.StockQuantity + quantity;

                var newPublished             = product.Published;
                var newDisableBuyButton      = product.DisableBuyButton;
                var newDisableWishlistButton = product.DisableWishlistButton;

                // Check if the minimum quantity is reached.
                switch (product.LowStockActivity)
                {
                case LowStockActivity.DisableBuyButton:
                    newDisableBuyButton      = product.MinStockQuantity >= result.StockQuantityNew;
                    newDisableWishlistButton = product.MinStockQuantity >= result.StockQuantityNew;
                    break;

                case LowStockActivity.Unpublish:
                    newPublished = product.MinStockQuantity <= result.StockQuantityNew;
                    break;
                }

                product.StockQuantity         = result.StockQuantityNew;
                product.DisableBuyButton      = newDisableBuyButton;
                product.DisableWishlistButton = newDisableWishlistButton;
                product.Published             = newPublished;

                // TODO: (mg) (core) ProductService.AdjustInventoryAsync doesn't send SendQuantityBelowStoreOwnerNotification anymore. Must be sent by caller after (!) database commit.
                // TODO: (mg) (core) The caller should definitely NOT be responsible for figuring out, when and how to publish messages. That would be extremely bad API design.
                //if (decrease && product.NotifyAdminForQuantityBelow > result.StockQuantityNew)
                //{
                //    _services.MessageFactory.SendQuantityBelowStoreOwnerNotification(product, _localizationSettings.DefaultAdminLanguageId);
                //}
            }
            break;

            case ManageInventoryMethod.ManageStockByAttributes:
            {
                var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(product.Id, selection);

                if (combination != null)
                {
                    result.StockQuantityOld = combination.StockQuantity;

                    result.StockQuantityNew = decrease
                                ? combination.StockQuantity - quantity
                                : combination.StockQuantity + quantity;

                    combination.StockQuantity = result.StockQuantityNew;
                }
            }
            break;

            case ManageInventoryMethod.DontManageStock:
            default:
                // Do nothing.
                break;
            }

            var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(selection);

            var productLinkageValues = attributeValues
                                       .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                                       .ToList();

            foreach (var chunk in productLinkageValues.Slice(100))
            {
                var linkedProductIds = chunk.Select(x => x.LinkedProductId).Distinct().ToArray();
                var linkedProducts   = await _db.Products.GetManyAsync(linkedProductIds, true);

                var linkedProductsDic = linkedProducts.ToDictionarySafe(x => x.Id);

                foreach (var value in chunk)
                {
                    if (linkedProductsDic.TryGetValue(value.LinkedProductId, out var linkedProduct))
                    {
                        await AdjustInventoryAsync(linkedProduct, null, decrease, quantity *value.Quantity);
                    }
                }
            }

            return(result);
        }