Exemple #1
0
        private async Task <ImageModel> PrepareOrderItemImageModelAsync(
            Product product,
            int pictureSize,
            string productName,
            ProductVariantAttributeSelection attributeSelection,
            CatalogSettings catalogSettings)
        {
            Guard.NotNull(product, nameof(product));

            MediaFileInfo file        = null;
            var           combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(product.Id, attributeSelection);

            if (combination != null)
            {
                var mediaIds = combination.GetAssignedMediaIds();
                if (mediaIds.Any())
                {
                    file = await _mediaService.GetFileByIdAsync(mediaIds[0], MediaLoadFlags.AsNoTracking);
                }
            }

            // No attribute combination image, then load product picture.
            if (file == null)
            {
                var mediaFile = await _db.ProductMediaFiles
                                .AsNoTracking()
                                .Include(x => x.MediaFile)
                                .ApplyProductFilter(product.Id)
                                .FirstOrDefaultAsync();

                if (mediaFile?.MediaFile != null)
                {
                    file = _mediaService.ConvertMediaFile(mediaFile.MediaFile);
                }
            }

            // Let's check whether this product has some parent "grouped" product.
            if (file == null && product.Visibility == ProductVisibility.Hidden && product.ParentGroupedProductId > 0)
            {
                var mediaFile = await _db.ProductMediaFiles
                                .AsNoTracking()
                                .Include(x => x.MediaFile)
                                .ApplyProductFilter(product.ParentGroupedProductId)
                                .FirstOrDefaultAsync();

                if (mediaFile?.MediaFile != null)
                {
                    file = _mediaService.ConvertMediaFile(mediaFile.MediaFile);
                }
            }

            return(new ImageModel
            {
                File = file,
                ThumbSize = pictureSize,
                Title = file?.File?.GetLocalized(x => x.Title)?.Value.NullEmpty() ?? T("Media.Product.ImageLinkTitleFormat", productName),
                Alt = file?.File?.GetLocalized(x => x.Alt)?.Value.NullEmpty() ?? T("Media.Product.ImageAlternateTextFormat", productName),
                NoFallback = catalogSettings.HideProductDefaultPictures
            });
        }
Exemple #2
0
        protected async Task <ImageModel> PrepareCartItemPictureModelAsync(Product product, int pictureSize, string productName, ProductVariantAttributeSelection attributeSelection)
        {
            Guard.NotNull(product, nameof(product));
            Guard.NotNull(attributeSelection, nameof(attributeSelection));

            MediaFileInfo file        = null;
            var           combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(product.Id, attributeSelection);

            if (combination != null)
            {
                var fileIds = combination.GetAssignedMediaIds();
                if (fileIds?.Any() ?? false)
                {
                    file = await _mediaService.GetFileByIdAsync(fileIds[0], MediaLoadFlags.AsNoTracking);
                }
            }

            // No attribute combination image, then load product picture.
            if (file == null)
            {
                var productMediaFile = await _db.ProductMediaFiles
                                       .Include(x => x.MediaFile)
                                       .Where(x => x.Id == product.Id)
                                       .OrderBy(x => x.DisplayOrder)
                                       .FirstOrDefaultAsync();

                if (productMediaFile != null)
                {
                    file = _mediaService.ConvertMediaFile(productMediaFile.MediaFile);
                }
            }

            // Let's check whether this product has some parent "grouped" product.
            if (file == null && product.Visibility == ProductVisibility.Hidden && product.ParentGroupedProductId > 0)
            {
                var productMediaFile = await _db.ProductMediaFiles
                                       .Include(x => x.MediaFile)
                                       .Where(x => x.Id == product.ParentGroupedProductId)
                                       .OrderBy(x => x.DisplayOrder)
                                       .FirstOrDefaultAsync();

                if (productMediaFile != null)
                {
                    file = _mediaService.ConvertMediaFile(productMediaFile.MediaFile);
                }
            }

            var pm = new ImageModel
            {
                Id        = file?.Id ?? 0,
                ThumbSize = pictureSize,
                Host      = _mediaService.GetUrl(file, pictureSize, null, !_catalogSettings.HideProductDefaultPictures),
                Title     = file?.File?.GetLocalized(x => x.Title)?.Value.NullEmpty() ?? T("Media.Product.ImageLinkTitleFormat", productName),
                Alt       = file?.File?.GetLocalized(x => x.Alt)?.Value.NullEmpty() ?? T("Media.Product.ImageAlternateTextFormat", productName),
                File      = file
            };

            return(pm);
        }
        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);
        }
        public override async Task MapAsync(OrganizedShoppingCartItem from, ImageModel to, dynamic parameters = null)
        {
            Guard.NotNull(from, nameof(from));
            Guard.NotNull(to, nameof(to));

            var product = parameters?.Product as Product;
            var attributeSelection = parameters?.Selection as ProductVariantAttributeSelection;
            var pictureSize = parameters?.PictureSize as int? ?? _mediaSettings.CartThumbPictureSize;
            var productName = parameters?.ProductName as string ?? string.Empty;

            if (product == null)
            {
                return;
            }

            MediaFileInfo file = null;
            if (attributeSelection != null)
            {
                var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(product.Id, attributeSelection);
                if (combination != null)
                {
                    var fileIds = combination.GetAssignedMediaIds();
                    if (fileIds.Any())
                    {
                        file = await _mediaService.GetFileByIdAsync(fileIds[0], MediaLoadFlags.AsNoTracking);
                    }
                }
            }

            // If no attribute combination image was found, then load product pictures.
            if (file == null)
            {
                var mediaFile = await _db.ProductMediaFiles
                    .AsNoTracking()
                    .Include(x => x.MediaFile)
                    .ApplyProductFilter(product.Id)
                    .FirstOrDefaultAsync();

                if (mediaFile?.MediaFile != null)
                {
                    file = _mediaService.ConvertMediaFile(mediaFile.MediaFile);
                }
            }

            // Let's check whether this product has some parent "grouped" product.
            if (file == null && product.Visibility == ProductVisibility.Hidden && product.ParentGroupedProductId > 0)
            {
                var mediaFile = await _db.ProductMediaFiles
                    .AsNoTracking()
                    .Include(x => x.MediaFile)
                    .ApplyProductFilter(product.ParentGroupedProductId)
                    .FirstOrDefaultAsync();

                if (mediaFile?.MediaFile != null)
                {
                    file = _mediaService.ConvertMediaFile(mediaFile.MediaFile);
                }
            }

            to.File = file;
            to.ThumbSize = pictureSize;
            to.Title = file?.File?.GetLocalized(x => x.Title)?.Value.NullEmpty() ?? T("Media.Product.ImageLinkTitleFormat", productName);
            to.Alt = file?.File?.GetLocalized(x => x.Alt)?.Value.NullEmpty() ?? T("Media.Product.ImageAlternateTextFormat", productName);
            to.NoFallback = _catalogSettings.HideProductDefaultPictures;
        }
Exemple #5
0
        public virtual async Task <IList <string> > ValidateProductAsync(AddToCartContext ctx, IEnumerable <OrganizedShoppingCartItem> shoppingCart)
        {
            Guard.NotNull(ctx, nameof(ctx));

            var warnings = new List <string>();
            var product  = ctx.Product;
            var cartType = ctx.CartType;

            if (product.Deleted)
            {
                warnings.Add(T("ShoppingCart.ProductDeleted"));
                ctx.Warnings.AddRange(warnings);
                return(ctx.Warnings);
            }

            // Grouped products are not available for order
            if (product.ProductType == ProductType.GroupedProduct)
            {
                warnings.Add(T("ShoppingCart.ProductNotAvailableForOrder"));
            }

            // Validate product bundle, no customer entered price allowed
            if (product.ProductType == ProductType.BundledProduct &&
                product.BundlePerItemPricing &&
                ctx.CustomerEnteredPrice != decimal.Zero)
            {
                warnings.Add(T("ShoppingCart.Bundle.NoCustomerEnteredPrice"));
            }

            // Not published or no permissions for customer or store
            if (!product.Published ||
                !await _aclService.AuthorizeAsync(product, ctx.Customer) ||
                !await _storeMappingService.AuthorizeAsync(product.Name, product.Id, ctx.StoreId ?? _storeContext.CurrentStore.Id))
            {
                warnings.Add(T("ShoppingCart.ProductUnpublished"));
            }

            // Disabled buy button
            if (cartType == ShoppingCartType.ShoppingCart && product.DisableBuyButton)
            {
                warnings.Add(T("ShoppingCart.BuyingDisabled"));
            }

            // Disabled wishlist button
            if (cartType == ShoppingCartType.Wishlist && product.DisableWishlistButton)
            {
                warnings.Add(T("ShoppingCart.WishlistDisabled"));
            }

            // Call for price
            if (cartType == ShoppingCartType.ShoppingCart && product.CallForPrice)
            {
                warnings.Add(T("Products.CallForPrice"));
            }

            // Customer entered price
            if (product.CustomerEntersPrice &&
                (ctx.CustomerEnteredPrice < product.MinimumCustomerEnteredPrice || ctx.CustomerEnteredPrice > product.MaximumCustomerEnteredPrice))
            {
                var minimum = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency);
                var maximum = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency);

                var moneyMin = _currencyService.CreateMoney(minimum, true, displayTax: false);
                var moneyMax = _currencyService.CreateMoney(maximum, true, displayTax: false);

                warnings.Add(T("ShoppingCart.CustomerEnteredPrice.RangeError", moneyMin.ToString(), moneyMax.ToString()));
            }

            // Quantity validation
            if (ctx.Quantity <= 0)
            {
                warnings.Add(T("ShoppingCart.QuantityShouldPositive"));
            }

            if (ctx.Quantity < product.OrderMinimumQuantity)
            {
                warnings.Add(T("ShoppingCart.MinimumQuantity", product.OrderMinimumQuantity));
            }

            if (ctx.Quantity > product.OrderMaximumQuantity)
            {
                warnings.Add(T("ShoppingCart.MaximumQuantity", product.OrderMaximumQuantity));
            }

            var allowedQuantities = product.ParseAllowedQuantities();

            if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(ctx.Quantity))
            {
                warnings.Add(T("ShoppingCart.AllowedQuantities", string.Join(", ", allowedQuantities)));
            }

            // Stock validation
            var validateOutOfStock = ctx.CartType == ShoppingCartType.ShoppingCart || !_cartSettings.AllowOutOfStockItemsToBeAddedToWishlist;

            if (validateOutOfStock)
            {
                switch (product.ManageInventoryMethod)
                {
                case ManageInventoryMethod.ManageStock:
                {
                    if (product.BackorderMode != BackorderMode.NoBackorders || product.StockQuantity >= ctx.Quantity)
                    {
                        break;
                    }

                    var warning = product.StockQuantity > 0
                                ? T("ShoppingCart.QuantityExceedsStock", product.StockQuantity)
                                : T("ShoppingCart.OutOfStock");

                    warnings.Add(warning);
                }
                break;

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

                    if (combination == null || combination.AllowOutOfStockOrders || combination.StockQuantity >= ctx.Quantity)
                    {
                        break;
                    }

                    var warning = combination.StockQuantity > 0
                                ? T("ShoppingCart.QuantityExceedsStock", combination.StockQuantity)
                                : T("ShoppingCart.OutOfStock");

                    warnings.Add(warning);
                }
                break;

                case ManageInventoryMethod.DontManageStock:
                default:
                    break;
                }
            }

            // Validate availability
            var availableStartDateError = false;

            if (ctx.Product.AvailableStartDateTimeUtc.HasValue)
            {
                var availableStartDate = DateTime.SpecifyKind(ctx.Product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc);
                if (availableStartDate.CompareTo(DateTime.UtcNow) > 0)
                {
                    warnings.Add(T("ShoppingCart.NotAvailable"));
                    availableStartDateError = true;
                }
            }

            if (ctx.Product.AvailableEndDateTimeUtc.HasValue && !availableStartDateError)
            {
                var availableEndDate = DateTime.SpecifyKind(ctx.Product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc);
                if (availableEndDate.CompareTo(DateTime.UtcNow) < 0)
                {
                    warnings.Add(T("ShoppingCart.NotAvailable"));
                }
            }

            ctx.Warnings.AddRange(warnings);
            return(warnings);
        }
        protected async Task <ShipmentDetailsModel> PrepareShipmentDetailsModelAsync(Shipment shipment)
        {
            Guard.NotNull(shipment, nameof(shipment));

            var order = shipment.Order;

            if (order == null)
            {
                throw new SmartException(T("Order.NotFound", shipment.OrderId));
            }

            var currentStore = Services.StoreContext.CurrentStore;
            var store        = currentStore.Id != order.StoreId
                ? (await _db.Stores.FindByIdAsync(order.StoreId, false) ?? currentStore)
                : currentStore;
            var settingFactory  = Services.SettingFactory;
            var catalogSettings = await settingFactory.LoadSettingsAsync <CatalogSettings>(store.Id);

            var shippingSettings = await settingFactory.LoadSettingsAsync <ShippingSettings>(store.Id);

            var model = new ShipmentDetailsModel
            {
                Id                = shipment.Id,
                TrackingNumber    = shipment.TrackingNumber,
                TrackingNumberUrl = shipment.TrackingUrl
            };

            if (shipment.ShippedDateUtc.HasValue)
            {
                model.ShippedDate = _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc);
            }

            if (shipment.DeliveryDateUtc.HasValue)
            {
                model.DeliveryDate = _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc);
            }

            var srcm = _providerManager.GetProvider <IShippingRateComputationMethod>(order.ShippingRateComputationMethodSystemName, store.Id);

            if (srcm != null && srcm.IsShippingRateComputationMethodActive(shippingSettings))
            {
                var shipmentTracker = srcm.Value.ShipmentTracker;
                if (shipmentTracker != null)
                {
                    // The URL entered by the merchant takes precedence over an automatically generated URL.
                    if (model.TrackingNumberUrl.IsEmpty())
                    {
                        model.TrackingNumberUrl = shipmentTracker.GetUrl(shipment.TrackingNumber);
                    }

                    if (shippingSettings.DisplayShipmentEventsToCustomers)
                    {
                        var shipmentEvents = await shipmentTracker.GetShipmentEventsAsync(shipment.TrackingNumber);

                        if (shipmentEvents != null)
                        {
                            foreach (var shipmentEvent in shipmentEvents)
                            {
                                var shipmentEventCountry = await _db.Countries
                                                           .AsNoTracking()
                                                           .ApplyIsoCodeFilter(shipmentEvent.CountryCode)
                                                           .FirstOrDefaultAsync();

                                var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel
                                {
                                    Country                                 = shipmentEventCountry != null?shipmentEventCountry.GetLocalized(x => x.Name) : shipmentEvent.CountryCode,
                                                                  Date      = shipmentEvent.Date,
                                                                  EventName = shipmentEvent.EventName,
                                                                  Location  = shipmentEvent.Location
                                };

                                model.ShipmentStatusEvents.Add(shipmentStatusEventModel);
                            }
                        }
                    }
                }
            }

            // Products in this shipment.
            model.ShowSku = catalogSettings.ShowProductSku;

            foreach (var shipmentItem in shipment.ShipmentItems)
            {
                var orderItem = await _db.OrderItems
                                .Include(x => x.Product)
                                .FindByIdAsync(shipmentItem.OrderItemId, false);

                if (orderItem == null)
                {
                    continue;
                }

                var attributeCombination = await _productAttributeMaterializer.FindAttributeCombinationAsync(orderItem.Product.Id, orderItem.AttributeSelection);

                orderItem.Product.MergeWithCombination(attributeCombination);

                var shipmentItemModel = new ShipmentDetailsModel.ShipmentItemModel
                {
                    Id              = shipmentItem.Id,
                    Sku             = orderItem.Product.Sku,
                    ProductId       = orderItem.Product.Id,
                    ProductName     = orderItem.Product.GetLocalized(x => x.Name),
                    ProductSeName   = await orderItem.Product.GetActiveSlugAsync(),
                    AttributeInfo   = orderItem.AttributeDescription,
                    QuantityOrdered = orderItem.Quantity,
                    QuantityShipped = shipmentItem.Quantity
                };

                shipmentItemModel.ProductUrl = await _productUrlHelper.GetProductUrlAsync(shipmentItemModel.ProductSeName, orderItem);

                model.Items.Add(shipmentItemModel);
            }

            model.Order = await _orderHelper.PrepareOrderDetailsModelAsync(order);

            return(model);
        }
        public virtual async Task <bool> ValidateProductAsync(ShoppingCartItem cartItem, IList <string> warnings, int?storeId = null, int?quantity = null)
        {
            Guard.NotNull(cartItem, nameof(cartItem));
            Guard.NotNull(warnings, nameof(warnings));

            var product = cartItem.Product;

            if (product == null)
            {
                warnings.Add(T("Products.NotFound", cartItem.ProductId));
                return(false);
            }

            if (product.Deleted)
            {
                warnings.Add(T("ShoppingCart.ProductDeleted"));
                return(false);
            }

            var currentWarnings = new List <string>();

            // Grouped products are not available for order
            if (product.ProductType == ProductType.GroupedProduct)
            {
                currentWarnings.Add(T("ShoppingCart.ProductNotAvailableForOrder"));
            }

            // Validate product bundle, no customer entered price allowed
            if (product.ProductType == ProductType.BundledProduct &&
                product.BundlePerItemPricing &&
                cartItem.CustomerEnteredPrice != decimal.Zero)
            {
                currentWarnings.Add(T("ShoppingCart.Bundle.NoCustomerEnteredPrice"));
            }

            // TODO: (ms) (core) returns warning even on published products!

            // Not published or no permissions for customer or store
            //if (!product.Published
            //    || !await _aclService.AuthorizeAsync(product, cartItem.Customer)
            //    || !await _storeMappingService.AuthorizeAsync(product.Name, product.Id, storeId ?? _storeContext.CurrentStore.Id))
            //{
            //    currentWarnings.Add(T("ShoppingCart.ProductUnpublished"));
            //}

            // Disabled buy button
            if (cartItem.ShoppingCartType == ShoppingCartType.ShoppingCart && product.DisableBuyButton)
            {
                currentWarnings.Add(T("ShoppingCart.BuyingDisabled"));
            }

            // Disabled wishlist button
            if (cartItem.ShoppingCartType == ShoppingCartType.Wishlist && product.DisableWishlistButton)
            {
                currentWarnings.Add(T("ShoppingCart.WishlistDisabled"));
            }

            // Call for price
            if (cartItem.ShoppingCartType == ShoppingCartType.ShoppingCart && product.CallForPrice)
            {
                currentWarnings.Add(T("Products.CallForPrice"));
            }

            // Customer entered price
            if (product.CustomerEntersPrice &&
                (cartItem.CustomerEnteredPrice < product.MinimumCustomerEnteredPrice ||
                 cartItem.CustomerEnteredPrice > product.MaximumCustomerEnteredPrice))
            {
                var min = _currencyService.ConvertToWorkingCurrency(product.MinimumCustomerEnteredPrice);
                var max = _currencyService.ConvertToWorkingCurrency(product.MaximumCustomerEnteredPrice);

                currentWarnings.Add(T("ShoppingCart.CustomerEnteredPrice.RangeError", min, max));
            }

            var quanitityToValidate = quantity ?? cartItem.Quantity;

            // Quantity validation
            if (quanitityToValidate <= 0)
            {
                currentWarnings.Add(T("ShoppingCart.QuantityShouldPositive"));
            }

            if (quanitityToValidate < product.OrderMinimumQuantity)
            {
                currentWarnings.Add(T("ShoppingCart.MinimumQuantity", product.OrderMinimumQuantity));
            }

            if (quanitityToValidate > product.OrderMaximumQuantity)
            {
                currentWarnings.Add(T("ShoppingCart.MaximumQuantity", product.OrderMaximumQuantity));
            }

            var allowedQuantities = product.ParseAllowedQuantities();

            if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(quanitityToValidate))
            {
                currentWarnings.Add(T("ShoppingCart.AllowedQuantities", string.Join(", ", allowedQuantities)));
            }

            // Stock validation
            var validateOutOfStock = cartItem.ShoppingCartType == ShoppingCartType.ShoppingCart || !_cartSettings.AllowOutOfStockItemsToBeAddedToWishlist;

            if (validateOutOfStock)
            {
                switch (product.ManageInventoryMethod)
                {
                case ManageInventoryMethod.ManageStock:
                {
                    if (product.BackorderMode != BackorderMode.NoBackorders || product.StockQuantity >= quanitityToValidate)
                    {
                        break;
                    }

                    var warning = product.StockQuantity > 0
                                ? T("ShoppingCart.QuantityExceedsStock", product.StockQuantity)
                                : T("ShoppingCart.OutOfStock");

                    currentWarnings.Add(warning);
                }
                break;

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

                    if (combination == null || combination.AllowOutOfStockOrders || combination.StockQuantity >= quanitityToValidate)
                    {
                        break;
                    }

                    var warning = combination.StockQuantity > 0
                                ? T("ShoppingCart.QuantityExceedsStock", combination.StockQuantity)
                                : T("ShoppingCart.OutOfStock");

                    currentWarnings.Add(warning);
                }
                break;

                case ManageInventoryMethod.DontManageStock:
                default:
                    break;
                }
            }

            // Validate availability
            var availableStartDateError = false;

            if (product.AvailableStartDateTimeUtc.HasValue)
            {
                var availableStartDate = DateTime.SpecifyKind(product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc);
                if (availableStartDate.CompareTo(DateTime.UtcNow) > 0)
                {
                    currentWarnings.Add(T("ShoppingCart.NotAvailable"));
                    availableStartDateError = true;
                }
            }

            if (product.AvailableEndDateTimeUtc.HasValue && !availableStartDateError)
            {
                var availableEndDate = DateTime.SpecifyKind(product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc);
                if (availableEndDate.CompareTo(DateTime.UtcNow) < 0)
                {
                    currentWarnings.Add(T("ShoppingCart.NotAvailable"));
                }
            }

            warnings.AddRange(currentWarnings);
            return(!currentWarnings.Any());
        }
        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);
        }