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 }); }
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; }
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); }