public virtual async Task <bool> ValidateAddToCartItemAsync(AddToCartContext ctx, ShoppingCartItem cartItem, IEnumerable <OrganizedShoppingCartItem> cartItems) { Guard.NotNull(ctx, nameof(ctx)); Guard.NotNull(cartItem, nameof(cartItem)); Guard.NotNull(cartItems, nameof(cartItems)); var warnings = new List <string>(); await ValidateProductAsync(cartItem, warnings, ctx.StoreId); await ValidateProductAttributesAsync(cartItem, cartItems, warnings); ValidateGiftCardInfo(cartItem, warnings); await ValidateRequiredProductsAsync(ctx.Product, cartItems, warnings); // Bundle and bundle items (child items) warnings if (ctx.BundleItem != null || !ctx.ChildItems.IsNullOrEmpty()) { var bundleItem = ctx.BundleItem ?? ctx.ChildItems.Select(x => x.BundleItem).FirstOrDefault(); if (bundleItem != null) { ValidateBundleItem(bundleItem, warnings); } } ctx.Warnings.AddRange(warnings); return(!warnings.Any()); }
public virtual async Task <IList <string> > CopyAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); var warnings = await AddToCartAsync(ctx); if (warnings.Count > 0 || ctx.ChildItems == null) { return(warnings); } foreach (var childItem in ctx.ChildItems) { ctx.BundleItem = childItem.BundleItem; ctx.Product = childItem.Product; ctx.Quantity = childItem.Quantity; ctx.RawAttributes = childItem.AttributeSelection.AsJson(); ctx.CustomerEnteredPrice = childItem.CustomerEnteredPrice; ctx.AutomaticallyAddRequiredProductsIfEnabled = false; warnings.AddRange(await AddToCartAsync(ctx)); } _requestCache.RemoveByPattern(CartItemsPatternKey); return(warnings); }
// was GetShoppingCartItemWarnings // TODO: (ms) (core) Have bundle item, and child item already added to context public virtual async Task <IList <string> > ValidateCartItemAsync(AddToCartContext ctx, IEnumerable <OrganizedShoppingCartItem> shoppingCart) { Guard.NotNull(ctx, nameof(ctx)); Guard.NotNull(shoppingCart, nameof(shoppingCart)); await ValidateProductAsync(ctx, shoppingCart); await ValidateProductAttributesAsync(ctx, shoppingCart); ValidateGiftCardInfo(ctx); ctx.Warnings.AddRange(await ValidateRequiredProductsAsync(ctx, shoppingCart)); // Bundle and bundle item warnings if (ctx.BundleItem != null) { ctx.Warnings.AddRange(this.ValidateBundleItem(ctx.BundleItem)); } if (ctx.ChildItems != null) { var childItems = ctx.ChildItems.Select(x => x.BundleItem).ToList(); ctx.Warnings.AddRange(ValidateBundleItems(childItems)); } return(ctx.Warnings); }
public virtual async Task <bool> CopyAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); if (!await AddToCartAsync(ctx) || ctx.ChildItems == null) { return(false); } foreach (var childItem in ctx.ChildItems) { ctx.BundleItem = childItem.BundleItem; ctx.Product = childItem.Product; ctx.Quantity = childItem.Quantity; ctx.RawAttributes = childItem.AttributeSelection.AsJson(); ctx.CustomerEnteredPrice = new(childItem.CustomerEnteredPrice, ctx.CustomerEnteredPrice.Currency); ctx.AutomaticallyAddRequiredProductsIfEnabled = false; ctx.AutomaticallyAddBundleProductsIfEnabled = false; await AddToCartAsync(ctx); } _requestCache.RemoveByPattern(CartItemsPatternKey); return(!ctx.Warnings.Any()); }
public virtual async Task <IList <string> > UpdateCartItemAsync(Customer customer, int cartItemId, int newQuantity, bool resetCheckoutData) { Guard.NotNull(customer, nameof(customer)); var warnings = new List <string>(); var cartItem = customer.ShoppingCartItems.FirstOrDefault(x => x.Id == cartItemId && x.ParentItemId == null); if (cartItem == null) { return(warnings); } if (resetCheckoutData) { customer.ResetCheckoutData(cartItem.StoreId); } if (newQuantity > 0) { var ctx = new AddToCartContext { Customer = customer, CartType = cartItem.ShoppingCartType, Product = cartItem.Product, StoreId = cartItem.StoreId, RawAttributes = cartItem.AttributeSelection.AsJson(), CustomerEnteredPrice = new Money(cartItem.CustomerEnteredPrice, _primaryCurrency), Quantity = newQuantity, AutomaticallyAddRequiredProductsIfEnabled = false, }; var cartItems = await GetCartItemsAsync(customer, cartItem.ShoppingCartType, cartItem.StoreId); if (await _cartValidator.ValidateAddToCartItemAsync(ctx, cartItem, cartItems)) { cartItem.Quantity = newQuantity; cartItem.UpdatedOnUtc = DateTime.UtcNow; _db.TryUpdate(customer); await _db.SaveChangesAsync(); } else { warnings.AddRange(ctx.Warnings); } } else { await DeleteCartItemsAsync(new[] { cartItem }, resetCheckoutData, true); } _requestCache.RemoveByPattern(CartItemsPatternKey); return(warnings); }
public virtual async Task <bool> MigrateCartAsync(Customer fromCustomer, Customer toCustomer) { Guard.NotNull(fromCustomer, nameof(fromCustomer)); Guard.NotNull(toCustomer, nameof(toCustomer)); if (fromCustomer.Id == toCustomer.Id) { return(false); } var cartItems = await OrganizeCartItemsAsync(fromCustomer.ShoppingCartItems); if (cartItems.IsNullOrEmpty()) { return(false); } var storeId = 0; foreach (var cartItem in cartItems) { if (storeId == 0) { storeId = cartItem.Item.StoreId; } var ctx = new AddToCartContext { Product = cartItem.Item.Product, RawAttributes = cartItem.Item.AttributeSelection.AsJson(), CustomerEnteredPrice = new Money(cartItem.Item.CustomerEnteredPrice, _workContext.WorkingCurrency), Quantity = cartItem.Item.Quantity, ChildItems = cartItem.ChildItems.Select(x => x.Item).ToList(), Customer = toCustomer, CartType = cartItem.Item.ShoppingCartType, StoreId = cartItem.Item.StoreId, AutomaticallyAddRequiredProductsIfEnabled = false }; if (!await CopyAsync(ctx)) { return(false); } } if (fromCustomer != null && toCustomer != null) { _eventPublisher.Publish(new MigrateShoppingCartEvent(fromCustomer, toCustomer, storeId)); } var itemsToDelete = cartItems.Select(x => x.Item); return(await DeleteCartItemsAsync(itemsToDelete) - itemsToDelete.Count() == 0); }
public virtual async Task <IList <string> > ValidateAccessPermissionsAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); var warnings = new List <string>(); // Validate access permissions if (ctx.CartType == ShoppingCartType.ShoppingCart && !await _permissionService.AuthorizeAsync(Permissions.Cart.AccessShoppingCart, ctx.Customer)) { warnings.Add(T("ShoppingCart.IsDisabled")); } else if (ctx.CartType == ShoppingCartType.Wishlist && !await _permissionService.AuthorizeAsync(Permissions.Cart.AccessWishlist, ctx.Customer)) { warnings.Add(T("Wishlist.IsDisabled")); } ctx.Warnings.AddRange(warnings); return(warnings); }
// was GetShoppingCartItemGiftCardWarnings public virtual IList <string> ValidateGiftCardInfo(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); var warnings = new List <string>(); if (!ctx.Product.IsGiftCard) { return(warnings); } var recipientName = ctx.AttributeSelection.GiftCardInfo?.RecipientName; var recipientEmail = ctx.AttributeSelection.GiftCardInfo?.RecipientEmail; var senderName = ctx.AttributeSelection.GiftCardInfo?.SenderName; var senderEmail = ctx.AttributeSelection.GiftCardInfo?.SenderEmail; if (recipientName.IsEmpty()) { warnings.Add(T("ShoppingCart.RecipientNameError")); } if (senderName.IsEmpty()) { warnings.Add(T("ShoppingCart.SenderNameError")); } if (ctx.Product.GiftCardType == GiftCardType.Virtual) { if (recipientEmail.IsEmpty() || !recipientEmail.IsEmail()) { warnings.Add(T("ShoppingCart.RecipientEmailError")); } if (senderEmail.IsEmpty() || !senderEmail.IsEmail()) { warnings.Add(T("ShoppingCart.SenderEmailError")); } } ctx.Warnings.AddRange(warnings); return(warnings); }
protected virtual async Task AddItemToCartAsync(AddToCartContext ctx) { if (ctx.Item != null) { ctx.Customer.ShoppingCartItems.Add(ctx.Item); if (!ctx.ChildItems.IsNullOrEmpty()) { foreach (var childItem in ctx.ChildItems) { childItem.ParentItemId = ctx.Item.Id; } ctx.Customer.ShoppingCartItems.AddRange(ctx.ChildItems); } _db.TryUpdate(ctx.Customer); await _db.SaveChangesAsync(); } }
public virtual async Task AddItemToCartAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); Guard.NotNull(ctx.Item, nameof(ctx.Item)); var customer = ctx.Customer ?? _workContext.CurrentCustomer; customer.ShoppingCartItems.Add(ctx.Item); await _db.SaveChangesAsync(); if (ctx.ChildItems.Any()) { foreach (var childItem in ctx.ChildItems) { childItem.ParentItemId = ctx.Item.Id; } customer.ShoppingCartItems.AddRange(ctx.ChildItems); await _db.SaveChangesAsync(); } }
public virtual async Task <bool> CopyAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); var childItems = ctx.ChildItems; ctx.ChildItems = new(); foreach (var childItem in childItems) { var childCtx = new AddToCartContext { Customer = ctx.Customer, CartType = ctx.CartType, StoreId = ctx.StoreId, BundleItem = childItem.BundleItem, Product = childItem.Product, Quantity = childItem.Quantity, RawAttributes = childItem.RawAttributes, CustomerEnteredPrice = new(childItem.CustomerEnteredPrice, ctx.CustomerEnteredPrice.Currency), ChildItems = ctx.ChildItems }; // TODO: (ms) (core) what about customer entered prices? if (!await AddToCartAsync(childCtx)) { ctx.Warnings.AddRange(childCtx.Warnings); } } if (ctx.Warnings.Any() || !await AddToCartAsync(ctx)) { return(false); } _requestCache.RemoveByPattern(CartItemsPatternKey); return(!ctx.Warnings.Any()); }
public virtual async Task <IList <string> > ValidateRequiredProductsAsync(AddToCartContext ctx, IEnumerable <OrganizedShoppingCartItem> cartItems) { Guard.NotNull(cartItems, nameof(cartItems)); Guard.NotNull(ctx, nameof(ctx)); var warnings = new List <string>(); if (!ctx.Product.RequireOtherProducts) { return(warnings); } var requiredProductIds = ctx.Product.ParseRequiredProductIds(); if (!requiredProductIds.Any()) { return(warnings); } var cartProductIds = cartItems.Select(x => x.Item.ProductId); var missingRequiredProductIds = requiredProductIds.Except(cartProductIds); if (!missingRequiredProductIds.Any()) { return(warnings); } var missingRequiredProducts = await _db.Products.GetManyAsync(missingRequiredProductIds); foreach (var product in missingRequiredProducts) { warnings.Add(T("ShoppingCart.RequiredProductWarning", product.GetLocalized(x => x.Name))); } ctx.Warnings.AddRange(warnings); return(warnings); }
// TODO: (ms) (core) TESTING! Make sure it works in any case - Works for ReOrder(). public virtual async Task <bool> AddToCartAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); // This is called when customer adds a product to cart ctx.Customer ??= _workContext.CurrentCustomer; ctx.StoreId ??= _storeContext.CurrentStore.Id; ctx.Customer.ResetCheckoutData(ctx.StoreId.Value); // Checks whether attributes have been selected if (ctx.VariantQuery != null) { // Create attribute selection from product attributes var attributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.Item.AttributeSelection); ctx.RawAttributes = ctx.Item.RawAttributes; // Check context for bundle item errors if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.RawAttributes.HasValue()) { ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); if (ctx.BundleItem != null) { return(false); } } } if (!await _cartValidator.ValidateAccessPermissionsAsync(ctx.Customer, ctx.CartType, ctx.Warnings)) { return(false); } var cartItems = await GetCartItemsAsync(ctx.Customer, ctx.CartType, ctx.StoreId.Value); // Adds required products automatically if it is enabled if (ctx.AutomaticallyAddRequiredProductsIfEnabled) { var requiredProductIds = ctx.Product.ParseRequiredProductIds(); if (requiredProductIds.Any()) { var cartProductIds = cartItems.Select(x => x.Item.ProductId); var missingRequiredProductIds = requiredProductIds.Except(cartProductIds); var missingRequiredProducts = await _db.Products.GetManyAsync(missingRequiredProductIds); foreach (var product in missingRequiredProducts) { var item = new ShoppingCartItem { CustomerEnteredPrice = ctx.CustomerEnteredPrice.Amount, RawAttributes = ctx.AttributeSelection.AsJson(), ShoppingCartType = ctx.CartType, StoreId = ctx.StoreId.Value, Quantity = ctx.Quantity, Customer = ctx.Customer, Product = product, ParentItemId = product.ParentGroupedProductId, BundleItemId = ctx.BundleItem?.Id }; await AddItemToCartAsync(new AddToCartContext { Item = item, ChildItems = ctx.ChildItems, Customer = ctx.Customer }); } } } // Checks whether required products are still missing await _cartValidator.ValidateRequiredProductsAsync(ctx.Product, cartItems, ctx.Warnings); ShoppingCartItem existingCartItem = null; if (ctx.BundleItem == null) { existingCartItem = cartItems.FindItemInCart(ctx.CartType, ctx.Product, ctx.AttributeSelection, ctx.CustomerEnteredPrice)?.Item; } // Add item to cart (if no warnings accured) if (existingCartItem != null) { // Product is already in cart, find existing item var newQuantity = ctx.Quantity + existingCartItem.Quantity; if (!await _cartValidator.ValidateAddToCartItemAsync(ctx, existingCartItem, cartItems)) { return(false); } // Update cart item existingCartItem.Quantity = newQuantity; existingCartItem.UpdatedOnUtc = DateTime.UtcNow; existingCartItem.RawAttributes = ctx.AttributeSelection.AsJson(); _db.TryUpdate(ctx.Customer); await _db.SaveChangesAsync(); } else { if (!_cartValidator.ValidateItemsMaximumCartQuantity(ctx.CartType, cartItems.Count, ctx.Warnings)) { return(false); } // Product is not in cart yet, create new item var cartItem = new ShoppingCartItem { CustomerEnteredPrice = ctx.CustomerEnteredPrice.Amount, RawAttributes = ctx.RawAttributes, ShoppingCartType = ctx.CartType, StoreId = ctx.StoreId.Value, Quantity = ctx.Quantity, Customer = ctx.Customer, Product = ctx.Product, ProductId = ctx.Product.Id, ParentItemId = null, BundleItemId = ctx.BundleItem?.Id }; if (!await _cartValidator.ValidateAddToCartItemAsync(ctx, cartItem, cartItems)) { return(false); } // Check whether the product is part of a bundle, the bundle item or just any item. // If product is no child of bundle or no bundle at all if (ctx.BundleItem == null) { // Set cart item as item for simple & bundle products, only if its not set by the caller ctx.Item ??= cartItem; } else { ctx.ChildItems.Add(cartItem); } } _requestCache.RemoveByPattern(CartItemsPatternKey); // If ctx.Product is a bundle product and the setting to automatically add bundle products is true, try to add all corresponding BundleItems. if (ctx.AutomaticallyAddBundleProductsIfEnabled && ctx.Product.ProductType == ProductType.BundledProduct && ctx.BundleItem == null && ctx.Warnings.Count == 0) { var bundleItems = await _db.ProductBundleItem .Include(x => x.Product) .Include(x => x.BundleProduct) .ApplyBundledProductsFilter(new[] { ctx.Product.Id }, true) .ToListAsync(); foreach (var bundleItem in bundleItems) { var bundleItemContext = new AddToCartContext { Warnings = new(), Item = ctx.Item, StoreId = ctx.StoreId, Customer = ctx.Customer, CartType = ctx.CartType, BundleItem = bundleItem, ChildItems = ctx.ChildItems, Product = bundleItem.Product, Quantity = bundleItem.Quantity, VariantQuery = ctx.VariantQuery, RawAttributes = ctx.RawAttributes, CustomerEnteredPrice = ctx.CustomerEnteredPrice, AutomaticallyAddRequiredProductsIfEnabled = ctx.AutomaticallyAddRequiredProductsIfEnabled, }; // If bundleItem could not be added to the shopping cart, remove child items if (!await AddToCartAsync(bundleItemContext)) { ctx.ChildItems.Clear(); break; } } } // If context is no bundleItem, add item (parent) and its children (grouped product) if ((ctx.Product.ProductType == ProductType.SimpleProduct || ctx.AutomaticallyAddBundleProductsIfEnabled) && ctx.BundleItem == null && ctx.Warnings.Count == 0) { await AddItemToCartAsync(ctx); } return(true); }
// was GetShoppingCartItemAttributeWarnings public virtual async Task <IList <string> > ValidateProductAttributesAsync(AddToCartContext ctx, IEnumerable <OrganizedShoppingCartItem> shoppingCart) { Guard.NotNull(ctx, nameof(ctx)); var warnings = new List <string>(); // Customer cannot select anything cause bundles have no attributes if (ctx.Product.ProductType == ProductType.BundledProduct || ctx.BundleItem != null && !ctx.BundleItem.BundleProduct.BundlePerItemPricing) { return(warnings); } // Get selected product variant attributes var selectedAttributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.AttributeSelection); foreach (var attribute in selectedAttributes) { if (attribute.Product == null || attribute.Product.Id != ctx.Product.Id) { warnings.Add(T("ShoppingCart.AttributeError")); ctx.Warnings.AddRange(warnings); return(warnings); } } // Get existing product variant attributes foreach (var existingAttribute in ctx.Product.ProductVariantAttributes) { if (!existingAttribute.IsRequired) { continue; } var found = false; // Selected product attributes foreach (var selectedAttribute in selectedAttributes) { if (selectedAttribute.Id == existingAttribute.Id) { var values = ctx.AttributeSelection.GetAttributeValues(selectedAttribute.Id).Select(x => x.ToString()).ToList(); found = values.Find(x => x.HasValue()).HasValue(); if (found) { break; } } } // If attribute is filtered out by bundle item, it cannot be selected by the customer found = !found && (ctx.BundleItem?.FilterAttributes ?? false) ? !ctx.BundleItem.AttributeFilters.Any(x => x.AttributeId == existingAttribute.ProductAttributeId) : true; if (!found) { warnings.Add(T( "ShoppingCart.SelectAttribute", existingAttribute.TextPrompt.IsEmpty() ? existingAttribute.ProductAttribute.GetLocalized(x => x.Name) : existingAttribute.GetLocalized(x => x.TextPrompt) )); } } if (warnings.Count > 0) { ctx.Warnings.AddRange(warnings); return(warnings); } // Checks whether there is an active selected attribute combination if (ctx.AttributeSelection.AttributesMap.Any()) { var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(ctx.Product.Id, ctx.AttributeSelection); if (combination != null && !combination.IsActive) { warnings.Add(T("ShoppingCart.NotAvailable")); } } var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(ctx.AttributeSelection); var linkedProductIds = attributeValues .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage) .Select(x => x.LinkedProductId) .Distinct(); // Get products linked to attributes var linkedProducts = await _db.Products.GetManyAsync(linkedProductIds); // Filter products which could not be loaded var notFoundProductIds = linkedProductIds.Except(linkedProducts.Select(x => x.Id)); foreach (var productId in notFoundProductIds) { warnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", productId)); } // Validate each linkedProduct, create shopping cart item from linkedProduct and run validation foreach (var attributeValue in attributeValues) { var linkedProduct = linkedProducts.FirstOrDefault(x => x.Id == attributeValue.LinkedProductId); if (linkedProduct == null) { warnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", attributeValue.LinkedProductId)); continue; } var newCtx = new AddToCartContext { Product = linkedProduct, Customer = ctx.Customer, CartType = ctx.CartType, StoreId = ctx.StoreId, Quantity = ctx.Quantity * attributeValue.Quantity }; var linkageWarnings = await ValidateCartItemAsync(newCtx, shoppingCart); foreach (var linkageWarning in linkageWarnings) { warnings.Add( T("ShoppingCart.ProductLinkageAttributeWarning", attributeValue.ProductVariantAttribute.ProductAttribute.GetLocalized(x => x.Name), attributeValue.GetLocalized(x => x.Name), linkageWarning) ); } } ctx.Warnings.AddRange(warnings); return(warnings); }
// TODO: (ms) (core) AddToCartContext needs to have bundleItem and childItems already included correctly, they get just added... public virtual async Task <IList <string> > AddToCartAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); // This is called when customer adds a product to cart var warnings = new List <string>(); ctx.Customer ??= _workContext.CurrentCustomer; ctx.StoreId ??= _storeContext.CurrentStore.Id; // TODO: (ms) (core) customerService.ResetCheckoutData() is missing //_customerService.ResetCheckoutData(customer, storeId); // Checks whether attributes have been selected // TODO: (ms) (core) VariantQuery is missing. Tries to get attributes of product. //if (ctx.VariantQuery != null) //{ // // Create attribute selection from product attributes // var attributes = _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.AttributeSelection); // //ctx.RawAttributes = ctx.VariantQuery.CreateSelectedAttributesXml(ctx.Product.Id, ctx.BundleItemId, attributes, _productAttributeParser, // //_localizationService, _downloadService, _catalogSettings, null, ctx.Warnings); // // Check context for bundle item errors // if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.RawAttributes.HasValue()) // { // ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); // if (ctx.BundleItem != null) // return ctx.Warnings; // } //} warnings.AddRange(await _cartValidator.ValidateAccessPermissionsAsync(ctx)); if (warnings.Count > 0) { return(warnings); } // TODO: (ms) (core) Call ValidateRequiredProductsAsync and check product for required products, that are not included in cart // add them if neccessary and wanted //ctx.AutomaticallyAddRequiredProductsIfEnabled var shoppingCart = await GetCartItemsAsync(ctx.Customer, ctx.CartType, ctx.StoreId.Value); warnings.AddRange(await _cartValidator.ValidateRequiredProductsAsync(ctx, shoppingCart)); //if(ctx.AutomaticallyAddRequiredProductsIfEnabled) // Add missing products..... OrganizedShoppingCartItem existingCartItem = null; if (ctx.BundleItem == null) { existingCartItem = shoppingCart.FindItemInCart(ctx.CartType, ctx.Product, ctx.AttributeSelection, ctx.CustomerEnteredPrice); } // Add item to cart (if no warnings accured) if (existingCartItem != null) { // Product is already in cart, find existing item ctx.Quantity += existingCartItem.Item.Quantity; warnings.AddRange(await _cartValidator.ValidateCartItemAsync(ctx, shoppingCart)); if (warnings.Count > 0) { return(warnings); } // Update cart item existingCartItem.Item.Quantity = ctx.Quantity; existingCartItem.Item.UpdatedOnUtc = DateTime.UtcNow; existingCartItem.Item.RawAttributes = ctx.RawAttributes; _db.TryUpdate(ctx.Customer); await _db.SaveChangesAsync(); } else { warnings.AddRange(await _cartValidator.ValidateCartItemAsync(ctx, shoppingCart)); if (warnings.Count > 0) { return(warnings); } var warning = _cartValidator.ValidateCartItemsMaximum(ctx.CartType, shoppingCart.Count); if (warning.Count > 0) { ctx.Warnings.AddRange(warning); warnings.AddRange(warning); return(warnings); } // Product is not in cart yet, create new item var cartItem = new ShoppingCartItem { CustomerEnteredPrice = ctx.CustomerEnteredPrice, RawAttributes = ctx.RawAttributes, ShoppingCartType = ctx.CartType, StoreId = ctx.StoreId.Value, Quantity = ctx.Quantity, Customer = ctx.Customer, Product = ctx.Product, ParentItemId = null, BundleItemId = ctx.BundleItem?.Id }; // If product is no bundle, add it as cartItem if (ctx.BundleItem == null) { Debug.Assert(ctx.Item == null, "Add to cart item already specified"); ctx.Item = cartItem; } else { ctx.ChildItems.Add(cartItem); } } _requestCache.RemoveByPattern(CartItemsPatternKey); // If ctx.Product is a bundle product, try adding all corresponding bundleItems if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.BundleItem == null && warnings.Count == 0) { var bundleItems = new List <ProductBundleItem>(); // Get all bundle items and add each to the cart if (_db.IsCollectionLoaded(ctx.Product, x => x.ProductBundleItems)) { bundleItems = ctx.Product.ProductBundleItems.ToList(); } else { bundleItems = await _db.ProductBundleItem .ApplyBundledProductsFilter(new[] { ctx.Product.Id }) .ToListAsync(); } foreach (var bundleItem in bundleItems) { // Try add each bundleItem to the cart warnings.AddRange( await AddToCartAsync( new AddToCartContext { Item = ctx.Item, Customer = ctx.Customer, BundleItem = bundleItem, Warnings = ctx.Warnings, CartType = ctx.CartType, StoreId = ctx.StoreId.Value, ChildItems = ctx.ChildItems, Product = bundleItem.Product, Quantity = bundleItem.Quantity, //VariantQuery = ctx.VariantQuery, AutomaticallyAddRequiredProductsIfEnabled = ctx.AutomaticallyAddRequiredProductsIfEnabled }) ); // If bundleItem could not be added to the shopping cart, remove child items as they if (warnings.Count > 0) { ctx.ChildItems.Clear(); break; } } } // If context is no bundleItem, add item (parent) and its children (grouped product) if (ctx.BundleItem == null && warnings.Count == 0) { await AddItemToCartAsync(ctx); } return(warnings); }
public virtual async Task <bool> ValidateProductAttributesAsync(ShoppingCartItem cartItem, IEnumerable <OrganizedShoppingCartItem> cartItems, IList <string> warnings) { Guard.NotNull(cartItem, nameof(cartItem)); // Check if the product is a bundle. Since bundles have no attributes, the customer has nothing to select if (cartItem.Product.ProductType == ProductType.BundledProduct || cartItem.BundleItem?.BundleProduct != null && !cartItem.BundleItem.BundleProduct.BundlePerItemPricing) { if (cartItem.RawAttributes.HasValue()) { warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); } return(true); } // Get selected product variant attributes and check for product errors var selectedAttributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(cartItem.AttributeSelection); foreach (var attribute in selectedAttributes) { if (attribute.Product == null || attribute.Product.Id != cartItem.Product.Id) { warnings.Add(T("ShoppingCart.AttributeError")); return(false); } } await _db.LoadCollectionAsync(cartItem.Product, x => x.ProductVariantAttributes, false, q => q.Include(x => x.ProductAttribute)); var currentWarnings = new List <string>(); // Get existing product variant attributes foreach (var existingAttribute in cartItem.Product.ProductVariantAttributes) { if (!existingAttribute.IsRequired) { continue; } var found = false; // Selected product attributes foreach (var selectedAttribute in selectedAttributes) { if (selectedAttribute.Id == existingAttribute.Id) { var values = cartItem.AttributeSelection.GetAttributeValues(selectedAttribute.Id) .Select(x => x.ToString()) .ToList(); found = values.Find(x => x.HasValue()).HasValue(); if (found) { break; } } } // If attribute is filtered out by bundle item, it cannot be selected by the customer if (!found && (cartItem.BundleItem?.FilterAttributes ?? false) && !cartItem.BundleItem.AttributeFilters.Any(x => x.AttributeId == existingAttribute.ProductAttributeId)) { found = true; } if (!found) { currentWarnings.Add(T( "ShoppingCart.SelectAttribute", existingAttribute.TextPrompt.IsEmpty() ? existingAttribute.ProductAttribute.GetLocalized(x => x.Name) : existingAttribute.GetLocalized(x => x.TextPrompt) )); } } if (currentWarnings.Any()) { warnings.AddRange(currentWarnings); return(false); } // Checks whether there is an active selected attribute combination if (cartItem.AttributeSelection.AttributesMap.Any()) { var combination = await _productAttributeMaterializer.FindAttributeCombinationAsync(cartItem.Product.Id, cartItem.AttributeSelection); if (combination != null && !combination.IsActive) { currentWarnings.Add(T("ShoppingCart.NotAvailable")); } } var attributeValues = await _productAttributeMaterializer.MaterializeProductVariantAttributeValuesAsync(cartItem.AttributeSelection); var linkedProductIds = attributeValues .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage) .Select(x => x.LinkedProductId) .Distinct(); // Get products linked to attributes var linkedProducts = await _db.Products.GetManyAsync(linkedProductIds); // Filter products which could not be loaded var notFoundProductIds = linkedProductIds.Except(linkedProducts.Select(x => x.Id)); foreach (var productId in notFoundProductIds) { currentWarnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", productId)); } // Validate each linkedProduct, create shopping cart item from linkedProduct and run validation foreach (var linkedProductId in linkedProductIds) { var linkedProduct = linkedProducts.FirstOrDefault(x => x.Id == linkedProductId); var linkedAttributeValue = attributeValues.FirstOrDefault(x => x.LinkedProductId == linkedProductId); if (linkedProduct == null || linkedAttributeValue == null) { currentWarnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", linkedProductId)); continue; } var item = new ShoppingCartItem { ProductId = linkedProduct.Id, Product = linkedProduct, ShoppingCartType = cartItem.ShoppingCartType, Customer = cartItem.Customer, StoreId = cartItem.StoreId, Quantity = cartItem.Quantity * linkedAttributeValue.Quantity }; var ctx = new AddToCartContext { Product = linkedProduct, Customer = cartItem.Customer, CartType = cartItem.ShoppingCartType, StoreId = cartItem.StoreId, Quantity = cartItem.Quantity * linkedAttributeValue.Quantity }; // Get product linkage warnings await ValidateAddToCartItemAsync(ctx, item, cartItems); foreach (var linkageWarning in ctx.Warnings) { currentWarnings.Add( T("ShoppingCart.ProductLinkageAttributeWarning", linkedAttributeValue.ProductVariantAttribute.ProductAttribute.GetLocalized(x => x.Name), linkedAttributeValue.GetLocalized(x => x.Name), linkageWarning) ); } } warnings.AddRange(currentWarnings); return(!currentWarnings.Any()); }
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); }
// TODO: (ms) (core) AddToCartContext needs to have bundleItem and childItems already included correctly, they get just added... public virtual async Task <IList <string> > AddToCartAsync(AddToCartContext ctx) { Guard.NotNull(ctx, nameof(ctx)); // This is called when customer adds a product to cart var warnings = new List <string>(); ctx.Customer ??= _workContext.CurrentCustomer; ctx.StoreId ??= _storeContext.CurrentStore.Id; ctx.Customer.ResetCheckoutData(ctx.StoreId.Value); // Checks whether attributes have been selected if (ctx.VariantQuery != null) { // Create attribute selection from product attributes var attributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(ctx.Item.AttributeSelection); ctx.RawAttributes = ctx.Item.AttributeSelection.AsJson(); // Check context for bundle item errors if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.RawAttributes.HasValue()) { ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); if (ctx.BundleItem != null) { return(ctx.Warnings); } } } warnings.AddRange(await _cartValidator.ValidateAccessPermissionsAsync(ctx)); if (warnings.Count > 0) { return(warnings); } var shoppingCart = await GetCartItemsAsync(ctx.Customer, ctx.CartType, ctx.StoreId.Value); // Adds required products automatically if it is enabled if (ctx.AutomaticallyAddRequiredProductsIfEnabled) { var requiredProductIds = ctx.Product.ParseRequiredProductIds(); if (requiredProductIds.Any()) { var cartProductIds = shoppingCart.Select(x => x.Item.ProductId); var missingRequiredProductIds = requiredProductIds.Except(cartProductIds); var missingRequiredProducts = await _db.Products.GetManyAsync(missingRequiredProductIds); foreach (var product in missingRequiredProducts) { var item = new ShoppingCartItem { CustomerEnteredPrice = ctx.CustomerEnteredPrice, RawAttributes = ctx.AttributeSelection.AsJson(), ShoppingCartType = ctx.CartType, StoreId = ctx.StoreId.Value, Quantity = ctx.Quantity, Customer = ctx.Customer, Product = product, ParentItemId = product.ParentGroupedProductId, BundleItemId = ctx.BundleItem?.Id }; await AddItemToCartAsync(new AddToCartContext { Item = item, ChildItems = ctx.ChildItems, Customer = ctx.Customer }); } } } // Checks whether required products are still missing warnings.AddRange(await _cartValidator.ValidateRequiredProductsAsync(ctx, shoppingCart)); OrganizedShoppingCartItem existingCartItem = null; if (ctx.BundleItem == null) { existingCartItem = shoppingCart.FindItemInCart(ctx.CartType, ctx.Product, ctx.AttributeSelection, ctx.CustomerEnteredPrice); } // Add item to cart (if no warnings accured) if (existingCartItem != null) { // Product is already in cart, find existing item ctx.Quantity += existingCartItem.Item.Quantity; warnings.AddRange(await _cartValidator.ValidateCartItemAsync(ctx, shoppingCart)); if (warnings.Count > 0) { return(warnings); } // Update cart item existingCartItem.Item.Quantity = ctx.Quantity; existingCartItem.Item.UpdatedOnUtc = DateTime.UtcNow; existingCartItem.Item.RawAttributes = ctx.AttributeSelection.AsJson(); _db.TryUpdate(ctx.Customer); await _db.SaveChangesAsync(); } else { warnings.AddRange(await _cartValidator.ValidateCartItemAsync(ctx, shoppingCart)); if (warnings.Count > 0) { return(warnings); } var warning = _cartValidator.ValidateCartItemsMaximum(ctx.CartType, shoppingCart.Count); if (warning.Count > 0) { ctx.Warnings.AddRange(warning); warnings.AddRange(warning); return(warnings); } // Product is not in cart yet, create new item var cartItem = new ShoppingCartItem { CustomerEnteredPrice = ctx.CustomerEnteredPrice, RawAttributes = ctx.AttributeSelection.AsJson(), ShoppingCartType = ctx.CartType, StoreId = ctx.StoreId.Value, Quantity = ctx.Quantity, Customer = ctx.Customer, Product = ctx.Product, ParentItemId = null, BundleItemId = ctx.BundleItem?.Id }; // If product is no bundle, add it as cartItem if (ctx.BundleItem == null) { Debug.Assert(ctx.Item == null, "Add to cart item already specified"); ctx.Item = cartItem; } else { ctx.ChildItems.Add(cartItem); } } _requestCache.RemoveByPattern(CartItemsPatternKey); // If ctx.Product is a bundle product, try adding all corresponding bundleItems if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.BundleItem == null && warnings.Count == 0) { // Get all bundle items and add each to the cart var bundleItems = _db.IsCollectionLoaded(ctx.Product, x => x.ProductBundleItems) ? ctx.Product.ProductBundleItems : await _db.ProductBundleItem .ApplyBundledProductsFilter(new[] { ctx.Product.Id }) .ToListAsync(); foreach (var bundleItem in bundleItems) { // Try add each bundleItem to the cart warnings.AddRange( await AddToCartAsync( new AddToCartContext { Item = ctx.Item, Customer = ctx.Customer, BundleItem = bundleItem, Warnings = ctx.Warnings, CartType = ctx.CartType, StoreId = ctx.StoreId.Value, ChildItems = ctx.ChildItems, Product = bundleItem.Product, Quantity = bundleItem.Quantity, VariantQuery = ctx.VariantQuery, AutomaticallyAddRequiredProductsIfEnabled = ctx.AutomaticallyAddRequiredProductsIfEnabled }) ); // If bundleItem could not be added to the shopping cart, remove child items if (warnings.Count > 0) { ctx.ChildItems.Clear(); break; } } } // If context is no bundleItem, add item (parent) and its children (grouped product) if (ctx.BundleItem == null && warnings.Count == 0) { await AddItemToCartAsync(ctx); } return(warnings); }