public async Task <bool> Handle(UpdateProductStockCommand request, CancellationToken cancellationToken)
        {
            var product = await _productService.GetProductById(request.Product.Id);

            if (product != null)
            {
                var prevStockQuantity       = _stockQuantityService.GetTotalStockQuantity(product);
                var prevMultiWarehouseStock = product.ProductWarehouseInventory.Select(i => new ProductWarehouseInventory()
                {
                    WarehouseId = i.WarehouseId, StockQuantity = i.StockQuantity, ReservedQuantity = i.ReservedQuantity
                }).ToList();

                if (string.IsNullOrEmpty(request.WarehouseId))
                {
                    product.StockQuantity = request.Stock;
                    await _inventoryManageService.UpdateStockProduct(product, false);
                }
                else
                {
                    if (product.UseMultipleWarehouses)
                    {
                        var existingPwI = product.ProductWarehouseInventory.FirstOrDefault(x => x.WarehouseId == request.WarehouseId);
                        if (existingPwI != null)
                        {
                            existingPwI.StockQuantity = request.Stock;
                            await _productService.UpdateProductWarehouseInventory(existingPwI, product.Id);
                        }
                        else
                        {
                            var newPwI = new ProductWarehouseInventory
                            {
                                WarehouseId      = request.WarehouseId,
                                StockQuantity    = request.Stock,
                                ReservedQuantity = 0
                            };
                            await _productService.InsertProductWarehouseInventory(newPwI, product.Id);
                        }

                        product.StockQuantity    = product.ProductWarehouseInventory.Sum(x => x.StockQuantity);
                        product.ReservedQuantity = product.ProductWarehouseInventory.Sum(x => x.ReservedQuantity);
                        await _inventoryManageService.UpdateStockProduct(product, false);
                    }
                    else
                    {
                        throw new ArgumentException("Product don't support multiple warehouses (warehouseId should be null or empty)");
                    }
                }
                await OutOfStockNotifications(product, prevStockQuantity, prevMultiWarehouseStock);

                //activity log
                await _customerActivityService.InsertActivity("EditProduct", product.Id, _translationService.GetResource("ActivityLog.EditProduct"), product.Name);
            }
            return(true);
        }
        public virtual async Task <(bool valid, string message)> ValidStockShipment(Shipment shipment)
        {
            foreach (var item in shipment.ShipmentItems)
            {
                var product = await _productService.GetProductById(item.ProductId);

                if (product.ManageInventoryMethodId == ManageInventoryMethod.ManageStock)
                {
                    var stock = _stockQuantityService.GetTotalStockQuantity(product, useReservedQuantity: false, warehouseId: item.WarehouseId);
                    if (stock - item.Quantity < 0)
                    {
                        return(false, $"Out of stock for product {product.Name}");
                    }
                }
                if (product.ManageInventoryMethodId == ManageInventoryMethod.ManageStockByAttributes)
                {
                    var combination = _productAttributeParser.FindProductAttributeCombination(product, item.Attributes);
                    if (combination == null)
                    {
                        return(false, $"Can't find combination for product {product.Name}");
                    }

                    var stock = _stockQuantityService.GetTotalStockQuantityForCombination(product, combination, useReservedQuantity: false, warehouseId: item.WarehouseId);
                    if (stock - item.Quantity < 0)
                    {
                        return(false, $"Out of stock for product {product.Name}");
                    }
                }
            }

            return(true, string.Empty);
        }
Exemplo n.º 3
0
        public async Task <IActionResult> LowStockReportList(DataSourceRequest command)
        {
            string vendorId = "";

            //a vendor should have access only to his products
            if (_workContext.CurrentVendor != null && !await _groupService.IsStaff(_workContext.CurrentCustomer))
            {
                vendorId = _workContext.CurrentVendor.Id;
            }

            string storeId = "";

            if (await _groupService.IsStaff(_workContext.CurrentCustomer))
            {
                storeId = _workContext.CurrentCustomer.StaffStoreId;
            }

            var lowStockProducts = await _productsReportService.LowStockProducts(vendorId, storeId);

            var models = new List <LowStockProductModel>();

            //products
            foreach (var product in lowStockProducts.products)
            {
                var lowStockModel = new LowStockProductModel
                {
                    Id   = product.Id,
                    Name = product.Name,
                    ManageInventoryMethod = product.ManageInventoryMethodId.GetTranslationEnum(_translationService, _workContext.WorkingLanguage.Id),
                    StockQuantity         = _stockQuantityService.GetTotalStockQuantity(product, total: true),
                    Published             = product.Published
                };
                models.Add(lowStockModel);
            }
            //combinations
            foreach (var combination in lowStockProducts.combinations)
            {
                var product = await _productService.GetProductById(combination.ProductId);

                var lowStockModel = new LowStockProductModel
                {
                    Id                    = product.Id,
                    Name                  = product.Name,
                    Attributes            = await _productAttributeFormatter.FormatAttributes(product, combination.Attributes, _workContext.CurrentCustomer, "<br />", true, true, true, false),
                    ManageInventoryMethod = product.ManageInventoryMethodId.GetTranslationEnum(_translationService, _workContext.WorkingLanguage.Id),
                    StockQuantity         = combination.StockQuantity,
                    Published             = product.Published
                };
                models.Add(lowStockModel);
            }
            var gridModel = new DataSourceResult
            {
                Data  = models.PagedForCommand(command),
                Total = models.Count
            };

            return(Json(gridModel));
        }
        public void Can_calculate_total_quantity_when_we_do_not_use_multiple_warehouses()
        {
            //if UseMultipleWarehouses is set to false, it will ignore StockQuantity of attached Warhouses
            var product = new Product {
                ManageInventoryMethodId = ManageInventoryMethod.ManageStock,
                UseMultipleWarehouses   = false,
                StockQuantity           = 8765
            };

            product.ProductWarehouseInventory.Add(new ProductWarehouseInventory {
                WarehouseId = "101", StockQuantity = 7
            });
            product.ProductWarehouseInventory.Add(new ProductWarehouseInventory {
                WarehouseId = "111", StockQuantity = 8
            });
            product.ProductWarehouseInventory.Add(new ProductWarehouseInventory {
                WarehouseId = "121", StockQuantity = -2
            });

            Assert.AreEqual(8765, _stockQuantityService.GetTotalStockQuantity(product, true));
        }
        public async Task <ProductDto> Handle(UpdateProductCommand request, CancellationToken cancellationToken)
        {
            //product
            var product = await _productService.GetProductById(request.Model.Id);

            var prevStockQuantity = product.StockQuantity;
            var prevPublished     = product.Published;

            product = request.Model.ToEntity(product);
            product.UpdatedOnUtc = DateTime.UtcNow;
            request.Model.SeName = await product.ValidateSeName(request.Model.SeName, product.Name, true, _seoSettings, _slugService, _languageService);

            product.SeName = request.Model.SeName;
            //search engine name
            await _slugService.SaveSlug(product, request.Model.SeName, "");

            //update product
            await _productService.UpdateProduct(product);

            if (product.ManageInventoryMethodId == ManageInventoryMethod.ManageStock &&
                product.BackorderModeId == BackorderMode.NoBackorders &&
                product.AllowOutOfStockSubscriptions &&
                _stockQuantityService.GetTotalStockQuantity(product) > 0 &&
                prevStockQuantity <= 0 && product.Published)
            {
                await _outOfStockSubscriptionService.SendNotificationsToSubscribers(product, "");
            }

            //activity log
            await _customerActivityService.InsertActivity("EditProduct", product.Id, _translationService.GetResource("ActivityLog.EditProduct"), product.Name);

            //raise event
            if (!prevPublished && product.Published)
            {
                await _mediator.Publish(new ProductPublishEvent(product));
            }

            if (prevPublished && !product.Published)
            {
                await _mediator.Publish(new ProductUnPublishEvent(product));
            }

            return(product.ToModel());
        }
Exemplo n.º 6
0
        public virtual async Task <IList <string> > GetStandardWarnings(Customer customer, Product product, ShoppingCartItem shoppingCartItem)
        {
            if (customer == null)
            {
                throw new ArgumentNullException(nameof(customer));
            }

            if (product == null)
            {
                throw new ArgumentNullException(nameof(product));
            }

            var warnings = new List <string>();

            //published
            if (!product.Published)
            {
                warnings.Add(_translationService.GetResource("ShoppingCart.ProductUnpublished"));
            }

            //we can't add grouped product
            if (product.ProductTypeId == ProductType.GroupedProduct)
            {
                warnings.Add("You can't add grouped product");
            }

            //ACL
            if (!_aclService.Authorize(product, customer))
            {
                warnings.Add(_translationService.GetResource("ShoppingCart.ProductUnpublished"));
            }

            //Store acl
            if (!_aclService.Authorize(product, shoppingCartItem.StoreId))
            {
                warnings.Add(_translationService.GetResource("ShoppingCart.ProductUnpublished"));
            }

            //disabled "add to cart" button
            if (shoppingCartItem.ShoppingCartTypeId == ShoppingCartType.ShoppingCart && product.DisableBuyButton)
            {
                warnings.Add(_translationService.GetResource("ShoppingCart.BuyingDisabled"));
            }

            //disabled "add to wishlist" button
            if (shoppingCartItem.ShoppingCartTypeId == ShoppingCartType.Wishlist && product.DisableWishlistButton)
            {
                warnings.Add(_translationService.GetResource("ShoppingCart.WishlistDisabled"));
            }

            //call for price
            if (shoppingCartItem.ShoppingCartTypeId == ShoppingCartType.ShoppingCart && product.CallForPrice)
            {
                warnings.Add(_translationService.GetResource("Products.CallForPrice"));
            }

            //customer entered price
            if (product.EnteredPrice)
            {
                var shoppingCartItemEnteredPrice = shoppingCartItem.EnteredPrice.HasValue ? shoppingCartItem.EnteredPrice.Value : 0;
                if (shoppingCartItemEnteredPrice < product.MinEnteredPrice ||
                    shoppingCartItemEnteredPrice > product.MaxEnteredPrice)
                {
                    double minimumCustomerEnteredPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(product.MinEnteredPrice, _workContext.WorkingCurrency);

                    double maximumCustomerEnteredPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(product.MaxEnteredPrice, _workContext.WorkingCurrency);

                    warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.CustomerEnteredPrice.RangeError"),
                                               _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, false),
                                               _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, false)));
                }
            }

            //quantity validation
            var hasQtyWarnings = false;

            if (shoppingCartItem.Quantity < product.OrderMinimumQuantity)
            {
                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.MinimumQuantity"), product.OrderMinimumQuantity));
                hasQtyWarnings = true;
            }
            if (shoppingCartItem.Quantity > product.OrderMaximumQuantity)
            {
                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.MaximumQuantity"), product.OrderMaximumQuantity));
                hasQtyWarnings = true;
            }
            var allowedQuantities = product.ParseAllowedQuantities();

            if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(shoppingCartItem.Quantity))
            {
                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.AllowedQuantities"), string.Join(", ", allowedQuantities)));
            }

            if (_shoppingCartSettings.AllowToSelectWarehouse && string.IsNullOrEmpty(shoppingCartItem.WarehouseId))
            {
                warnings.Add(_translationService.GetResource("ShoppingCart.RequiredWarehouse"));
            }

            var warehouseId = !string.IsNullOrEmpty(shoppingCartItem.WarehouseId) ? shoppingCartItem.WarehouseId : _workContext.CurrentStore?.DefaultWarehouseId;

            if (!string.IsNullOrEmpty(warehouseId))
            {
                var warehouse = await _warehouseService.GetWarehouseById(warehouseId);

                if (warehouse == null)
                {
                    warnings.Add(_translationService.GetResource("ShoppingCart.WarehouseNotExists"));
                }
            }

            var validateOutOfStock = shoppingCartItem.ShoppingCartTypeId == ShoppingCartType.ShoppingCart || !_shoppingCartSettings.AllowOutOfStockItemsToBeAddedToWishlist;

            if (validateOutOfStock && !hasQtyWarnings)
            {
                switch (product.ManageInventoryMethodId)
                {
                case ManageInventoryMethod.DontManageStock:
                {
                    //do nothing
                }
                break;

                case ManageInventoryMethod.ManageStock:
                {
                    if (product.BackorderModeId == BackorderMode.NoBackorders)
                    {
                        var qty = shoppingCartItem.Quantity;

                        qty += customer.ShoppingCartItems
                               .Where(x => x.ShoppingCartTypeId == shoppingCartItem.ShoppingCartTypeId &&
                                      x.WarehouseId == warehouseId &&
                                      x.ProductId == shoppingCartItem.ProductId &&
                                      x.StoreId == shoppingCartItem.StoreId &&
                                      x.Id != shoppingCartItem.Id)
                               .Sum(x => x.Quantity);

                        var maximumQuantityCanBeAdded = _stockQuantityService.GetTotalStockQuantity(product, warehouseId: warehouseId);
                        if (maximumQuantityCanBeAdded < qty)
                        {
                            if (maximumQuantityCanBeAdded <= 0)
                            {
                                warnings.Add(_translationService.GetResource("ShoppingCart.OutOfStock"));
                            }
                            else
                            {
                                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded));
                            }
                        }
                    }
                }
                break;

                case ManageInventoryMethod.ManageStockByBundleProducts:
                {
                    foreach (var item in product.BundleProducts)
                    {
                        var _qty = shoppingCartItem.Quantity * item.Quantity;
                        var p1   = await _productService.GetProductById(item.ProductId);

                        if (p1 != null)
                        {
                            if (p1.BackorderModeId == BackorderMode.NoBackorders)
                            {
                                if (p1.ManageInventoryMethodId == ManageInventoryMethod.ManageStock)
                                {
                                    int maximumQuantityCanBeAdded = _stockQuantityService.GetTotalStockQuantity(p1, warehouseId: warehouseId);
                                    if (maximumQuantityCanBeAdded < _qty)
                                    {
                                        warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.OutOfStock.BundleProduct"), p1.Name));
                                    }
                                }
                                if (p1.ManageInventoryMethodId == ManageInventoryMethod.ManageStockByAttributes)
                                {
                                    var combination = _productAttributeParser.FindProductAttributeCombination(p1, shoppingCartItem.Attributes);
                                    if (combination != null)
                                    {
                                        //combination exists - check stock level
                                        var stockquantity = _stockQuantityService.GetTotalStockQuantityForCombination(p1, combination, warehouseId: warehouseId);
                                        if (!combination.AllowOutOfStockOrders && stockquantity < _qty)
                                        {
                                            if (stockquantity <= 0)
                                            {
                                                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.OutOfStock.BundleProduct"), p1.Name));
                                            }
                                            else
                                            {
                                                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.QuantityExceedsStock.BundleProduct"), p1.Name, stockquantity));
                                            }
                                        }
                                    }
                                    else
                                    {
                                        warnings.Add(_translationService.GetResource("ShoppingCart.Combination.NotExist"));
                                    }
                                }
                            }
                        }
                    }
                }
                break;

                case ManageInventoryMethod.ManageStockByAttributes:
                {
                    var combination = _productAttributeParser.FindProductAttributeCombination(product, shoppingCartItem.Attributes);
                    if (combination != null)
                    {
                        //combination exists - check stock level
                        var stockquantity = _stockQuantityService.GetTotalStockQuantityForCombination(product, combination, warehouseId: warehouseId);
                        if (!combination.AllowOutOfStockOrders && stockquantity < shoppingCartItem.Quantity)
                        {
                            int maximumQuantityCanBeAdded = stockquantity;
                            if (maximumQuantityCanBeAdded <= 0)
                            {
                                warnings.Add(_translationService.GetResource("ShoppingCart.OutOfStock"));
                            }
                            else
                            {
                                warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded));
                            }
                        }
                    }
                    else
                    {
                        warnings.Add(_translationService.GetResource("ShoppingCart.Combination.NotExist"));
                    }
                }
                break;

                default:
                    break;
                }
            }

            //availability dates
            bool availableStartDateError = false;

            if (product.AvailableStartDateTimeUtc.HasValue)
            {
                DateTime now = DateTime.UtcNow;
                DateTime availableStartDateTime = DateTime.SpecifyKind(product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc);
                if (availableStartDateTime.CompareTo(now) > 0)
                {
                    warnings.Add(_translationService.GetResource("ShoppingCart.NotAvailable"));
                    availableStartDateError = true;
                }
            }
            if (product.AvailableEndDateTimeUtc.HasValue && !availableStartDateError && shoppingCartItem.ShoppingCartTypeId == ShoppingCartType.ShoppingCart)
            {
                DateTime now = DateTime.UtcNow;
                DateTime availableEndDateTime = DateTime.SpecifyKind(product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc);
                if (availableEndDateTime.CompareTo(now) < 0)
                {
                    warnings.Add(_translationService.GetResource("ShoppingCart.NotAvailable"));
                }
            }
            return(warnings);
        }
Exemplo n.º 7
0
        public virtual async Task <IActionResult> SubscribePopup(string productId, IFormCollection form)
        {
            var product = await _productService.GetProductById(productId);

            if (product == null)
            {
                throw new ArgumentException("No product found with the specified id");
            }

            var customer = _workContext.CurrentCustomer;

            string warehouseId = _shoppingCartSettings.AllowToSelectWarehouse ?
                                 form["WarehouseId"].ToString() :
                                 product.UseMultipleWarehouses ? _workContext.CurrentStore.DefaultWarehouseId :
                                 (string.IsNullOrEmpty(_workContext.CurrentStore.DefaultWarehouseId) ? product.WarehouseId : _workContext.CurrentStore.DefaultWarehouseId);

            if (!await _groupService.IsRegistered(customer))
            {
                return(Json(new
                {
                    subscribe = false,
                    buttontext = _translationService.GetResource("OutOfStockSubscriptions.NotifyMeWhenAvailable"),
                    resource = _translationService.GetResource("OutOfStockSubscriptions.OnlyRegistered")
                }));
            }

            if ((product.ManageInventoryMethodId == ManageInventoryMethod.ManageStock) &&
                product.BackorderModeId == BackorderMode.NoBackorders &&
                product.AllowOutOfStockSubscriptions &&
                _stockQuantityService.GetTotalStockQuantity(product, warehouseId: warehouseId) <= 0)
            {
                var subscription = await _outOfStockSubscriptionService
                                   .FindSubscription(customer.Id, product.Id, null, _workContext.CurrentStore.Id, warehouseId);

                if (subscription != null)
                {
                    //subscription already exists
                    //unsubscribe
                    await _outOfStockSubscriptionService.DeleteSubscription(subscription);

                    return(Json(new
                    {
                        subscribe = false,
                        buttontext = _translationService.GetResource("OutOfStockSubscriptions.NotifyMeWhenAvailable"),
                        resource = _translationService.GetResource("OutOfStockSubscriptions.Unsubscribed")
                    }));
                }

                //subscription does not exist
                //subscribe
                subscription = new OutOfStockSubscription
                {
                    CustomerId   = customer.Id,
                    ProductId    = product.Id,
                    StoreId      = _workContext.CurrentStore.Id,
                    WarehouseId  = warehouseId,
                    CreatedOnUtc = DateTime.UtcNow
                };
                await _outOfStockSubscriptionService.InsertSubscription(subscription);

                return(Json(new
                {
                    subscribe = true,
                    buttontext = _translationService.GetResource("OutOfStockSubscriptions.DeleteNotifyWhenAvailable"),
                    resource = _translationService.GetResource("OutOfStockSubscriptions.Subscribed")
                }));
            }

            if (product.ManageInventoryMethodId == ManageInventoryMethod.ManageStockByAttributes &&
                product.BackorderModeId == BackorderMode.NoBackorders &&
                product.AllowOutOfStockSubscriptions)
            {
                var attributes = await _mediator.Send(new GetParseProductAttributes()
                {
                    Product = product, Form = form
                });

                var subscription = await _outOfStockSubscriptionService
                                   .FindSubscription(customer.Id, product.Id, attributes, _workContext.CurrentStore.Id, warehouseId);

                if (subscription != null)
                {
                    //subscription already exists
                    //unsubscribe
                    await _outOfStockSubscriptionService.DeleteSubscription(subscription);

                    return(Json(new
                    {
                        subscribe = false,
                        buttontext = _translationService.GetResource("OutOfStockSubscriptions.NotifyMeWhenAvailable"),
                        resource = _translationService.GetResource("OutOfStockSubscriptions.Unsubscribed")
                    }));
                }

                //subscription does not exist
                //subscribe

                subscription = new OutOfStockSubscription
                {
                    CustomerId    = customer.Id,
                    ProductId     = product.Id,
                    Attributes    = attributes,
                    AttributeInfo = !attributes.Any() ? "" : await _productAttributeFormatter.FormatAttributes(product, attributes),
                    StoreId       = _workContext.CurrentStore.Id,
                    WarehouseId   = warehouseId,
                    CreatedOnUtc  = DateTime.UtcNow
                };

                await _outOfStockSubscriptionService.InsertSubscription(subscription);

                return(Json(new
                {
                    subscribe = true,
                    buttontext = _translationService.GetResource("OutOfStockSubscriptions.DeleteNotifyWhenAvailable"),
                    resource = _translationService.GetResource("OutOfStockSubscriptions.Subscribed")
                }));
            }

            return(Json(new
            {
                subscribe = false,
                buttontext = _translationService.GetResource("OutOfStockSubscriptions.NotifyMeWhenAvailable"),
                resource = _translationService.GetResource("OutOfStockSubscriptions.NotAllowed")
            }));
        }
        /// <summary>
        /// Adjust reserved inventory
        /// </summary>
        /// <param name="product">Product</param>
        /// <param name="quantityToChange">Quantity to increase or descrease</param>
        /// <param name="attributes">Attributes</param>
        public virtual async Task AdjustReserved(Product product, int quantityToChange, IList <CustomAttribute> attributes = null, string warehouseId = "")
        {
            if (product == null)
            {
                throw new ArgumentNullException(nameof(product));
            }

            if (quantityToChange == 0)
            {
                return;
            }

            if (product.ManageInventoryMethodId == ManageInventoryMethod.ManageStock)
            {
                var prevStockQuantity = _stockQuantityService.GetTotalStockQuantity(product, warehouseId: warehouseId);

                //update stock quantity
                if (quantityToChange < 0)
                {
                    await ReserveInventory(product, quantityToChange, warehouseId);
                }
                else
                {
                    await UnblockReservedInventory(product, quantityToChange, warehouseId);
                }

                if (product.UseMultipleWarehouses)
                {
                    product.StockQuantity    = product.ProductWarehouseInventory.Sum(x => x.StockQuantity);
                    product.ReservedQuantity = product.ProductWarehouseInventory.Sum(x => x.ReservedQuantity);

                    await UpdateStockProduct(product);
                }
                //check if minimum quantity is reached
                if (quantityToChange < 0 && product.MinStockQuantity >= _stockQuantityService.GetTotalStockQuantity(product, warehouseId: warehouseId))
                {
                    switch (product.LowStockActivityId)
                    {
                    case LowStockActivity.DisableBuyButton:
                        product.DisableBuyButton = true;
                        product.LowStock         = true;

                        await _productRepository.UpdateField(product.Id, x => x.DisableBuyButton, product.DisableBuyButton);

                        await _productRepository.UpdateField(product.Id, x => x.LowStock, product.LowStock);

                        await _productRepository.UpdateField(product.Id, x => x.UpdatedOnUtc, DateTime.UtcNow);

                        //cache
                        await _cacheBase.RemoveByPrefix(string.Format(CacheKey.PRODUCTS_BY_ID_KEY, product.Id));

                        //event notification
                        await _mediator.EntityUpdated(product);

                        break;

                    case LowStockActivity.Unpublish:
                        product.Published = false;
                        product.LowStock  = true;

                        await _productRepository.UpdateField(product.Id, x => x.Published, product.Published);

                        await _productRepository.UpdateField(product.Id, x => x.LowStock, product.LowStock);

                        await _productRepository.UpdateField(product.Id, x => x.UpdatedOnUtc, DateTime.UtcNow);

                        //cache
                        await _cacheBase.RemoveByPrefix(string.Format(CacheKey.PRODUCTS_BY_ID_KEY, product.Id));

                        //event
                        await _mediator.Publish(new ProductUnPublishEvent(product));

                        //event notification
                        await _mediator.EntityUpdated(product);

                        break;

                    default:
                        break;
                    }
                }
                //qty is increased. product is out of stock (minimum stock quantity is reached again)?
                if (_catalogSettings.PublishBackProductWhenCancellingOrders)
                {
                    var totalStock = prevStockQuantity;
                    if (quantityToChange > 0 && product.MinStockQuantity >= prevStockQuantity)
                    {
                        switch (product.LowStockActivityId)
                        {
                        case LowStockActivity.DisableBuyButton:
                            product.DisableBuyButton = false;
                            product.LowStock         = product.MinStockQuantity <= totalStock;

                            await _productRepository.UpdateField(product.Id, x => x.DisableBuyButton, product.DisableBuyButton);

                            await _productRepository.UpdateField(product.Id, x => x.LowStock, product.LowStock);

                            await _productRepository.UpdateField(product.Id, x => x.UpdatedOnUtc, DateTime.UtcNow);

                            //cache
                            await _cacheBase.RemoveByPrefix(string.Format(CacheKey.PRODUCTS_BY_ID_KEY, product.Id));

                            //event notification
                            await _mediator.EntityUpdated(product);

                            break;

                        case LowStockActivity.Unpublish:
                            product.Published = true;
                            product.LowStock  = product.MinStockQuantity < totalStock;

                            await _productRepository.UpdateField(product.Id, x => x.Published, product.Published);

                            await _productRepository.UpdateField(product.Id, x => x.LowStock, product.LowStock);

                            await _productRepository.UpdateField(product.Id, x => x.UpdatedOnUtc, DateTime.UtcNow);

                            //cache
                            await _cacheBase.RemoveByPrefix(string.Format(CacheKey.PRODUCTS_BY_ID_KEY, product.Id));

                            //event
                            await _mediator.Publish(new ProductPublishEvent(product));

                            //event notification
                            await _mediator.EntityUpdated(product);

                            break;

                        default:
                            break;
                        }
                    }
                }

                //send email notification
                if (quantityToChange < 0 && _stockQuantityService.GetTotalStockQuantity(product, warehouseId: warehouseId) < product.NotifyAdminForQuantityBelow)
                {
                    await _mediator.Send(new SendQuantityBelowStoreOwnerCommand()
                    {
                        Product = product
                    });
                }
            }

            if (attributes != null && product.ManageInventoryMethodId == ManageInventoryMethod.ManageStockByAttributes)
            {
                var combination = _productAttributeParser.FindProductAttributeCombination(product, attributes);
                if (combination != null)
                {
                    if (quantityToChange < 0)
                    {
                        await ReserveInventoryCombination(product, combination, quantityToChange, warehouseId);
                    }
                    else
                    {
                        await UnblockReservedInventoryCombination(product, combination, quantityToChange, warehouseId);
                    }

                    //send email notification
                    if (quantityToChange < 0 && combination.StockQuantity < combination.NotifyAdminForQuantityBelow)
                    {
                        await _mediator.Send(new SendQuantityBelowStoreOwnerCommand()
                        {
                            Product = product,
                            ProductAttributeCombination = combination
                        });
                    }
                }
            }

            if (product.ManageInventoryMethodId == ManageInventoryMethod.ManageStockByBundleProducts)
            {
                foreach (var item in product.BundleProducts)
                {
                    var p1 = await _productRepository.GetByIdAsync(item.ProductId);

                    if (p1 != null && (p1.ManageInventoryMethodId == ManageInventoryMethod.ManageStock || p1.ManageInventoryMethodId == ManageInventoryMethod.ManageStockByAttributes))
                    {
                        await AdjustReserved(p1, quantityToChange *item.Quantity, attributes, warehouseId);
                    }
                }
            }

            //bundled products
            var attributeValues = _productAttributeParser.ParseProductAttributeValues(product, attributes);

            foreach (var attributeValue in attributeValues)
            {
                if (attributeValue.AttributeValueTypeId == AttributeValueType.AssociatedToProduct)
                {
                    //associated product (bundle)
                    var associatedProduct = await _productRepository.GetByIdAsync(attributeValue.AssociatedProductId);

                    if (associatedProduct != null)
                    {
                        await AdjustReserved(associatedProduct, quantityToChange *attributeValue.Quantity, null, warehouseId);
                    }
                }
            }

            //event notification
            await _mediator.EntityUpdated(product);
        }