diff --git a/src/Business/Grand.Business.Authentication/Services/ExternalAuthenticationService.cs b/src/Business/Grand.Business.Authentication/Services/ExternalAuthenticationService.cs index e1d42be685..2cb6fd3b4a 100644 --- a/src/Business/Grand.Business.Authentication/Services/ExternalAuthenticationService.cs +++ b/src/Business/Grand.Business.Authentication/Services/ExternalAuthenticationService.cs @@ -8,6 +8,7 @@ using Grand.Business.Core.Utilities.Customers; using Grand.Data; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Stores; using Grand.Infrastructure.Extensions; using Grand.SharedKernel.Extensions; @@ -70,14 +71,15 @@ public ExternalAuthenticationService( /// Current logged-in user /// Customer of the request (a guest when nobody is logged in) /// Store identifier + /// Working language of the request /// URL to which the user will return after authentication /// Result of an authentication protected virtual async Task AuthenticateExistingUser(Customer associatedUser, - Customer currentLoggedInUser, Customer customer, string storeId, string returnUrl) + Customer currentLoggedInUser, Customer customer, string storeId, Language language, string returnUrl) { //log in guest user if (currentLoggedInUser == null) - return await LoginUser(associatedUser, customer, storeId, returnUrl); + return await LoginUser(associatedUser, customer, storeId, language, returnUrl); //account is already assigned to another user if (currentLoggedInUser.Id != associatedUser.Id) @@ -95,10 +97,11 @@ protected virtual async Task AuthenticateExistingUser(Customer as /// Authentication parameters received from external authentication method /// Customer of the request (a guest when nobody is logged in) /// Store identifier + /// Working language of the request /// URL to which the user will return after authentication /// Result of an authentication protected virtual async Task AuthenticateNewUser(Customer currentLoggedInUser, - ExternalAuthParam parameters, Customer customer, string storeId, string returnUrl) + ExternalAuthParam parameters, Customer customer, string storeId, Language language, string returnUrl) { //associate external account with logged-in user if (currentLoggedInUser != null) @@ -111,7 +114,7 @@ protected virtual async Task AuthenticateNewUser(Customer current //or try to register new user if (_customerSettings.UserRegistrationType != UserRegistrationType.Disabled) - return await RegisterNewUser(parameters, customer, storeId, returnUrl); + return await RegisterNewUser(parameters, customer, storeId, language, returnUrl); //registration is disabled return Error(["Registration is disabled"]); @@ -123,10 +126,11 @@ protected virtual async Task AuthenticateNewUser(Customer current /// Authentication parameters received from external authentication method /// Guest customer of the request; registration turns it into the new user /// Store identifier + /// Working language of the request /// URL to which the user will return after authentication /// Result of an authentication protected virtual async Task RegisterNewUser(ExternalAuthParam parameters, Customer customer, - string storeId, string returnUrl) + string storeId, Language language, string returnUrl) { var approved = _customerSettings.UserRegistrationType is UserRegistrationType.Standard @@ -147,7 +151,7 @@ _customerSettings.UserRegistrationType is UserRegistrationType.Standard await _mediator.Publish(new RegisteredByExternalMethod(customer, parameters)); //raise customer registered event - await _mediator.Publish(new CustomerRegisteredEvent(customer)); + await _mediator.Publish(new CustomerRegisteredEvent(customer, language)); //associate external account with registered user await AssociateCustomer(customer, parameters); @@ -169,13 +173,14 @@ _customerSettings.UserRegistrationType is UserRegistrationType.Standard /// User to login /// Customer of the request before the login (the guest whose cart is merged) /// Store identifier + /// Working language of the request /// URL to which the user will return after authentication /// Result of an authentication protected virtual async Task LoginUser(Customer user, Customer previousCustomer, string storeId, - string returnUrl) + Language language, string returnUrl) { //raise event - await _mediator.Publish(new CustomerLoggedInEvent(user, previousCustomer, storeId)); + await _mediator.Publish(new CustomerLoggedInEvent(user, previousCustomer, storeId, language)); //authenticate await _authenticationService.SignIn(user, false); @@ -270,14 +275,16 @@ public virtual bool AuthenticationProviderIsAvailable(string systemName, Custome /// External authentication parameters /// Customer of the request (a guest when nobody is logged in) /// Store of the request + /// Working language of the request /// URL to which the user will return after authentication /// Result of an authentication public virtual async Task Authenticate(ExternalAuthParam parameters, Customer customer, Store store, - string returnUrl = null) + Language language, string returnUrl = null) { ArgumentNullException.ThrowIfNull(parameters); ArgumentNullException.ThrowIfNull(customer); ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(language); if (!AuthenticationProviderIsAvailable(parameters.ProviderSystemName, customer, store)) return Error(["External authentication method cannot be loaded"]); @@ -288,10 +295,11 @@ public virtual async Task Authenticate(ExternalAuthParam paramete //authenticate associated user if already exists var associatedUser = await GetCustomer(parameters); if (associatedUser != null) - return await AuthenticateExistingUser(associatedUser, currentLoggedInUser, customer, store.Id, returnUrl); + return await AuthenticateExistingUser(associatedUser, currentLoggedInUser, customer, store.Id, language, + returnUrl); //or associate and authenticate new user - return await AuthenticateNewUser(currentLoggedInUser, parameters, customer, store.Id, returnUrl); + return await AuthenticateNewUser(currentLoggedInUser, parameters, customer, store.Id, language, returnUrl); } #endregion diff --git a/src/Business/Grand.Business.Authentication/Services/OidcCustomerResolver.cs b/src/Business/Grand.Business.Authentication/Services/OidcCustomerResolver.cs index 6d4c3871be..41fa7461a5 100644 --- a/src/Business/Grand.Business.Authentication/Services/OidcCustomerResolver.cs +++ b/src/Business/Grand.Business.Authentication/Services/OidcCustomerResolver.cs @@ -10,6 +10,7 @@ using Grand.Data; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Permissions; using Grand.Infrastructure.Configuration; using Grand.Mediator; @@ -146,7 +147,8 @@ await RequiresPassword(linkedCustomer, storeId)) return result; } - public virtual async Task ResolveForLogin(string sub, string email, bool emailVerified, string storeId) + public virtual async Task ResolveForLogin(string sub, string email, bool emailVerified, string storeId, + Language language) { if (string.IsNullOrEmpty(sub)) return OidcLoginResult.Refused(OidcLoginRefusal.InvalidIdentity); @@ -186,7 +188,7 @@ public virtual async Task ResolveForLogin(string sub, string em { //provider-only with automatic accounts: the provider decides who gets in, no form and no registration type if (_loginConfig is { ProviderOnly: true, AutoProvision: true }) - return await ProvisionForLogin(sub, email, emailVerified, storeId); + return await ProvisionForLogin(sub, email, emailVerified, storeId, language); var customerSettings = await _settingService.LoadSetting(storeId); if (customerSettings.UserRegistrationType == UserRegistrationType.Disabled) @@ -277,7 +279,8 @@ private async Task Register(string sub, string email, string if (existing != null) return await Usable(await _customerService.GetCustomerById(existing.CustomerId), sub, storeId); - var (outcome, customer, winner) = await CreateLinkedAccount(sub, email, storeId); + //an access token carries no working language: the registration falls back to the store's + var (outcome, customer, winner) = await CreateLinkedAccount(sub, email, storeId, null); return outcome switch { Provisioning.UserNameTaken => OidcCustomerResult.Forbidden("The user name is already taken"), Provisioning.LinkedToOtherIdentity => LinkedToOtherIdentity(customer, sub), @@ -290,14 +293,15 @@ private async Task Register(string sub, string email, string /// ProviderOnly + AutoProvision: the account of an unknown, provider-verified e-mail, created and linked at its first /// browser sign-in (the profile sync of the sign-in fills in the names) /// - private async Task ProvisionForLogin(string sub, string email, bool emailVerified, string storeId) + private async Task ProvisionForLogin(string sub, string email, bool emailVerified, string storeId, + Language language) { - var (outcome, customer, _) = await CreateLinkedAccount(sub, email, storeId); + var (outcome, customer, _) = await CreateLinkedAccount(sub, email, storeId, language); return outcome switch { Provisioning.UserNameTaken => OidcLoginResult.Refused(OidcLoginRefusal.AccountUnusable), Provisioning.LinkedToOtherIdentity => OidcLoginResult.Refused(OidcLoginRefusal.LinkedToOtherIdentity), //another request of the same user created the account first: its link answers now - Provisioning.LostRace => await ResolveForLogin(sub, email, emailVerified, storeId), + Provisioning.LostRace => await ResolveForLogin(sub, email, emailVerified, storeId, language), _ => OidcLoginResult.SignIn(customer) }; } @@ -315,7 +319,7 @@ private enum Provisioning /// e-mail validation / admin approval does not apply: access comes from the provider and from permissions an admin grants /// private async Task<(Provisioning Outcome, Customer Customer, Customer Winner)> CreateLinkedAccount(string sub, - string email, string storeId) + string email, string storeId, Language language) { //the e-mail becomes the user name - another account may hold it as its user name without that e-mail var normalizedEmail = email.ToLowerInvariant(); @@ -347,7 +351,7 @@ await _customerManagerService.RegisterCustomer(new RegistrationRequest(customer, } //only after the link is stored, so the loser of a race sends no welcome message - await _mediator.Publish(new CustomerRegisteredEvent(customer)); + await _mediator.Publish(new CustomerRegisteredEvent(customer, language)); _logger.LogInformation("Subject {Subject} registered as customer {CustomerId} (store {StoreId})", sub, customer.Id, storeId); return (Provisioning.Created, customer, null); diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AddRequiredProductsCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AddRequiredProductsCommandHandler.cs index 48167b835c..64a2d31be2 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AddRequiredProductsCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AddRequiredProductsCommandHandler.cs @@ -51,7 +51,7 @@ public async Task Handle(AddRequiredProductsCommand request, CancellationT //not in the cart if (alreadyInTheCart) continue; if (!product.AutoAddRequiredProducts) continue; - var addToCart = await _shoppingCartService.AddToCart(customer, + var addToCart = await _shoppingCartService.AddToCart(customer, request.Language, rp.Id, shoppingCartType, storeId, @@ -63,7 +63,8 @@ await Handle(new AddRequiredProductsCommand { Customer = customer, ShoppingCartType = shoppingCartType, StoreId = storeId, - Product = rp + Product = rp, + Language = request.Language }, cancellationToken); } } diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandler.cs index aaa7c116d5..0f30e2ddc7 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandler.cs @@ -1,8 +1,11 @@ using Grand.Business.Core.Commands.Checkout.Orders; +using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; +using Grand.Domain.Orders; using Grand.Mediator; namespace Grand.Business.Checkout.Commands.Handlers.Orders; @@ -14,6 +17,8 @@ public class AwardLoyaltyPointsCommandHandler : IRequestHandler GetMessage(string resourceKey, Order order) + { + var store = await _storeService.GetStoreById(order.StoreId); + var language = await _languageService.GetLanguageOrStoreDefault(order.CustomerLanguageId, store); + return language != null ? _translationService.GetResource(resourceKey, language.Id) : ""; + } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs index f8e8280201..01527bb29d 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs @@ -458,7 +458,7 @@ await _checkoutAttributeFormatter.FormatAttributes(details.CheckoutAttributes, d //validate the entire shopping cart var warnings = await _shoppingCartValidator.GetShoppingCartWarnings(details.Cart, details.CheckoutAttributes, details.Context, - true, true); + details.WorkingLanguage, true, true); if (warnings.Any()) throw new GrandException(string.Join(", ", warnings)); //validate individual cart items @@ -466,7 +466,7 @@ await _shoppingCartValidator.GetShoppingCartWarnings(details.Cart, details.Check { var product = await _productService.GetProductById(sci.ProductId); var sciWarnings = await _shoppingCartValidator.GetShoppingCartItemWarnings(details.Customer, sci, product, - new ShoppingCartValidatorOptions()); + new ShoppingCartValidatorOptions(), details.WorkingLanguage); if (sciWarnings.Any()) { var warningsSb = new StringBuilder(); diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReOrderCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReOrderCommandHandler.cs index 56dca1e45c..668fcd8a80 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReOrderCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReOrderCommandHandler.cs @@ -32,6 +32,7 @@ public ReOrderCommandHandler( public async Task> Handle(ReOrderCommand request, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(request.Order); + ArgumentNullException.ThrowIfNull(request.Language); var warnings = new List(); var customer = await _customerService.GetCustomerById(request.Order.CustomerId); @@ -42,7 +43,8 @@ public async Task> Handle(ReOrderCommand request, CancellationToke if (product != null) { if (product.ProductTypeId == ProductType.SimpleProduct) - warnings.AddRange((await _shoppingCartService.AddToCart(customer, orderItem.ProductId, + warnings.AddRange((await _shoppingCartService.AddToCart(customer, + request.Language, orderItem.ProductId, ShoppingCartType.ShoppingCart, request.Order.StoreId, orderItem.WarehouseId, orderItem.Attributes, product.EnteredPrice diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandler.cs index a9604af1c6..c0586a4469 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandler.cs @@ -1,8 +1,11 @@ using Grand.Business.Core.Commands.Checkout.Orders; +using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; +using Grand.Domain.Orders; using Grand.Mediator; namespace Grand.Business.Checkout.Commands.Handlers.Orders; @@ -14,6 +17,8 @@ public class ReduceLoyaltyPointsCommandHandler : IRequestHandler GetMessage(string resourceKey, Order order) + { + var store = await _storeService.GetStoreById(order.StoreId); + var language = await _languageService.GetLanguageOrStoreDefault(order.CustomerLanguageId, store); + return language != null ? _translationService.GetResource(resourceKey, language.Id) : ""; + } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandler.cs index 75015db674..4d995c33d2 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandler.cs @@ -1,6 +1,9 @@ using Grand.Business.Core.Commands.Checkout.Orders; +using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Domain.Orders; using Grand.Mediator; namespace Grand.Business.Checkout.Commands.Handlers.Orders; @@ -9,14 +12,20 @@ public class ReturnBackRedeemedLoyaltyPointsCommandHandler : IRequestHandler { private readonly ILoyaltyPointsService _loyaltyPointsService; + private readonly ILanguageService _languageService; + private readonly IStoreService _storeService; private readonly ITranslationService _translationService; public ReturnBackRedeemedLoyaltyPointsCommandHandler( ILoyaltyPointsService loyaltyPointsService, - ITranslationService translationService) + ITranslationService translationService, + ILanguageService languageService, + IStoreService storeService) { _loyaltyPointsService = loyaltyPointsService; _translationService = translationService; + _languageService = languageService; + _storeService = storeService; } public async Task Handle(ReturnBackRedeemedLoyaltyPointsCommand request, CancellationToken cancellationToken) @@ -30,9 +39,17 @@ public async Task Handle(ReturnBackRedeemedLoyaltyPointsCommand request, C //return back await _loyaltyPointsService.AddLoyaltyPointsHistory(request.Order.CustomerId, -request.Order.RedeemedLoyaltyPoints, request.Order.StoreId, - string.Format(_translationService.GetResource("LoyaltyPoints.Message.ReturnedForOrder"), + string.Format(await GetMessage("LoyaltyPoints.Message.ReturnedForOrder", request.Order), request.Order.OrderNumber)); return true; } + + //the message is written in the language the order was placed in, or in the store's language when that is gone + private async Task GetMessage(string resourceKey, Order order) + { + var store = await _storeService.GetStoreById(order.StoreId); + var language = await _languageService.GetLanguageOrStoreDefault(order.CustomerLanguageId, store); + return language != null ? _translationService.GetResource(resourceKey, language.Id) : ""; + } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Events/Customers/CustomerRegisteredEventHandler.cs b/src/Business/Grand.Business.Checkout/Events/Customers/CustomerRegisteredEventHandler.cs index dbcaf218d6..602ba90f6e 100644 --- a/src/Business/Grand.Business.Checkout/Events/Customers/CustomerRegisteredEventHandler.cs +++ b/src/Business/Grand.Business.Checkout/Events/Customers/CustomerRegisteredEventHandler.cs @@ -1,6 +1,8 @@ using Grand.Business.Core.Events.Customers; +using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Domain.Orders; using Grand.Mediator; @@ -8,29 +10,42 @@ namespace Grand.Business.Checkout.Events.Customers; public class CustomerRegisteredEventHandler : INotificationHandler { + private readonly ILanguageService _languageService; private readonly ILoyaltyPointsService _loyaltyPointsService; private readonly LoyaltyPointsSettings _loyaltyPointsSettings; + private readonly IStoreService _storeService; private readonly ITranslationService _translationService; public CustomerRegisteredEventHandler( ITranslationService translationService, ILoyaltyPointsService loyaltyPointsService, - LoyaltyPointsSettings loyaltyPointsSettings + LoyaltyPointsSettings loyaltyPointsSettings, + IStoreService storeService, + ILanguageService languageService ) { _translationService = translationService; _loyaltyPointsService = loyaltyPointsService; _loyaltyPointsSettings = loyaltyPointsSettings; + _storeService = storeService; + _languageService = languageService; } public async Task Handle(CustomerRegisteredEvent notification, CancellationToken cancellationToken) { //Add loyalty points for customer registration (if enabled) - if (_loyaltyPointsSettings.Enabled && - _loyaltyPointsSettings.PointsForRegistration > 0) - await _loyaltyPointsService.AddLoyaltyPointsHistory(notification.Customer.Id, - _loyaltyPointsSettings.PointsForRegistration, - notification.Customer.StoreId, - _translationService.GetResource("LoyaltyPoints.Message.EarnedForRegistration")); + if (!_loyaltyPointsSettings.Enabled || _loyaltyPointsSettings.PointsForRegistration <= 0) + return; + + //the request language; registration without a request (access-token sign-up) writes in the store's language + var language = notification.Language ?? + await _languageService.GetLanguageOrStoreDefault(null, + await _storeService.GetStoreById(notification.Customer.StoreId)); + await _loyaltyPointsService.AddLoyaltyPointsHistory(notification.Customer.Id, + _loyaltyPointsSettings.PointsForRegistration, + notification.Customer.StoreId, + language != null + ? _translationService.GetResource("LoyaltyPoints.Message.EarnedForRegistration", language.Id) + : ""); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Events/ShoppingCart/CustomerLoggedInEventHandler.cs b/src/Business/Grand.Business.Checkout/Events/ShoppingCart/CustomerLoggedInEventHandler.cs index 22e9efb41c..750c49cce1 100644 --- a/src/Business/Grand.Business.Checkout/Events/ShoppingCart/CustomerLoggedInEventHandler.cs +++ b/src/Business/Grand.Business.Checkout/Events/ShoppingCart/CustomerLoggedInEventHandler.cs @@ -17,6 +17,6 @@ public async Task Handle(CustomerLoggedInEvent notification, CancellationToken c { //migrate shopping cart await _shoppingCartService.MigrateShoppingCart(notification.PreviousCustomer, notification.Customer, - notification.StoreId, true); + notification.StoreId, notification.Language, true); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs index ba0a992d36..fff4930d34 100644 --- a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs +++ b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs @@ -7,6 +7,7 @@ using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Infrastructure.Extensions; using Grand.Mediator; @@ -170,6 +171,7 @@ public virtual async Task FindShoppingCartItem(IListProduct id /// ShoppingCartType /// Store id + /// Language the warnings are written in /// Warehouse id /// Attributes /// EnteredPrice @@ -183,7 +185,7 @@ public virtual async Task FindShoppingCartItem(IListShoppingCartValidatorOptions /// (warnings, shoppingCartItem) public virtual async Task<(IList warnings, ShoppingCartItem shoppingCartItem)> AddToCart(Customer customer, - string productId, + Language language, string productId, ShoppingCartType shoppingCartType, string storeId, string warehouseId = null, IList attributes = null, double? customerEnteredPrice = null, @@ -206,7 +208,7 @@ public virtual async Task FindShoppingCartItem(IList FindShoppingCartItem(IList InsertNewItem( - Customer customer, Product product, ShoppingCartItem shoppingCartItem, + Customer customer, Product product, ShoppingCartItem shoppingCartItem, Language language, bool automaticallyAddRequiredProductsIfEnabled) { customer.ShoppingCartItems.Add(shoppingCartItem); @@ -289,7 +292,8 @@ await _mediator.Send(new AddRequiredProductsCommand { Customer = customer, Product = product, ShoppingCartType = shoppingCartItem.ShoppingCartTypeId, - StoreId = shoppingCartItem.StoreId + StoreId = shoppingCartItem.StoreId, + Language = language }); return shoppingCartItem; @@ -302,6 +306,7 @@ await _mediator.Send(new AddRequiredProductsCommand { /// Shopping cart item identifier /// /// Attributes + /// Language the warnings are written in /// New customer entered price /// Rental start date /// Rental end date @@ -310,7 +315,7 @@ await _mediator.Send(new AddRequiredProductsCommand { /// /// /// Warnings - public virtual async Task> UpdateShoppingCartItem(Customer customer, + public virtual async Task> UpdateShoppingCartItem(Customer customer, Language language, string shoppingCartItemId, string warehouseId, IList attributes, double? customerEnteredPrice = null, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null, @@ -342,7 +347,7 @@ public virtual async Task> UpdateShoppingCartItem(Customer custome shoppingCartItem.IsGiftVoucher = product.IsGiftVoucher; //check warnings warnings.AddRange(await _shoppingCartValidator.GetShoppingCartItemWarnings(customer, shoppingCartItem, - product, new ShoppingCartValidatorOptions())); + product, new ShoppingCartValidatorOptions(), language)); if (warnings.Any()) return warnings; //if everything is OK, then update a shopping cart item await _customerService.UpdateShoppingCartItem(customer.Id, shoppingCartItem); @@ -395,12 +400,13 @@ public virtual async Task DeleteShoppingCartItem(Customer customer, ShoppingCart /// /// Store whose selected checkout attributes move along, when the source customer has selected any there /// + /// Language the items are validated in /// /// A value indicating whether to coupon codes (discount and gift voucher) should be also /// re-applied /// public virtual async Task MigrateShoppingCart(Customer fromCustomer, Customer toCustomer, string storeId, - bool includeCouponCodes) + Language language, bool includeCouponCodes) { ArgumentNullException.ThrowIfNull(fromCustomer); ArgumentNullException.ThrowIfNull(toCustomer); @@ -416,10 +422,11 @@ public virtual async Task MigrateShoppingCart(Customer fromCustomer, Customer to { var product = await _productService.GetProductById(sci.ProductId); if (product != null && - (await _shoppingCartValidator.GetRequiredProductWarnings(fromCustomer, sci, product, sci.StoreId)).Any()) + (await _shoppingCartValidator.GetRequiredProductWarnings(fromCustomer, sci, product, sci.StoreId, + language)).Any()) continue; - await AddToCart(toCustomer, sci.ProductId, sci.ShoppingCartTypeId, sci.StoreId, sci.WarehouseId, + await AddToCart(toCustomer, language, sci.ProductId, sci.ShoppingCartTypeId, sci.StoreId, sci.WarehouseId, sci.Attributes, sci.EnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, false, sci.ReservationId, sci.Parameter, sci.Duration, diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs index 47c4cfdd6b..24660dded2 100644 --- a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs +++ b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs @@ -5,6 +5,7 @@ using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Infrastructure.Validators; using Grand.Mediator; @@ -25,15 +26,16 @@ public ShoppingCartValidator( } public virtual async Task> GetStandardWarnings(Customer customer, Product product, - ShoppingCartItem shoppingCartItem) + ShoppingCartItem shoppingCartItem, Language language) { ArgumentNullException.ThrowIfNull(customer); ArgumentNullException.ThrowIfNull(product); + ArgumentNullException.ThrowIfNull(language); var warnings = new List(); var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartStandardValidatorRecord(customer, product, shoppingCartItem)); + .ValidateAsync(new ShoppingCartStandardValidatorRecord(customer, product, shoppingCartItem, language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -41,15 +43,16 @@ public virtual async Task> GetStandardWarnings(Customer customer, } public virtual async Task> GetShoppingCartItemAttributeWarnings(Customer customer, Product product, - ShoppingCartItem shoppingCartItem, bool ignoreNonCombinableAttributes = false) + ShoppingCartItem shoppingCartItem, Language language, bool ignoreNonCombinableAttributes = false) { ArgumentNullException.ThrowIfNull(product); + ArgumentNullException.ThrowIfNull(language); var warnings = new List(); var result = await _validatorFactory.GetValidator() .ValidateAsync(new ShoppingCartItemAttributeValidatorRecord(customer, product, shoppingCartItem, - ignoreNonCombinableAttributes)); + ignoreNonCombinableAttributes, language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -58,16 +61,17 @@ public virtual async Task> GetShoppingCartItemAttributeWarnings(Cu public virtual async Task> GetShoppingCartItemGiftVoucherWarnings(Customer customer, - Product product, ShoppingCartItem shoppingCartItem) + Product product, ShoppingCartItem shoppingCartItem, Language language) { ArgumentNullException.ThrowIfNull(product); + ArgumentNullException.ThrowIfNull(language); var warnings = new List(); //gift vouchers if (!product.IsGiftVoucher) return warnings; var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartGiftVoucherValidatorRecord(customer, product, shoppingCartItem)); + .ValidateAsync(new ShoppingCartGiftVoucherValidatorRecord(customer, product, shoppingCartItem, language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -75,12 +79,15 @@ public virtual async Task> GetShoppingCartItemGiftVoucherWarnings( } public virtual async Task> GetInventoryProductWarnings(Customer customer, Product product, - ShoppingCartItem shoppingCartItem) + ShoppingCartItem shoppingCartItem, Language language) { + ArgumentNullException.ThrowIfNull(language); + var warnings = new List(); var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartInventoryProductValidatorRecord(customer, product, shoppingCartItem)); + .ValidateAsync(new ShoppingCartInventoryProductValidatorRecord(customer, product, shoppingCartItem, + language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -88,12 +95,15 @@ public virtual async Task> GetInventoryProductWarnings(Customer cu } - public virtual async Task> GetAuctionProductWarning(double bid, Product product, Customer customer) + public virtual async Task> GetAuctionProductWarning(double bid, Product product, Customer customer, + Language language) { + ArgumentNullException.ThrowIfNull(language); + var warnings = new List(); if (product.ProductTypeId != ProductType.Auction) return warnings; var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartAuctionValidatorRecord(customer, product, null, bid)); + .ValidateAsync(new ShoppingCartAuctionValidatorRecord(customer, product, null, bid, language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -101,15 +111,18 @@ public virtual async Task> GetAuctionProductWarning(double bid, Pr } public virtual async Task> GetReservationProductWarnings(Customer customer, Product product, - ShoppingCartItem shoppingCartItem) + ShoppingCartItem shoppingCartItem, Language language) { + ArgumentNullException.ThrowIfNull(language); + var warnings = new List(); if (product.ProductTypeId != ProductType.Reservation) return warnings; var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartReservationProductValidatorRecord(customer, product, shoppingCartItem)); + .ValidateAsync(new ShoppingCartReservationProductValidatorRecord(customer, product, shoppingCartItem, + language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -117,16 +130,18 @@ public virtual async Task> GetReservationProductWarnings(Customer } public virtual async Task> GetShoppingCartWarnings(IList shoppingCart, - IList checkoutAttributes, CalculationContext context, bool validateCheckoutAttributes, - bool validateAmount) + IList checkoutAttributes, CalculationContext context, Language language, + bool validateCheckoutAttributes, bool validateAmount) { ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(language); var warnings = new List(); checkoutAttributes ??= new List(); var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartWarningsValidatorRecord(context.Customer, context.Store, shoppingCart)); + .ValidateAsync(new ShoppingCartWarningsValidatorRecord(context.Customer, context.Store, shoppingCart, + language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -136,7 +151,7 @@ public virtual async Task> GetShoppingCartWarnings(IList().ValidateAsync( new ShoppingCartCheckoutAttributesValidatorRecord(context.Customer, context.Store, - shoppingCart, checkoutAttributes)); + shoppingCart, checkoutAttributes, language)); if (!resultCheckoutAttributes.IsValid) warnings.AddRange(resultCheckoutAttributes.Errors.Select(x => x.ErrorMessage)); } @@ -146,13 +161,14 @@ public virtual async Task> GetShoppingCartWarnings(IList().ValidateAsync( - new ShoppingCartTotalAmountValidatorRecord(shoppingCart, context)); + new ShoppingCartTotalAmountValidatorRecord(shoppingCart, context, language)); if (!resultCheckoutAttributes.IsValid) warnings.AddRange(resultCheckoutAttributes.Errors.Select(x => x.ErrorMessage)); } //event notification - await _mediator.ShoppingCartWarningsAdd(warnings, shoppingCart, checkoutAttributes, validateCheckoutAttributes); + await _mediator.ShoppingCartWarningsAdd(warnings, shoppingCart, checkoutAttributes, validateCheckoutAttributes, + language); return warnings; } @@ -160,13 +176,15 @@ public virtual async Task> GetShoppingCartWarnings(IList> CheckCommonWarnings(Customer customer, IList currentCart, Product product, ShoppingCartType shoppingCartType, DateTime? rentalStartDate, DateTime? rentalEndDate, - int quantity, string reservationId) + int quantity, string reservationId, Language language) { + ArgumentNullException.ThrowIfNull(language); + var warnings = new List(); var result = await _validatorFactory.GetValidator().ValidateAsync( new ShoppingCartCommonWarningsValidatorRecord(customer, currentCart, - product, shoppingCartType, rentalStartDate, rentalEndDate, quantity, reservationId)); + product, shoppingCartType, rentalStartDate, rentalEndDate, quantity, reservationId, language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); @@ -175,55 +193,60 @@ public async Task> CheckCommonWarnings(Customer customer, IList> GetShoppingCartItemWarnings(Customer customer, ShoppingCartItem shoppingCartItem, - Product product, ShoppingCartValidatorOptions options) + Product product, ShoppingCartValidatorOptions options, Language language) { ArgumentNullException.ThrowIfNull(product); + ArgumentNullException.ThrowIfNull(language); var warnings = new List(); //standard properties if (options.GetStandardWarnings) - warnings.AddRange(await GetStandardWarnings(customer, product, shoppingCartItem)); + warnings.AddRange(await GetStandardWarnings(customer, product, shoppingCartItem, language)); //inventory properties if (options.GetInventoryWarnings) - warnings.AddRange(await GetInventoryProductWarnings(customer, product, shoppingCartItem)); + warnings.AddRange(await GetInventoryProductWarnings(customer, product, shoppingCartItem, language)); //selected attributes if (options.GetAttributesWarnings) - warnings.AddRange(await GetShoppingCartItemAttributeWarnings(customer, product, shoppingCartItem)); + warnings.AddRange( + await GetShoppingCartItemAttributeWarnings(customer, product, shoppingCartItem, language)); //gift vouchers if (options.GetGiftVoucherWarnings) - warnings.AddRange(await GetShoppingCartItemGiftVoucherWarnings(customer, product, shoppingCartItem)); + warnings.AddRange( + await GetShoppingCartItemGiftVoucherWarnings(customer, product, shoppingCartItem, language)); //required products if (options.GetRequiredProductWarnings) warnings.AddRange(await GetRequiredProductWarnings(customer, shoppingCartItem, product, - shoppingCartItem.StoreId)); + shoppingCartItem.StoreId, language)); //reservation products if (options.GetReservationWarnings) - warnings.AddRange(await GetReservationProductWarnings(customer, product, shoppingCartItem)); + warnings.AddRange(await GetReservationProductWarnings(customer, product, shoppingCartItem, language)); //event notification - await _mediator.ShoppingCartItemWarningsAdded(warnings, customer, shoppingCartItem, product); + await _mediator.ShoppingCartItemWarningsAdded(warnings, customer, shoppingCartItem, product, language); return warnings; } public virtual async Task> GetRequiredProductWarnings(Customer customer, - ShoppingCartItem shoppingCartItem, Product product, string storeId) + ShoppingCartItem shoppingCartItem, Product product, string storeId, Language language) { ArgumentNullException.ThrowIfNull(customer); ArgumentNullException.ThrowIfNull(product); + ArgumentNullException.ThrowIfNull(language); var warnings = new List(); if (!product.RequireOtherProducts) return warnings; var result = await _validatorFactory.GetValidator() - .ValidateAsync(new ShoppingCartRequiredProductValidatorRecord(customer, storeId, product, shoppingCartItem)); + .ValidateAsync(new ShoppingCartRequiredProductValidatorRecord(customer, storeId, product, shoppingCartItem, + language)); if (!result.IsValid) warnings.AddRange(result.Errors.Select(x => x.ErrorMessage)); return warnings; diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartAuctionValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartAuctionValidator.cs index 2543c9b174..85565c7b2e 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartAuctionValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartAuctionValidator.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Checkout.Validators; @@ -10,7 +11,8 @@ public record ShoppingCartAuctionValidatorRecord( Customer Customer, Product Product, ShoppingCartItem ShoppingCartItem, - double Bid); + double Bid, + Language Language); public class ShoppingCartAuctionValidator : AbstractValidator { @@ -19,13 +21,13 @@ public ShoppingCartAuctionValidator(ITranslationService translationService) RuleFor(x => x).Custom((value, context) => { if (value.Bid <= value.Product.HighestBid || value.Bid <= value.Product.StartPrice) - context.AddFailure(translationService.GetResource("ShoppingCart.BidMustBeHigher")); + context.AddFailure(translationService.GetResource("ShoppingCart.BidMustBeHigher", value.Language.Id)); if (!value.Product.AvailableEndDateTimeUtc.HasValue) - context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable")); + context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable", value.Language.Id)); if (value.Product.AvailableEndDateTimeUtc < DateTime.UtcNow) - context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable")); + context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable", value.Language.Id)); }); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCheckoutAttributesValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCheckoutAttributesValidator.cs index 8ff102e60a..4b543f98db 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCheckoutAttributesValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCheckoutAttributesValidator.cs @@ -5,6 +5,7 @@ using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Stores; @@ -14,7 +15,8 @@ public record ShoppingCartCheckoutAttributesValidatorRecord( Customer Customer, Store Store, IList ShoppingCarts, - IList CheckoutAttributes); + IList CheckoutAttributes, + Language Language); public class ShoppingCartCheckoutAttributesValidator : AbstractValidator { @@ -50,7 +52,7 @@ await checkoutAttributeService.GetAllCheckoutAttributes(CatalogAccess.For(value. //if not found if (!found) context.AddFailure(a2.TextPrompt ?? - string.Format(translationService.GetResource("ShoppingCart.SelectAttribute"), + string.Format(translationService.GetResource("ShoppingCart.SelectAttribute", value.Language.Id), a2.Name)); } @@ -69,7 +71,7 @@ await checkoutAttributeService.GetAllCheckoutAttributes(CatalogAccess.For(value. if (ca.ValidationMinLength.Value > enteredTextLength) context.AddFailure(string.Format( - translationService.GetResource("ShoppingCart.TextBoxMinimumLength"), ca.Name, + translationService.GetResource("ShoppingCart.TextBoxMinimumLength", value.Language.Id), ca.Name, ca.ValidationMinLength.Value)); } @@ -84,7 +86,7 @@ await checkoutAttributeService.GetAllCheckoutAttributes(CatalogAccess.For(value. if (ca.ValidationMaxLength.Value < enteredTextLength) context.AddFailure(string.Format( - translationService.GetResource("ShoppingCart.TextBoxMaximumLength"), ca.Name, + translationService.GetResource("ShoppingCart.TextBoxMaximumLength", value.Language.Id), ca.Name, ca.ValidationMaxLength.Value)); } } diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCommonWarningsValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCommonWarningsValidator.cs index 43bd391384..b3d481a60d 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCommonWarningsValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartCommonWarningsValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Security; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Permissions; @@ -16,7 +17,8 @@ public record ShoppingCartCommonWarningsValidatorRecord( DateTime? RentalStartDate, DateTime? RentalEndDate, int Quantity, - string ReservationId); + string ReservationId, + Language Language); public class ShoppingCartCommonWarningsValidator : AbstractValidator { @@ -32,7 +34,7 @@ public ShoppingCartCommonWarningsValidator(ITranslationService translationServic { if (value.ShoppingCarts.Count >= shoppingCartSettings.MaximumShoppingCartItems) context.AddFailure(string.Format( - translationService.GetResource("ShoppingCart.MaximumShoppingCartItems"), + translationService.GetResource("ShoppingCart.MaximumShoppingCartItems", value.Language.Id), shoppingCartSettings.MaximumShoppingCartItems)); } break; @@ -40,7 +42,7 @@ public ShoppingCartCommonWarningsValidator(ITranslationService translationServic { if (value.ShoppingCarts.Count >= shoppingCartSettings.MaximumWishlistItems) context.AddFailure(string.Format( - translationService.GetResource("ShoppingCart.MaximumWishlistItems"), + translationService.GetResource("ShoppingCart.MaximumWishlistItems", value.Language.Id), shoppingCartSettings.MaximumWishlistItems)); } break; @@ -59,7 +61,7 @@ public ShoppingCartCommonWarningsValidator(ITranslationService translationServic } if (value.Quantity <= 0) - context.AddFailure(translationService.GetResource("ShoppingCart.QuantityShouldPositive")); + context.AddFailure(translationService.GetResource("ShoppingCart.QuantityShouldPositive", value.Language.Id)); }); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartGiftVoucherValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartGiftVoucherValidator.cs index a98e725c34..f156b21ae5 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartGiftVoucherValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartGiftVoucherValidator.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.SharedKernel.Extensions; @@ -10,7 +11,8 @@ namespace Grand.Business.Checkout.Validators; public record ShoppingCartGiftVoucherValidatorRecord( Customer Customer, Product Product, - ShoppingCartItem ShoppingCartItem); + ShoppingCartItem ShoppingCartItem, + Language Language); public class ShoppingCartGiftVoucherValidator : AbstractValidator { @@ -23,21 +25,21 @@ public ShoppingCartGiftVoucherValidator(ITranslationService translationService) out var giftVoucherSenderName, out var giftVoucherSenderEmail, out _); if (string.IsNullOrEmpty(giftVoucherRecipientName)) - context.AddFailure(translationService.GetResource("ShoppingCart.RecipientNameError")); + context.AddFailure(translationService.GetResource("ShoppingCart.RecipientNameError", value.Language.Id)); if (value.Product.GiftVoucherTypeId == GiftVoucherType.Virtual) //validate for virtual gift vouchers only if (string.IsNullOrEmpty(giftVoucherRecipientEmail) || !CommonHelper.IsValidEmail(giftVoucherRecipientEmail)) - context.AddFailure(translationService.GetResource("ShoppingCart.RecipientEmailError")); + context.AddFailure(translationService.GetResource("ShoppingCart.RecipientEmailError", value.Language.Id)); if (string.IsNullOrEmpty(giftVoucherSenderName)) - context.AddFailure(translationService.GetResource("ShoppingCart.SenderNameError")); + context.AddFailure(translationService.GetResource("ShoppingCart.SenderNameError", value.Language.Id)); if (value.Product.GiftVoucherTypeId != GiftVoucherType.Virtual) return; //validate for virtual gift vouchers only if (string.IsNullOrEmpty(giftVoucherSenderEmail) || !CommonHelper.IsValidEmail(giftVoucherSenderEmail)) - context.AddFailure(translationService.GetResource("ShoppingCart.SenderEmailError")); + context.AddFailure(translationService.GetResource("ShoppingCart.SenderEmailError", value.Language.Id)); }); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartInventoryProductValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartInventoryProductValidator.cs index 6373cdb789..fe4b13b822 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartInventoryProductValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartInventoryProductValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Checkout.Validators; @@ -10,7 +11,8 @@ namespace Grand.Business.Checkout.Validators; public record ShoppingCartInventoryProductValidatorRecord( Customer Customer, Product Product, - ShoppingCartItem ShoppingCartItem); + ShoppingCartItem ShoppingCartItem, + Language Language); public class ShoppingCartInventoryProductValidator : AbstractValidator { @@ -23,25 +25,25 @@ public ShoppingCartInventoryProductValidator(ITranslationService translationServ var hasQtyWarnings = false; if (value.ShoppingCartItem.Quantity < value.Product.OrderMinimumQuantity) { - context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.MinimumQuantity"), + context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.MinimumQuantity", value.Language.Id), value.Product.OrderMinimumQuantity)); hasQtyWarnings = true; } if (value.ShoppingCartItem.Quantity > value.Product.OrderMaximumQuantity) { - context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.MaximumQuantity"), + context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.MaximumQuantity", value.Language.Id), value.Product.OrderMaximumQuantity)); hasQtyWarnings = true; } var allowedQuantities = value.Product.ParseAllowedQuantities(); if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(value.ShoppingCartItem.Quantity)) - context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.AllowedQuantities"), + context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.AllowedQuantities", value.Language.Id), string.Join(", ", allowedQuantities))); if (shoppingCartSettings.AllowToSelectWarehouse && string.IsNullOrEmpty(value.ShoppingCartItem.WarehouseId)) - context.AddFailure(translationService.GetResource("ShoppingCart.RequiredWarehouse")); + context.AddFailure(translationService.GetResource("ShoppingCart.RequiredWarehouse", value.Language.Id)); var warehouseId = !string.IsNullOrEmpty(value.ShoppingCartItem.WarehouseId) ? value.ShoppingCartItem.WarehouseId @@ -73,9 +75,9 @@ public ShoppingCartInventoryProductValidator(ITranslationService translationServ stockQuantityService.GetTotalStockQuantity(value.Product, warehouseId: warehouseId); if (maximumQuantityCanBeAdded < qty) context.AddFailure(maximumQuantityCanBeAdded <= 0 - ? translationService.GetResource("ShoppingCart.OutOfStock") + ? translationService.GetResource("ShoppingCart.OutOfStock", value.Language.Id) : string.Format( - translationService.GetResource("ShoppingCart.QuantityExceedsStock"), + translationService.GetResource("ShoppingCart.QuantityExceedsStock", value.Language.Id), maximumQuantityCanBeAdded)); } } @@ -93,7 +95,7 @@ public ShoppingCartInventoryProductValidator(ITranslationService translationServ stockQuantityService.GetTotalStockQuantity(p1, warehouseId: warehouseId); if (maximumQuantityCanBeAdded < qty) context.AddFailure(string.Format( - translationService.GetResource("ShoppingCart.OutOfStock.BundleProduct"), + translationService.GetResource("ShoppingCart.OutOfStock.BundleProduct", value.Language.Id), p1.Name)); } @@ -110,15 +112,15 @@ public ShoppingCartInventoryProductValidator(ITranslationService translationServ context.AddFailure(stockQuantity <= 0 ? string.Format( translationService.GetResource( - "ShoppingCart.OutOfStock.BundleProduct"), p1.Name) + "ShoppingCart.OutOfStock.BundleProduct", value.Language.Id), p1.Name) : string.Format( translationService.GetResource( - "ShoppingCart.QuantityExceedsStock.BundleProduct"), p1.Name, + "ShoppingCart.QuantityExceedsStock.BundleProduct", value.Language.Id), p1.Name, stockQuantity)); } else { - context.AddFailure(translationService.GetResource("ShoppingCart.Combination.NotExist")); + context.AddFailure(translationService.GetResource("ShoppingCart.Combination.NotExist", value.Language.Id)); } } } @@ -135,14 +137,14 @@ public ShoppingCartInventoryProductValidator(ITranslationService translationServ warehouseId: warehouseId); if (!combination.AllowOutOfStockOrders && stockQuantity < value.ShoppingCartItem.Quantity) context.AddFailure(stockQuantity <= 0 - ? translationService.GetResource("ShoppingCart.OutOfStock") + ? translationService.GetResource("ShoppingCart.OutOfStock", value.Language.Id) : string.Format( - translationService.GetResource("ShoppingCart.QuantityExceedsStock"), + translationService.GetResource("ShoppingCart.QuantityExceedsStock", value.Language.Id), stockQuantity)); } else { - context.AddFailure(translationService.GetResource("ShoppingCart.Combination.NotExist")); + context.AddFailure(translationService.GetResource("ShoppingCart.Combination.NotExist", value.Language.Id)); } } break; diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartItemAttributeValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartItemAttributeValidator.cs index 4473ef08f4..bbf8cff53c 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartItemAttributeValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartItemAttributeValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.SharedKernel.Extensions; @@ -12,7 +13,8 @@ public record ShoppingCartItemAttributeValidatorRecord( Customer Customer, Product Product, ShoppingCartItem ShoppingCartItem, - bool IgnoreNonCombinableAttributes); + bool IgnoreNonCombinableAttributes, + Language Language); public class ShoppingCartItemAttributeValidator : AbstractValidator { @@ -73,7 +75,7 @@ await productAttributeService.GetProductAttributeById(productAttributeMapping var attributeName = productAttribute.Name; var attributeValueName = attributeValue.Name; warnings.Add(string.Format( - translationService.GetResource("ShoppingCart.AssociatedAttributeWarning"), + translationService.GetResource("ShoppingCart.AssociatedAttributeWarning", value.Language.Id), attributeName, attributeValueName, associatedProductWarning)); } } @@ -157,7 +159,7 @@ where attributeValuesStr.Any(str1 => !string.IsNullOrEmpty(str1.Trim())) { var notFoundWarning = !string.IsNullOrEmpty(a2.TextPrompt) ? a2.TextPrompt - : string.Format(_translationService.GetResource("ShoppingCart.SelectAttribute"), paa.Name); + : string.Format(_translationService.GetResource("ShoppingCart.SelectAttribute", value.Language.Id), paa.Name); warnings.Add(notFoundWarning); } @@ -201,7 +203,7 @@ where attributeValuesStr.Any(str1 => !string.IsNullOrEmpty(str1.Trim())) { var productAttribute = await _productAttributeService.GetProductAttributeById(pam.ProductAttributeId); - warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.TextboxMinimumLength"), + warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.TextboxMinimumLength", value.Language.Id), productAttribute.Name, pam.ValidationMinLength.Value)); } } @@ -217,7 +219,7 @@ where attributeValuesStr.Any(str1 => !string.IsNullOrEmpty(str1.Trim())) if (pam.ValidationMaxLength.Value >= enteredTextLength) continue; var productAttribute = await _productAttributeService.GetProductAttributeById(pam.ProductAttributeId); - warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.TextboxMaximumLength"), + warnings.Add(string.Format(_translationService.GetResource("ShoppingCart.TextboxMaximumLength", value.Language.Id), productAttribute.Name, pam.ValidationMaxLength.Value)); } } diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartRequiredProductValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartRequiredProductValidator.cs index cc455c493b..fdf6a45f79 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartRequiredProductValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartRequiredProductValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Checkout.Validators; @@ -11,7 +12,8 @@ public record ShoppingCartRequiredProductValidatorRecord( Customer Customer, string StoreId, Product Product, - ShoppingCartItem ShoppingCartItem); + ShoppingCartItem ShoppingCartItem, + Language Language); public class ShoppingCartRequiredProductValidator : AbstractValidator { @@ -37,7 +39,7 @@ public ShoppingCartRequiredProductValidator(ITranslationService translationServi let alreadyInTheCart = cart.Any(sci => sci.ProductId == rp.Id) where !alreadyInTheCart select rp) - context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.RequiredProductWarning"), + context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.RequiredProductWarning", value.Language.Id), rp.Name)); }); } diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartReservationProductValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartReservationProductValidator.cs index 4169b68dd9..fd918639d1 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartReservationProductValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartReservationProductValidator.cs @@ -4,6 +4,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Checkout.Validators; @@ -11,7 +12,8 @@ namespace Grand.Business.Checkout.Validators; public record ShoppingCartReservationProductValidatorRecord( Customer Customer, Product Product, - ShoppingCartItem ShoppingCartItem); + ShoppingCartItem ShoppingCartItem, + Language Language); public class ShoppingCartReservationProductValidator : AbstractValidator { @@ -25,7 +27,7 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe var reservations = await productReservationService.GetCustomerReservationsHelpers(value.Customer.Id); if (value.Customer.ShoppingCartItems.All(x => x.Id != value.ShoppingCartItem.Id)) if (reservations.Any(x => x.ReservationId == value.ShoppingCartItem.ReservationId)) - context.AddFailure(translationService.GetResource("ShoppingCart.AlreadyReservation")); + context.AddFailure(translationService.GetResource("ShoppingCart.AlreadyReservation", value.Language.Id)); } if (value.ShoppingCartItem.RentalStartDateUtc.HasValue && value.ShoppingCartItem.RentalEndDateUtc.HasValue) @@ -46,7 +48,7 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe if (!canBeBook) { context.AddFailure( - translationService.GetResource("ShoppingCart.Reservation.NoFreeReservationsInThisPeriod")); + translationService.GetResource("ShoppingCart.Reservation.NoFreeReservationsInThisPeriod", value.Language.Id)); return; } } @@ -54,7 +56,7 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe if (string.IsNullOrEmpty(value.ShoppingCartItem.ReservationId) && value.Product.IntervalUnitId != IntervalUnit.Day) { - context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.NoReservationFound")); + context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.NoReservationFound", value.Language.Id)); return; } @@ -64,12 +66,12 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe await productReservationService.GetProductReservation(value.ShoppingCartItem.ReservationId); if (reservation == null) { - context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.ReservationDeleted")); + context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.ReservationDeleted", value.Language.Id)); } else { if (!string.IsNullOrEmpty(reservation.OrderId)) - context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.AlreadyReserved")); + context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.AlreadyReserved", value.Language.Id)); } } else @@ -77,7 +79,7 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe if (!(value.ShoppingCartItem.RentalStartDateUtc.HasValue && value.ShoppingCartItem.RentalEndDateUtc.HasValue)) { - context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.ChooseBothDates")); + context.AddFailure(translationService.GetResource("ShoppingCart.Reservation.ChooseBothDates", value.Language.Id)); } else { @@ -87,7 +89,7 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe value.ShoppingCartItem.RentalEndDateUtc.Value) context.AddFailure( translationService.GetResource( - "ShoppingCart.Reservation.EndDateMustBeLaterThanStartDate")); + "ShoppingCart.Reservation.EndDateMustBeLaterThanStartDate", value.Language.Id)); } else { @@ -95,14 +97,14 @@ public ShoppingCartReservationProductValidator(ITranslationService translationSe value.ShoppingCartItem.RentalEndDateUtc.Value) context.AddFailure( translationService.GetResource( - "ShoppingCart.Reservation.EndDateMustBeLaterThanStartDate")); + "ShoppingCart.Reservation.EndDateMustBeLaterThanStartDate", value.Language.Id)); } if (value.ShoppingCartItem.RentalStartDateUtc.Value < DateTime.UtcNow || value.ShoppingCartItem.RentalEndDateUtc.Value < DateTime.UtcNow) context.AddFailure( translationService.GetResource( - "ShoppingCart.Reservation.ReservationDatesMustBeLaterThanToday")); + "ShoppingCart.Reservation.ReservationDatesMustBeLaterThanToday", value.Language.Id)); if (value.Customer.ShoppingCartItems.Any(x => x.Id == value.ShoppingCartItem.Id)) { @@ -111,7 +113,7 @@ await productReservationService.GetCustomerReservationsHelperBySciId(value.Shopp .Id); if (!reserved.Any()) context.AddFailure( - translationService.GetResource("ShoppingCart.Reservation.ReservationNotExists")); + translationService.GetResource("ShoppingCart.Reservation.ReservationNotExists", value.Language.Id)); else foreach (var item in reserved) { @@ -120,13 +122,13 @@ await productReservationService.GetCustomerReservationsHelperBySciId(value.Shopp if (reservation == null) { context.AddFailure( - translationService.GetResource("ShoppingCart.Reservation.ReservationDeleted")); + translationService.GetResource("ShoppingCart.Reservation.ReservationDeleted", value.Language.Id)); break; } if (string.IsNullOrEmpty(reservation.OrderId)) continue; context.AddFailure( - translationService.GetResource("ShoppingCart.Reservation.AlreadyReserved")); + translationService.GetResource("ShoppingCart.Reservation.AlreadyReserved", value.Language.Id)); break; } } diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartStandardValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartStandardValidator.cs index 89cdc96c9f..5e20d81224 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartStandardValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartStandardValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Security; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Checkout.Validators; @@ -10,7 +11,8 @@ namespace Grand.Business.Checkout.Validators; public record ShoppingCartStandardValidatorRecord( Customer Customer, Product Product, - ShoppingCartItem ShoppingCartItem); + ShoppingCartItem ShoppingCartItem, + Language Language); public class ShoppingCartStandardValidator : AbstractValidator { @@ -19,7 +21,7 @@ public ShoppingCartStandardValidator(ITranslationService translationService, IAc RuleFor(x => x).Custom((value, context) => { if (!value.Product.Published) - context.AddFailure(translationService.GetResource("ShoppingCart.ProductUnpublished")); + context.AddFailure(translationService.GetResource("ShoppingCart.ProductUnpublished", value.Language.Id)); //we can't add grouped value.Product if (value.Product.ProductTypeId == ProductType.GroupedProduct) @@ -27,27 +29,27 @@ public ShoppingCartStandardValidator(ITranslationService translationService, IAc //ACL if (!aclService.Authorize(value.Product, value.Customer)) - context.AddFailure(translationService.GetResource("ShoppingCart.ProductUnpublished")); + context.AddFailure(translationService.GetResource("ShoppingCart.ProductUnpublished", value.Language.Id)); //Store acl if (!aclService.Authorize(value.Product, value.ShoppingCartItem.StoreId)) - context.AddFailure(translationService.GetResource("ShoppingCart.ProductUnpublished")); + context.AddFailure(translationService.GetResource("ShoppingCart.ProductUnpublished", value.Language.Id)); switch (value.ShoppingCartItem.ShoppingCartTypeId) { //disabled "add to cart" button case ShoppingCartType.ShoppingCart when value.Product.DisableBuyButton: - context.AddFailure(translationService.GetResource("ShoppingCart.BuyingDisabled")); + context.AddFailure(translationService.GetResource("ShoppingCart.BuyingDisabled", value.Language.Id)); break; //disabled "add to wishlist" button case ShoppingCartType.Wishlist when value.Product.DisableWishlistButton: - context.AddFailure(translationService.GetResource("ShoppingCart.WishlistDisabled")); + context.AddFailure(translationService.GetResource("ShoppingCart.WishlistDisabled", value.Language.Id)); break; } //call for price if (value.ShoppingCartItem.ShoppingCartTypeId == ShoppingCartType.ShoppingCart && - value.Product.CallForPrice) context.AddFailure(translationService.GetResource("Products.CallForPrice")); + value.Product.CallForPrice) context.AddFailure(translationService.GetResource("Products.CallForPrice", value.Language.Id)); //customer entered price if (value.Product.EnteredPrice) @@ -56,7 +58,7 @@ public ShoppingCartStandardValidator(ITranslationService translationService, IAc if (shoppingCartItemEnteredPrice < value.Product.MinEnteredPrice || shoppingCartItemEnteredPrice > value.Product.MaxEnteredPrice) context.AddFailure(string.Format( - translationService.GetResource("ShoppingCart.CustomerEnteredPrice.RangeError"), + translationService.GetResource("ShoppingCart.CustomerEnteredPrice.RangeError", value.Language.Id), value.Product.MinEnteredPrice, value.Product.MaxEnteredPrice)); } @@ -70,7 +72,7 @@ public ShoppingCartStandardValidator(ITranslationService translationService, IAc DateTime.SpecifyKind(value.Product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc); if (availableStartDateTime.CompareTo(now) > 0) { - context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable")); + context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable", value.Language.Id)); availableStartDateError = true; } } @@ -82,7 +84,7 @@ public ShoppingCartStandardValidator(ITranslationService translationService, IAc var availableEndDateTime = DateTime.SpecifyKind(value.Product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc); if (availableEndDateTime.CompareTo(now) < 0) - context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable")); + context.AddFailure(translationService.GetResource("ShoppingCart.NotAvailable", value.Language.Id)); } }); } diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs index ae3966a703..18959add4f 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs @@ -4,13 +4,15 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Utilities.Checkout; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Checkout.Validators; public record ShoppingCartTotalAmountValidatorRecord( IList Cart, - CalculationContext Context); + CalculationContext Context, + Language Language); public class ShoppingCartTotalAmountValidator : AbstractValidator { @@ -40,9 +42,9 @@ public ShoppingCartTotalAmountValidator( if (shoppingCartTotalBase.HasValue && (shoppingCartTotalBase.Value < minOrderAmount || shoppingCartTotalBase.Value > maxOrderAmount)) - context.AddFailure(translationService.GetResource("Checkout.MinMaxOrderTotalAmount")); + context.AddFailure(translationService.GetResource("Checkout.MinMaxOrderTotalAmount", value.Language.Id)); else if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < orderSettings.MinOrderTotalAmount) - context.AddFailure(translationService.GetResource("Checkout.MinMaxOrderTotalAmount")); + context.AddFailure(translationService.GetResource("Checkout.MinMaxOrderTotalAmount", value.Language.Id)); //subtotal var (_, _, subTotalWithoutDiscount, _, _) = @@ -52,7 +54,7 @@ public ShoppingCartTotalAmountValidator( var minOrderSubtotalAmount = await currencyService.ConvertFromPrimaryStoreCurrency(orderSettings.MinOrderSubtotalAmount, value.Context.Currency); - context.AddFailure(string.Format(translationService.GetResource("Checkout.MinOrderSubtotalAmount"), + context.AddFailure(string.Format(translationService.GetResource("Checkout.MinOrderSubtotalAmount", value.Language.Id), priceFormatter.FormatPrice(minOrderSubtotalAmount, value.Context.Currency))); } }); diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartWarningsValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartWarningsValidator.cs index 78e747b0d8..738f8f716d 100644 --- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartWarningsValidator.cs +++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartWarningsValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Stores; @@ -11,7 +12,8 @@ namespace Grand.Business.Checkout.Validators; public record ShoppingCartWarningsValidatorRecord( Customer Customer, Store Store, - IList ShoppingCarts); + IList ShoppingCarts, + Language Language); public class ShoppingCartWarningsValidator : AbstractValidator { @@ -31,7 +33,7 @@ public ShoppingCartWarningsValidator(ITranslationService translationService, IPr var product = await productService.GetProductById(sci.ProductId); if (product == null) { - context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.CannotLoadProduct"), + context.AddFailure(string.Format(translationService.GetResource("ShoppingCart.CannotLoadProduct", value.Language.Id), sci.ProductId)); return; } @@ -56,11 +58,11 @@ public ShoppingCartWarningsValidator(ITranslationService translationService, IPr //don't mix standard and recurring products if (hasStandardProducts && hasRecurringProducts) - context.AddFailure(translationService.GetResource("ShoppingCart.CannotMixStandardAndAutoshipProducts")); + context.AddFailure(translationService.GetResource("ShoppingCart.CannotMixStandardAndAutoshipProducts", value.Language.Id)); //don't mix recurring products if (hasRecurringProducts && hasRecurringProductsMix) - context.AddFailure(translationService.GetResource("ShoppingCart.CannotMixRecurringProducts")); + context.AddFailure(translationService.GetResource("ShoppingCart.CannotMixRecurringProducts", value.Language.Id)); }); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Commands/Checkout/Orders/AddRequiredProductsCommand.cs b/src/Business/Grand.Business.Core/Commands/Checkout/Orders/AddRequiredProductsCommand.cs index 763c3747e5..c630f70c17 100644 --- a/src/Business/Grand.Business.Core/Commands/Checkout/Orders/AddRequiredProductsCommand.cs +++ b/src/Business/Grand.Business.Core/Commands/Checkout/Orders/AddRequiredProductsCommand.cs @@ -1,5 +1,6 @@ using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Mediator; @@ -11,4 +12,9 @@ public class AddRequiredProductsCommand : IRequest public ShoppingCartType ShoppingCartType { get; set; } public Product Product { get; set; } public string StoreId { get; set; } + + /// + /// Language the cart warnings are written in + /// + public Language Language { get; set; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ReOrderCommand.cs b/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ReOrderCommand.cs index 87655bb253..e61701ebbc 100644 --- a/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ReOrderCommand.cs +++ b/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ReOrderCommand.cs @@ -1,4 +1,5 @@ -using Grand.Domain.Orders; +using Grand.Domain.Localization; +using Grand.Domain.Orders; using Grand.Mediator; namespace Grand.Business.Core.Commands.Checkout.Orders; @@ -6,4 +7,9 @@ namespace Grand.Business.Core.Commands.Checkout.Orders; public class ReOrderCommand : IRequest> { public Order Order { get; set; } + + /// + /// Language the returned warnings are written in + /// + public Language Language { get; set; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartItemWarningsEvent.cs b/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartItemWarningsEvent.cs index a422b61d49..8078d37b48 100644 --- a/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartItemWarningsEvent.cs +++ b/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartItemWarningsEvent.cs @@ -1,5 +1,6 @@ using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Mediator; @@ -8,12 +9,14 @@ namespace Grand.Business.Core.Events.Checkout.ShoppingCart; public class ShoppingCartItemWarningsEvent : INotification where C : Customer where S : ShoppingCartItem where P : Product { - public ShoppingCartItemWarningsEvent(IList warnings, C customer, S shoppingCartItem, P product) + public ShoppingCartItemWarningsEvent(IList warnings, C customer, S shoppingCartItem, P product, + Language language) { Warnings = warnings; Customer = customer; ShoppingCartItem = shoppingCartItem; Product = product; + Language = language; } public IList Warnings { get; } @@ -23,4 +26,9 @@ public ShoppingCartItemWarningsEvent(IList warnings, C customer, S shopp public S ShoppingCartItem { get; } public P Product { get; } + + /// + /// Language the warnings are written in + /// + public Language Language { get; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartWarningsEvent.cs b/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartWarningsEvent.cs index 638dbf5b83..0ee0b55e8a 100644 --- a/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartWarningsEvent.cs +++ b/src/Business/Grand.Business.Core/Events/Checkout/ShoppingCart/ShoppingCartWarningsEvent.cs @@ -1,4 +1,5 @@ using Grand.Domain.Common; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Mediator; @@ -7,12 +8,13 @@ namespace Grand.Business.Core.Events.Checkout.ShoppingCart; public class ShoppingCartWarningsEvent : INotification where U : ShoppingCartItem { public ShoppingCartWarningsEvent(IList warnings, IList shoppingCartItems, - IList checkoutAttributes, bool validateCheckoutAttributes) + IList checkoutAttributes, bool validateCheckoutAttributes, Language language) { Warnings = warnings; ShoppingCartItems = shoppingCartItems; CheckoutAttributes = checkoutAttributes; ValidateCheckoutAttributes = validateCheckoutAttributes; + Language = language; } public IList Warnings { get; } @@ -22,4 +24,9 @@ public ShoppingCartWarningsEvent(IList warnings, IList shoppingCartItems, public IList CheckoutAttributes { get; } public bool ValidateCheckoutAttributes { get; } + + /// + /// Language the warnings are written in + /// + public Language Language { get; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Events/Customers/CustomerLoggedInEvent.cs b/src/Business/Grand.Business.Core/Events/Customers/CustomerLoggedInEvent.cs index 7b05608c7d..6984219a94 100644 --- a/src/Business/Grand.Business.Core/Events/Customers/CustomerLoggedInEvent.cs +++ b/src/Business/Grand.Business.Core/Events/Customers/CustomerLoggedInEvent.cs @@ -1,4 +1,5 @@ using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Mediator; namespace Grand.Business.Core.Events.Customers; @@ -8,11 +9,12 @@ namespace Grand.Business.Core.Events.Customers; /// public class CustomerLoggedInEvent : INotification { - public CustomerLoggedInEvent(Customer customer, Customer previousCustomer, string storeId) + public CustomerLoggedInEvent(Customer customer, Customer previousCustomer, string storeId, Language language) { Customer = customer; PreviousCustomer = previousCustomer; StoreId = storeId; + Language = language; } /// @@ -32,4 +34,9 @@ public Customer Customer { /// Store of the request the customer logged in through /// public string StoreId { get; } + + /// + /// Working language of the request the customer logged in through + /// + public Language Language { get; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Events/Customers/CustomerRegisteredEvent.cs b/src/Business/Grand.Business.Core/Events/Customers/CustomerRegisteredEvent.cs index 5815ae0e06..9f98eae665 100644 --- a/src/Business/Grand.Business.Core/Events/Customers/CustomerRegisteredEvent.cs +++ b/src/Business/Grand.Business.Core/Events/Customers/CustomerRegisteredEvent.cs @@ -1,4 +1,5 @@ using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Mediator; namespace Grand.Business.Core.Events.Customers; @@ -8,9 +9,10 @@ namespace Grand.Business.Core.Events.Customers; /// public class CustomerRegisteredEvent : INotification { - public CustomerRegisteredEvent(Customer customer) + public CustomerRegisteredEvent(Customer customer, Language language) { Customer = customer; + Language = language; } /// @@ -20,4 +22,10 @@ public Customer Customer { get; private set; } + + /// + /// Working language of the request the customer registered through; null when the account was registered + /// without one (from an access token) + /// + public Language Language { get; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Extensions/LanguageServiceExtensions.cs b/src/Business/Grand.Business.Core/Extensions/LanguageServiceExtensions.cs new file mode 100644 index 0000000000..d1cb4631f9 --- /dev/null +++ b/src/Business/Grand.Business.Core/Extensions/LanguageServiceExtensions.cs @@ -0,0 +1,33 @@ +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Domain.Localization; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Extensions; + +public static class LanguageServiceExtensions +{ + /// + /// Gets the language to write in when there is no request: the given language while it still exists, + /// otherwise the store's default language when the store offers it, otherwise the store's first published + /// language, otherwise any language + /// + /// Language service + /// Preferred language identifier; may be empty or point to a deleted language + /// Store whose languages are the fallback; null for all stores + /// The language, or null when no language exists at all + public static async Task GetLanguageOrStoreDefault(this ILanguageService languageService, + string languageId, Store store) + { + if (!string.IsNullOrEmpty(languageId)) + { + var language = await languageService.GetLanguageById(languageId); + if (language != null) + return language; + } + + var storeLanguages = await languageService.GetAllLanguages(storeId: store?.Id ?? ""); + return storeLanguages.FirstOrDefault(l => l.Id == store?.DefaultLanguageId) + ?? storeLanguages.FirstOrDefault() + ?? (await languageService.GetAllLanguages(true)).FirstOrDefault(); + } +} diff --git a/src/Business/Grand.Business.Core/Extensions/ShoppingCartEventPublisherExtensions.cs b/src/Business/Grand.Business.Core/Extensions/ShoppingCartEventPublisherExtensions.cs index 7b5625eaa4..e311ef9a85 100644 --- a/src/Business/Grand.Business.Core/Extensions/ShoppingCartEventPublisherExtensions.cs +++ b/src/Business/Grand.Business.Core/Extensions/ShoppingCartEventPublisherExtensions.cs @@ -2,6 +2,7 @@ using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Mediator; @@ -10,18 +11,19 @@ namespace Grand.Business.Core.Extensions; public static class ShoppingCartEventPublisherExtensions { public static async Task ShoppingCartWarningsAdd(this IMediator eventPublisher, IList warnings, - IList shoppingCartItems, IList checkoutAttributes, bool validateCheckoutAttributes) + IList shoppingCartItems, IList checkoutAttributes, bool validateCheckoutAttributes, + Language language) where U : ShoppingCartItem { await eventPublisher.Publish(new ShoppingCartWarningsEvent(warnings, shoppingCartItems, - checkoutAttributes, validateCheckoutAttributes)); + checkoutAttributes, validateCheckoutAttributes, language)); } public static async Task ShoppingCartItemWarningsAdded(this IMediator eventPublisher, - IList warnings, C customer, S shoppingCartItem, P product) + IList warnings, C customer, S shoppingCartItem, P product, Language language) where C : Customer where S : ShoppingCartItem where P : Product { await eventPublisher.Publish( - new ShoppingCartItemWarningsEvent(warnings, customer, shoppingCartItem, product)); + new ShoppingCartItemWarningsEvent(warnings, customer, shoppingCartItem, product, language)); } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Interfaces/Authentication/IExternalAuthenticationService.cs b/src/Business/Grand.Business.Core/Interfaces/Authentication/IExternalAuthenticationService.cs index d9037a6936..4d76f2e639 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Authentication/IExternalAuthenticationService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Authentication/IExternalAuthenticationService.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Utilities.Authentication; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Stores; using Microsoft.AspNetCore.Mvc; @@ -18,9 +19,10 @@ public interface IExternalAuthenticationService /// External authentication parameters /// Customer of the request (a guest when nobody is logged in) /// Store of the request + /// Working language of the request /// Defines the URL of user return, when successfully authenticated /// Result of an authentication - Task Authenticate(ExternalAuthParam parameters, Customer customer, Store store, + Task Authenticate(ExternalAuthParam parameters, Customer customer, Store store, Language language, string returnUrl = null); #endregion diff --git a/src/Business/Grand.Business.Core/Interfaces/Authentication/IOidcCustomerResolver.cs b/src/Business/Grand.Business.Core/Interfaces/Authentication/IOidcCustomerResolver.cs index 38f70af1d8..51a802b891 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Authentication/IOidcCustomerResolver.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Authentication/IOidcCustomerResolver.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Utilities.Authentication; using Grand.Domain.Customers; +using Grand.Domain.Localization; using System.Security.Claims; namespace Grand.Business.Core.Interfaces.Authentication; @@ -23,7 +24,9 @@ public interface IOidcCustomerResolver /// E-mail of the provider identity /// The provider verified the e-mail /// Current store - Task ResolveForLogin(string sub, string email, bool emailVerified, string storeId); + /// Working language of the sign-in request + Task ResolveForLogin(string sub, string email, bool emailVerified, string storeId, + Language language); /// /// Stores the link between the provider subject and the customer (one subject per customer, one customer per subject; diff --git a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartService.cs b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartService.cs index cd67b2b767..79732af023 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartService.cs @@ -1,6 +1,7 @@ -using Grand.Business.Core.Utilities.Checkout; +using Grand.Business.Core.Utilities.Checkout; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Core.Interfaces.Checkout.Orders; @@ -45,6 +46,7 @@ Task FindShoppingCartItem(IList shoppingCart /// Add a product to shopping cart /// /// Customer + /// Language the warnings are written in /// Product id /// ShoppingCartType /// Store id @@ -60,8 +62,8 @@ Task FindShoppingCartItem(IList shoppingCart /// Duration for reservation /// ShoppingCartValidatorOptions /// (warnings, shoppingCartItem) - Task<(IList warnings, ShoppingCartItem shoppingCartItem)> AddToCart(Customer customer, string productId, - ShoppingCartType shoppingCartType, string storeId, string warehouseId = null, + Task<(IList warnings, ShoppingCartItem shoppingCartItem)> AddToCart(Customer customer, Language language, + string productId, ShoppingCartType shoppingCartType, string storeId, string warehouseId = null, IList attributes = null, double? customerEnteredPrice = null, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null, @@ -74,6 +76,7 @@ Task FindShoppingCartItem(IList shoppingCart /// Updates the shopping cart item /// /// Customer + /// Language the warnings are written in /// Shopping cart item identifier /// Warehouse ident /// Attributes @@ -85,7 +88,7 @@ Task FindShoppingCartItem(IList shoppingCart /// Reservation ident /// /// Warnings - Task> UpdateShoppingCartItem(Customer customer, + Task> UpdateShoppingCartItem(Customer customer, Language language, string shoppingCartItemId, string warehouseId, IList attributes, double? customerEnteredPrice = null, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null, @@ -110,9 +113,11 @@ Task DeleteShoppingCartItem(Customer customer, ShoppingCartItem shoppingCartItem /// From customer /// To customer /// Store whose selected checkout attributes move along + /// Language the items are validated in /// /// A value indicating whether to coupon codes (discount and gift voucher) should be also /// re-applied /// - Task MigrateShoppingCart(Customer fromCustomer, Customer toCustomer, string storeId, bool includeCouponCodes); + Task MigrateShoppingCart(Customer fromCustomer, Customer toCustomer, string storeId, Language language, + bool includeCouponCodes); } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartValidator.cs b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartValidator.cs index c80fb60fa5..8e6b3df2aa 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartValidator.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartValidator.cs @@ -2,6 +2,7 @@ using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; namespace Grand.Business.Core.Interfaces.Checkout.Orders; @@ -14,8 +15,10 @@ public interface IShoppingCartValidator /// Customer /// Product /// Shopping cart item + /// Language the warnings are written in /// Warnings - Task> GetStandardWarnings(Customer customer, Product product, ShoppingCartItem shoppingCartItem); + Task> GetStandardWarnings(Customer customer, Product product, ShoppingCartItem shoppingCartItem, + Language language); /// /// Validates shopping cart item attributes @@ -23,10 +26,11 @@ public interface IShoppingCartValidator /// Customer /// Product /// Shopping cart item + /// Language the warnings are written in /// A value indicating whether we should ignore non-combinable attributes /// Warnings Task> GetShoppingCartItemAttributeWarnings(Customer customer, - Product product, ShoppingCartItem shoppingCartItem, + Product product, ShoppingCartItem shoppingCartItem, Language language, bool ignoreNonCombinableAttributes = false); /// @@ -35,9 +39,10 @@ Task> GetShoppingCartItemAttributeWarnings(Customer customer, /// Customer /// Product /// Shopping cart item + /// Language the warnings are written in /// Warnings Task> GetShoppingCartItemGiftVoucherWarnings(Customer customer, - Product product, ShoppingCartItem shoppingCartItem); + Product product, ShoppingCartItem shoppingCartItem, Language language); /// /// Validate bid @@ -45,8 +50,9 @@ Task> GetShoppingCartItemGiftVoucherWarnings(Customer customer, /// /// /// + /// Language the warnings are written in /// - Task> GetAuctionProductWarning(double bid, Product product, Customer customer); + Task> GetAuctionProductWarning(double bid, Product product, Customer customer, Language language); /// /// Validates shopping cart item @@ -55,9 +61,10 @@ Task> GetShoppingCartItemGiftVoucherWarnings(Customer customer, /// Shopping cart type /// Product /// Options + /// Language the warnings are written in /// Warnings Task> GetShoppingCartItemWarnings(Customer customer, ShoppingCartItem shoppingCartItem, - Product product, ShoppingCartValidatorOptions options); + Product product, ShoppingCartValidatorOptions options, Language language); /// /// Validates whether this shopping cart is valid @@ -65,12 +72,13 @@ Task> GetShoppingCartItemWarnings(Customer customer, ShoppingCartI /// Shopping cart /// Checkout attributes /// Customer, store, currency and tax display the cart is validated for + /// Language the warnings are written in /// A value indicating whether to validate checkout attributes /// A value indicating whether to validate subtotal/total attributes /// Warnings Task> GetShoppingCartWarnings(IList shoppingCart, - IList checkoutAttributes, CalculationContext context, bool validateCheckoutAttributes, - bool validateAmount); + IList checkoutAttributes, CalculationContext context, Language language, + bool validateCheckoutAttributes, bool validateAmount); /// /// Validates shopping cart item for reservation products @@ -78,9 +86,10 @@ Task> GetShoppingCartWarnings(IList shoppingCart /// Customer /// product /// ShoppingCartItem + /// Language the warnings are written in /// Warnings Task> GetReservationProductWarnings(Customer customer, Product product, - ShoppingCartItem shoppingCartItem); + ShoppingCartItem shoppingCartItem, Language language); /// /// Validates shopping cart item for inventory @@ -88,9 +97,10 @@ Task> GetReservationProductWarnings(Customer customer, Product pro /// Customer /// product /// ShoppingCartItem + /// Language the warnings are written in /// Warnings Task> GetInventoryProductWarnings(Customer customer, Product product, - ShoppingCartItem shoppingCartItem); + ShoppingCartItem shoppingCartItem, Language language); /// /// Validates common problems @@ -103,10 +113,11 @@ Task> GetInventoryProductWarnings(Customer customer, Product produ /// rental end date /// quantity /// reservation Id + /// Language the warnings are written in /// Warnings Task> CheckCommonWarnings(Customer customer, IList currentCart, Product product, ShoppingCartType shoppingCartType, DateTime? rentalStartDate, DateTime? rentalEndDate, int quantity, - string reservationId); + string reservationId, Language language); /// /// Validates required products (products which require some other products to be added to the cart) @@ -115,8 +126,9 @@ Task> CheckCommonWarnings(Customer customer, IListShopping cart type /// Product /// Store identifier + /// Language the warnings are written in /// Warnings Task> GetRequiredProductWarnings(Customer customer, ShoppingCartItem shoppingCartItem, Product product, - string storeId); + string storeId, Language language); } \ No newline at end of file diff --git a/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs b/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs index 82faf4f22c..a94f66d73b 100644 --- a/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs +++ b/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs @@ -1,5 +1,7 @@ -using Grand.Business.Core.Interfaces.Catalog.Products; +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Checkout.Orders; +using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Interfaces.Messages; using Grand.Business.Core.Interfaces.System.ScheduleTasks; @@ -17,6 +19,7 @@ public class EndAuctionsTask : IScheduleTask { private readonly IAuctionService _auctionService; private readonly ICustomerService _customerService; + private readonly ILanguageService _languageService; private readonly LanguageSettings _languageSettings; private readonly ILogger _logger; private readonly IMessageProviderService _messageProviderService; @@ -25,8 +28,9 @@ public class EndAuctionsTask : IScheduleTask public EndAuctionsTask(IAuctionService auctionService, IMessageProviderService messageProviderService, LanguageSettings translationService, IShoppingCartService shoppingCartService, - ICustomerService customerService, ILogger logger) + ICustomerService customerService, ILanguageService languageService, ILogger logger) { + _languageService = languageService; _auctionService = auctionService; _messageProviderService = messageProviderService; _languageSettings = translationService; @@ -41,6 +45,11 @@ public EndAuctionsTask(IAuctionService auctionService, public async Task Execute() { var auctionsToEnd = await _auctionService.GetAuctionsToEnd(); + if (!auctionsToEnd.Any()) return; + + //cart warnings only reach the log, so they are written in the admin language + var language = + await _languageService.GetLanguageOrStoreDefault(_languageSettings.DefaultAdminLanguageId, null); foreach (var auctionToEnd in auctionsToEnd) { var bid = (await _auctionService.GetBidsByProductId(auctionToEnd.Id)).MaxBy(x => x.Amount); @@ -52,7 +61,15 @@ await _messageProviderService.SendAuctionEndedStoreOwnerMessage(auctionToEnd, continue; } + if (language == null) + { + _logger.LogWarning("EndAuctionTask - Product {Name} - no language exists to validate the bid", + auctionToEnd.Name); + continue; + } + var warnings = (await _shoppingCartService.AddToCart(await _customerService.GetCustomerById(bid.CustomerId), + language, bid.ProductId, ShoppingCartType.Auctions, bid.StoreId, bid.WarehouseId, customerEnteredPrice: bid.Amount, validator: new ShoppingCartValidatorOptions { GetRequiredProductWarnings = false })).warnings; diff --git a/src/Plugins/Authentication.Facebook/Controllers/FacebookAuthenticationController.cs b/src/Plugins/Authentication.Facebook/Controllers/FacebookAuthenticationController.cs index 0e72bc1adc..5dec5505b5 100644 --- a/src/Plugins/Authentication.Facebook/Controllers/FacebookAuthenticationController.cs +++ b/src/Plugins/Authentication.Facebook/Controllers/FacebookAuthenticationController.cs @@ -77,7 +77,8 @@ public async Task FacebookLoginCallback(string returnUrl) //authenticate Grand user return await _externalAuthenticationService.Authenticate(authenticationParameters, - _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, returnUrl); + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingLanguage, returnUrl); } public IActionResult FacebookSignInFailed(string error_code, string error_message, string state) diff --git a/src/Plugins/Authentication.Google/Controllers/GoogleAuthenticationController.cs b/src/Plugins/Authentication.Google/Controllers/GoogleAuthenticationController.cs index 368c4acdff..686bec8886 100644 --- a/src/Plugins/Authentication.Google/Controllers/GoogleAuthenticationController.cs +++ b/src/Plugins/Authentication.Google/Controllers/GoogleAuthenticationController.cs @@ -76,7 +76,8 @@ public async Task GoogleLoginCallback(string returnUrl) //authenticate grand user return await _externalAuthenticationService.Authenticate(authenticationParameters, - _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, returnUrl); + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingLanguage, returnUrl); } public IActionResult GoogleSignInFailed(string error_message) diff --git a/src/Plugins/Authentication.OpenIdConnect/Controllers/OidcLoginController.cs b/src/Plugins/Authentication.OpenIdConnect/Controllers/OidcLoginController.cs index 216949c58d..d37e88bc30 100644 --- a/src/Plugins/Authentication.OpenIdConnect/Controllers/OidcLoginController.cs +++ b/src/Plugins/Authentication.OpenIdConnect/Controllers/OidcLoginController.cs @@ -106,7 +106,7 @@ public async Task Callback(string returnUrl) returnUrl = LocalOrNull(returnUrl); var result = await _oidcCustomerResolver.ResolveForLogin(pending.Subject, pending.Email, pending.EmailVerified, - _contextAccessor.StoreContext.CurrentStore.Id); + _contextAccessor.StoreContext.CurrentStore.Id, _contextAccessor.WorkContext.WorkingLanguage); //a store session is never replaced by another account, and a signed-in visitor does not register; the same //account signing in again (for example right after "Connect your account") gets a session-backed cookie @@ -145,7 +145,8 @@ public async Task Callback(string returnUrl) //the identity provider is the sign-in: its multi-factor policy applies, not the store's two-factor code await _mediator.Publish(new CustomerLoggedInEvent(result.Customer, - _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id)); + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id, + _contextAccessor.WorkContext.WorkingLanguage)); await _authenticationService.SignIn(result.Customer, false, sessionId); return RedirectLocal(returnUrl); } diff --git a/src/Tests/Grand.Business.Authentication.Tests/Services/ExternalAuthenticationServiceTests.cs b/src/Tests/Grand.Business.Authentication.Tests/Services/ExternalAuthenticationServiceTests.cs index 065e400a0f..3a9e786213 100644 --- a/src/Tests/Grand.Business.Authentication.Tests/Services/ExternalAuthenticationServiceTests.cs +++ b/src/Tests/Grand.Business.Authentication.Tests/Services/ExternalAuthenticationServiceTests.cs @@ -8,6 +8,7 @@ using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Stores; using Grand.Mediator; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -123,18 +124,20 @@ public void AuthenticationProviderIsAvailable_NullCustomerOrStore_Throws() } [TestMethod] - public async Task Authenticate_AssociatedUserFromGuest_PublishesLoggedInEventWithPassedCustomerAndStore() + public async Task Authenticate_AssociatedUserFromGuest_PublishesLoggedInEventWithPassedCustomerStoreAndLanguage() { var user = new Customer { Id = "user" }; var guest = new Customer { Id = "guest" }; + var language = new Language { Id = "lang1" }; _customerServiceMock.Setup(c => c.GetCustomerById("user")).ReturnsAsync(user); var parameters = new ExternalAuthParam { ProviderSystemName = "ExternalAuthenticationProviderTest", Identifier = "1" }; await _externalAuthenticationService.AssociateCustomer(user, parameters); - await _externalAuthenticationService.Authenticate(parameters, guest, new Store { Id = "store1" }); + await _externalAuthenticationService.Authenticate(parameters, guest, new Store { Id = "store1" }, language); _mediatorMock.Verify(m => m.Publish(It.Is(e => - e.Customer == user && e.PreviousCustomer == guest && e.StoreId == "store1"), default), Times.Once); + e.Customer == user && e.PreviousCustomer == guest && e.StoreId == "store1" && e.Language == language), + default), Times.Once); _authenticationServiceMock.Verify(a => a.SignIn(user, false), Times.Once); } @@ -142,15 +145,17 @@ public async Task Authenticate_AssociatedUserFromGuest_PublishesLoggedInEventWit public async Task Authenticate_UnknownIdentity_RegistersPassedCustomerInPassedStore() { var guest = new Customer { Id = "guest" }; + var language = new Language { Id = "lang1" }; var parameters = new ExternalAuthParam { ProviderSystemName = "ExternalAuthenticationProviderTest", Identifier = "new", Email = "new@test.com" }; - await _externalAuthenticationService.Authenticate(parameters, guest, new Store { Id = "store1" }); + await _externalAuthenticationService.Authenticate(parameters, guest, new Store { Id = "store1" }, language); _customerManagerServiceMock.Verify(m => m.RegisterCustomer(It.Is(r => r.Customer == guest && r.StoreId == "store1" && r.Email == "new@test.com")), Times.Once); - _mediatorMock.Verify(m => m.Publish(It.Is(e => e.Customer == guest), default), + _mediatorMock.Verify(m => m.Publish(It.Is(e => + e.Customer == guest && e.Language == language), default), Times.Once); _authenticationServiceMock.Verify(a => a.SignIn(guest, false), Times.Once); Assert.AreEqual("guest", _externalAuthenticationRecordRepository.Table.Single().CustomerId); diff --git a/src/Tests/Grand.Business.Authentication.Tests/Services/OidcCustomerResolverLoginTests.cs b/src/Tests/Grand.Business.Authentication.Tests/Services/OidcCustomerResolverLoginTests.cs index 8b5e5529bd..f9f0c73dd1 100644 --- a/src/Tests/Grand.Business.Authentication.Tests/Services/OidcCustomerResolverLoginTests.cs +++ b/src/Tests/Grand.Business.Authentication.Tests/Services/OidcCustomerResolverLoginTests.cs @@ -11,6 +11,7 @@ using Grand.Data.Tests.MongoDb; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Permissions; using Grand.Infrastructure.Configuration; using Grand.Mediator; @@ -77,10 +78,11 @@ private List LinksOf(string sub) => private const string Store = "store1"; + private static readonly Language Language = new() { Id = "lang1" }; [TestMethod] public async Task NoSubject_Refused_InvalidIdentity() => - Assert.AreEqual(OidcLoginRefusal.InvalidIdentity, (await Resolver().ResolveForLogin(null, "a@b.c", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.InvalidIdentity, (await Resolver().ResolveForLogin(null, "a@b.c", true, Store, Language)).Refusal); [TestMethod] public async Task ProviderChanged_Linked_RefusedNothingLoaded() @@ -89,7 +91,7 @@ public async Task ProviderChanged_Linked_RefusedNothingLoaded() await Link("c1"); Assert.AreEqual(OidcLoginRefusal.ProviderChanged, - (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Refusal); + (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Refusal); _customerService.Verify(x => x.GetCustomerById(It.IsAny()), Times.Never); } @@ -109,7 +111,7 @@ public async Task Linked_SignIn_WithoutEmail() var customer = new Customer { Id = "c1", Active = true }; _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(customer); await Link("c1"); - var result = await Resolver().ResolveForLogin("sub-1", null, false, Store); + var result = await Resolver().ResolveForLogin("sub-1", null, false, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); Assert.AreSame(customer, result.Customer); } @@ -119,19 +121,19 @@ public async Task LinkedInactive_Refused_AccountInactive() { _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(new Customer { Id = "c1", Active = false }); await Link("c1"); - Assert.AreEqual(OidcLoginRefusal.AccountInactive, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.AccountInactive, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Refusal); } [TestMethod] public async Task NoEmail_Refused_InvalidIdentity() => - Assert.AreEqual(OidcLoginRefusal.InvalidIdentity, (await Resolver().ResolveForLogin("sub-1", " ", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.InvalidIdentity, (await Resolver().ResolveForLogin("sub-1", " ", true, Store, Language)).Refusal); [TestMethod] public async Task ExistingCustomer_Verified_LinkedAndSignIn() { var customer = new Customer { Id = "c1", Active = true, Email = "john@example.com" }; _customerService.Setup(x => x.GetCustomerByEmail("John@Example.com", "")).ReturnsAsync(customer); - var result = await Resolver().ResolveForLogin("sub-1", "John@Example.com", true, Store); + var result = await Resolver().ResolveForLogin("sub-1", "John@Example.com", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); var link = await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1"); Assert.AreEqual("c1", link.CustomerId); @@ -144,7 +146,7 @@ public async Task ExistingCustomer_Unverified_LinkRequired_NotLinked() { var customer = new Customer { Id = "c1", Active = true }; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(customer); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store, Language); Assert.AreEqual(OidcLoginOutcome.LinkRequired, result.Outcome); Assert.AreSame(customer, result.Customer); Assert.IsNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); @@ -156,7 +158,7 @@ public async Task ExistingAdmin_Verified_LinkRequired_NotLinked() var customer = new Customer { Id = "c1", Active = true }; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(customer); _permissionService.Setup(x => x.Authorize(StandardPermission.ManageAccessAdminPanel, customer)).ReturnsAsync(true); - Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); Assert.IsNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); } @@ -167,7 +169,7 @@ public async Task ExistingVendorOrStoreStaff_Verified_LinkRequired(string vendor { var customer = new Customer { Id = "c1", Active = true, VendorId = vendorId, StaffStoreId = staffStoreId }; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(customer); - Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); Assert.IsNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); } @@ -184,7 +186,7 @@ public async Task ExistingTwoFactorCustomer_Verified_LinkRequired_NotLinked() _customerSettings.TwoFactorAuthenticationEnabled = true; var customer = TwoFactorCustomer(); _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(customer); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.LinkRequired, result.Outcome); Assert.AreSame(customer, result.Customer); Assert.IsNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); @@ -195,7 +197,7 @@ public async Task ExistingTwoFactorCustomer_StoreTwoFactorOff_Verified_SignIn() { _customerSettings.TwoFactorAuthenticationEnabled = false; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(TwoFactorCustomer()); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); Assert.IsNotNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); } @@ -206,7 +208,7 @@ public async Task LinkedTwoFactorCustomer_SignIn() var customer = TwoFactorCustomer(); _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(customer); await Link("c1"); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -214,7 +216,7 @@ public async Task LinkMadeByEmail_PlainCustomer_Verified_SignIn() { _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(new Customer { Id = "c1", Active = true }); await Link("c1", byEmail: true); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -224,7 +226,7 @@ public async Task LinkMadeByEmail_Unverified_LinkRequired() var customer = new Customer { Id = "c1", Active = true }; _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(customer); await Link("c1", byEmail: true); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store, Language); Assert.AreEqual(OidcLoginOutcome.LinkRequired, result.Outcome); Assert.AreSame(customer, result.Customer); } @@ -235,7 +237,7 @@ public async Task LinkMadeByEmail_TwoFactorCustomer_LinkRequired() _customerSettings.TwoFactorAuthenticationEnabled = true; _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(TwoFactorCustomer()); await Link("c1", byEmail: true); - Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -250,7 +252,7 @@ public async Task LinkMadeByEmail_BackOfficeAccount_LinkRequired(string vendorId if (vendorId == null && staffStoreId == null) _permissionService.Setup(x => x.Authorize(StandardPermission.ManageAccessAdminPanel, customer)).ReturnsAsync(true); await Link("c1", byEmail: true); - Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -260,7 +262,7 @@ public async Task LinkedAdmin_SignIn() _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(customer); _permissionService.Setup(x => x.Authorize(StandardPermission.ManageAccessAdminPanel, customer)).ReturnsAsync(true); await Link("c1"); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -269,7 +271,7 @@ public async Task ProviderOnly_Staff_LinkedByVerifiedEmail() _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; var staff = new Customer { Id = "c1", Active = true, StaffStoreId = "s1" }; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(staff); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); Assert.AreEqual("c1", result.Customer.Id); Assert.IsTrue((await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")).LinkedByEmail); @@ -281,7 +283,7 @@ public async Task ProviderOnly_TwoFactor_LinkedByVerifiedEmail() _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; _customerSettings.TwoFactorAuthenticationEnabled = true; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(TwoFactorCustomer()); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); Assert.IsNotNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); } @@ -292,7 +294,7 @@ public async Task ProviderOnly_UnverifiedEmail_ExistingAccount_Refused() _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())) .ReturnsAsync(new Customer { Id = "c1", Active = true }); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store, Language); Assert.AreEqual(OidcLoginRefusal.EmailNotVerified, result.Refusal); Assert.IsNull(await _links.GetOneAsync(x => x.ExternalIdentifier == "sub-1")); } @@ -301,7 +303,7 @@ public async Task ProviderOnly_UnverifiedEmail_ExistingAccount_Refused() public async Task ProviderOnly_UnverifiedEmail_Unknown_RefusedNotRegistered() { _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; - var result = await Resolver().ResolveForLogin("sub-new", "new@shop.com", false, Store); + var result = await Resolver().ResolveForLogin("sub-new", "new@shop.com", false, Store, Language); Assert.AreEqual(OidcLoginOutcome.Refused, result.Outcome); Assert.AreEqual(OidcLoginRefusal.EmailNotVerified, result.Refusal); } @@ -311,7 +313,7 @@ public async Task ProviderOnly_VerifiedEmail_Unknown_RegistrationRequired() { _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; Assert.AreEqual(OidcLoginOutcome.RegistrationRequired, - (await Resolver().ResolveForLogin("sub-new", "new@shop.com", true, Store)).Outcome); + (await Resolver().ResolveForLogin("sub-new", "new@shop.com", true, Store, Language)).Outcome); } [TestMethod] @@ -324,7 +326,7 @@ public async Task ProviderOnly_AutoProvision_Unknown_AccountCreatedLinkedAndSign _loginConfig.AutoProvision = true; _customerSettings.UserRegistrationType = type; - var result = await Resolver().ResolveForLogin("sub-new", "New@Shop.com", true, Store); + var result = await Resolver().ResolveForLogin("sub-new", "New@Shop.com", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); Assert.AreEqual("new1", result.Customer.Id); @@ -334,7 +336,9 @@ public async Task ProviderOnly_AutoProvision_Unknown_AccountCreatedLinkedAndSign var link = LinksOf("sub-new").Single(); Assert.AreEqual("new1", link.CustomerId); Assert.IsFalse(link.LinkedByEmail); - _mediator.Verify(x => x.Publish(It.IsAny(), It.IsAny()), Times.Once); + //the event carries the language of the sign-in request + _mediator.Verify(x => x.Publish(It.Is(e => e.Language == Language), + It.IsAny()), Times.Once); } [TestMethod] @@ -344,7 +348,7 @@ public async Task ProviderOnly_AutoProvision_UnverifiedEmail_RefusedNothingCreat _loginConfig.AutoProvision = true; Assert.AreEqual(OidcLoginRefusal.EmailNotVerified, - (await Resolver().ResolveForLogin("sub-new", "new@shop.com", false, Store)).Refusal); + (await Resolver().ResolveForLogin("sub-new", "new@shop.com", false, Store, Language)).Refusal); _customerService.Verify(x => x.InsertGuestCustomer(It.IsAny()), Times.Never); } @@ -357,7 +361,7 @@ public async Task ProviderOnly_AutoProvision_UserNameTaken_RefusedNothingCreated .ReturnsAsync(new Customer { Id = "other" }); Assert.AreEqual(OidcLoginRefusal.AccountUnusable, - (await Resolver().ResolveForLogin("sub-new", "new@shop.com", true, Store)).Refusal); + (await Resolver().ResolveForLogin("sub-new", "new@shop.com", true, Store, Language)).Refusal); _customerService.Verify(x => x.InsertGuestCustomer(It.IsAny()), Times.Never); } @@ -367,7 +371,7 @@ public async Task Mixed_AutoProvisionIgnored_RegistrationRequired() _loginConfig.AutoProvision = true; Assert.AreEqual(OidcLoginOutcome.RegistrationRequired, - (await Resolver().ResolveForLogin("sub-new", "new@shop.com", true, Store)).Outcome); + (await Resolver().ResolveForLogin("sub-new", "new@shop.com", true, Store, Language)).Outcome); _customerService.Verify(x => x.InsertGuestCustomer(It.IsAny()), Times.Never); } @@ -378,7 +382,7 @@ public async Task ProviderOnly_LinkMadeByEmail_StaffSignsIn() _customerService.Setup(x => x.GetCustomerById("c1")) .ReturnsAsync(new Customer { Id = "c1", Active = true, StaffStoreId = "s1" }); await Link("c1", byEmail: true); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -388,7 +392,7 @@ public async Task ProviderOnly_LinkMadeByEmail_Unverified_SignsIn() _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(new Customer { Id = "c1", Active = true }); await Link("c1", byEmail: true); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store, Language)).Outcome); } [TestMethod] @@ -398,7 +402,7 @@ public async Task ProviderOnly_ExistingAccount_LinkedToOtherSubject_Refused() _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())) .ReturnsAsync(new Customer { Id = "c1", Active = true }); await Link("c1", "other-sub"); - Assert.AreEqual(OidcLoginRefusal.LinkedToOtherIdentity, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.LinkedToOtherIdentity, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Refusal); } [TestMethod] @@ -406,7 +410,7 @@ public async Task Mixed_Staff_LinkRequired() { var staff = new Customer { Id = "c1", Active = true, StaffStoreId = "s1" }; _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())).ReturnsAsync(staff); - Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.LinkRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); } [TestMethod] @@ -415,7 +419,7 @@ public async Task ExistingCustomerLinkedToOtherSubject_Refused() _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())) .ReturnsAsync(new Customer { Id = "c1", Active = true }); await Link("c1", "other-sub"); - Assert.AreEqual(OidcLoginRefusal.LinkedToOtherIdentity, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.LinkedToOtherIdentity, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Refusal); } [TestMethod] @@ -424,14 +428,14 @@ public async Task ExistingGuestOrSystem_Refused_AccountUnusable() _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())) .ReturnsAsync(new Customer { Id = "c1", Active = true }); _groupService.Setup(x => x.IsRegistered(It.IsAny())).ReturnsAsync(false); - Assert.AreEqual(OidcLoginRefusal.AccountUnusable, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.AccountUnusable, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Refusal); } [TestMethod] public async Task PerStore_EmailLookupScopedToStore() { _customerConfig.RegisterCustomersPerStore = true; - await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store); + await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language); _customerService.Verify(x => x.GetCustomerByEmail("a@b.c", Store), Times.Once); } @@ -439,7 +443,7 @@ public async Task PerStore_EmailLookupScopedToStore() public async Task NoCustomer_RegistrationRequired_NothingCreated_EvenWithAutoRegister() { _config.AutoRegister = true; - Assert.AreEqual(OidcLoginOutcome.RegistrationRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store)).Outcome); + Assert.AreEqual(OidcLoginOutcome.RegistrationRequired, (await Resolver().ResolveForLogin("sub-1", "a@b.c", false, Store, Language)).Outcome); _customerService.Verify(x => x.InsertGuestCustomer(It.IsAny()), Times.Never); } @@ -447,7 +451,7 @@ public async Task NoCustomer_RegistrationRequired_NothingCreated_EvenWithAutoReg public async Task NoCustomer_RegistrationDisabled_Refused() { _customerSettings.UserRegistrationType = UserRegistrationType.Disabled; - Assert.AreEqual(OidcLoginRefusal.RegistrationDisabled, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Refusal); + Assert.AreEqual(OidcLoginRefusal.RegistrationDisabled, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Refusal); } [TestMethod] @@ -519,7 +523,7 @@ public async Task ResolveForLogin_PerStore_SubjectLinkedInAnotherStore_SignsInTh var customer = new Customer { Id = "c1", Active = true, StoreId = Store }; _customerService.Setup(x => x.GetCustomerByEmail("a@b.c", Store)).ReturnsAsync(customer); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); Assert.AreEqual("c1", result.Customer.Id); @@ -534,8 +538,8 @@ public async Task ResolveForLogin_PerStore_GlobalCustomerLink_SignsInInEveryStor await Link("c1", storeId: ""); _customerService.Setup(x => x.GetCustomerById("c1")).ReturnsAsync(new Customer { Id = "c1", Active = true, StoreId = "" }); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store)).Outcome); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, "store2")).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language)).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, "store2", Language)).Outcome); } [TestMethod] @@ -577,12 +581,12 @@ public async Task ResolveForLogin_PerStore_LegacyGlobalLinkToOtherStoreCustomer_ _customerService.Setup(x => x.GetCustomerById("c9")).ReturnsAsync(new Customer { Id = "c9", Active = true, StoreId = "store2" }); _customerService.Setup(x => x.GetCustomerByEmail("a@b.c", Store)).ReturnsAsync(new Customer { Id = "c1", Active = true, StoreId = Store }); - var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store); + var result = await Resolver().ResolveForLogin("sub-1", "a@b.c", true, Store, Language); Assert.AreEqual(OidcLoginOutcome.SignIn, result.Outcome); Assert.AreEqual("c1", result.Customer.Id); Assert.AreEqual(Store, LinksOf("sub-1").Single(x => x.CustomerId == "c1").StoreId); - Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, "store2")).Outcome); + Assert.AreEqual(OidcLoginOutcome.SignIn, (await Resolver().ResolveForLogin("sub-1", "a@b.c", true, "store2", Language)).Outcome); } [TestMethod] @@ -647,7 +651,7 @@ public async Task ResolveForLogin_LostRaceToOtherSubjectOfTheCustomer_RefusedLin _customerService.Setup(x => x.GetCustomerByEmail(It.IsAny(), It.IsAny())) .ReturnsAsync(new Customer { Id = "c1", Active = true }); - var result = await Resolver(links).ResolveForLogin("sub-1", "a@b.c", true, Store); + var result = await Resolver(links).ResolveForLogin("sub-1", "a@b.c", true, Store, Language); Assert.AreEqual(OidcLoginRefusal.LinkedToOtherIdentity, result.Refusal); var stored = links.Table.Where(x => x.CustomerId == "c1").ToList(); diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AddRequiredProductsCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AddRequiredProductsCommandHandlerTests.cs index 148483d7b4..6610c123d8 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AddRequiredProductsCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AddRequiredProductsCommandHandlerTests.cs @@ -5,6 +5,7 @@ using Grand.Business.Core.Utilities.Checkout; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -42,6 +43,7 @@ public async Task HandleTest() { Id = "1", RequireOtherProducts = true, RequiredProductIds = "2,3", AutoAddRequiredProducts = true }; command.ShoppingCartType = ShoppingCartType.ShoppingCart; command.StoreId = ""; + command.Language = new Language { Id = "lang1" }; _productServiceMock.Setup(a => a.GetProductById("2", false)).Returns(() => Task.FromResult(new Product { Id = "2", Published = true, Price = 10 })); @@ -53,7 +55,8 @@ public async Task HandleTest() //Assert _shoppingCartServiceMock.Verify( - c => c.AddToCart(It.IsAny(), It.IsAny(), ShoppingCartType.ShoppingCart, "", null, null, + c => c.AddToCart(It.IsAny(), + command.Language, It.IsAny(), ShoppingCartType.ShoppingCart, "", null, null, null, null, null, 1, false, "", "", "", It.IsAny()), Times.AtLeastOnce); } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandlerTests.cs index d1055d0dcd..cd38ade64c 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/AwardLoyaltyPointsCommandHandlerTests.cs @@ -3,9 +3,11 @@ using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; using Grand.Domain.Customers; using Grand.Domain.Directory; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Mediator; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -22,6 +24,8 @@ public class AwardLoyaltyPointsCommandHandlerTests private Mock _loyaltyPointsServiceMock; private Mock _mediatorMock; private Mock _orderServiceMock; + private Mock _languageServiceMock; + private Mock _storeServiceMock; private Mock _translationServiceMock; @@ -34,10 +38,13 @@ public void Init() _currencyServiceMock = new Mock(); _orderServiceMock = new Mock(); _translationServiceMock = new Mock(); + _languageServiceMock = new Mock(); + _languageServiceMock.Setup(c => c.GetLanguageById("lang1")).ReturnsAsync(new Language { Id = "lang1" }); + _storeServiceMock = new Mock(); _handler = new AwardLoyaltyPointsCommandHandler(_customerServiceMock.Object, _loyaltyPointsServiceMock.Object, _mediatorMock.Object, _orderServiceMock.Object, _translationServiceMock.Object, - _currencyServiceMock.Object); + _languageServiceMock.Object, _storeServiceMock.Object, _currencyServiceMock.Object); } @@ -46,7 +53,7 @@ public async Task HandleTest() { //Arrange var command = new AwardLoyaltyPointsCommand { - Order = new Order { StoreId = "", OrderNumber = 1 } + Order = new Order { StoreId = "", OrderNumber = 1, CustomerLanguageId = "lang1" } }; var expectedCustomer = new Customer { Username = "John", Active = true }; _customerServiceMock.Setup(c => c.GetCustomerById(It.IsAny())) @@ -55,7 +62,9 @@ public async Task HandleTest() .Returns(() => Task.FromResult(new Currency())); _currencyServiceMock.Setup(c => c.ConvertToPrimaryStoreCurrency(100, It.IsAny())) .Returns(() => Task.FromResult((double)100)); - _translationServiceMock.Setup(x => x.GetResource(It.IsAny())).Returns("Name"); + //the message is written in the language the order was placed in + _translationServiceMock.Setup(x => x.GetResource(It.IsAny(), "lang1", It.IsAny(), + It.IsAny())).Returns("Name"); _mediatorMock.Setup(x => x.Send(It.IsAny(), default)) .Returns(Task.FromResult(100)); @@ -64,7 +73,7 @@ public async Task HandleTest() //Assert _loyaltyPointsServiceMock.Verify( - c => c.AddLoyaltyPointsHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + c => c.AddLoyaltyPointsHistory(It.IsAny(), It.IsAny(), It.IsAny(), "Name", It.IsAny(), It.IsAny()), Times.Once); } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandlerTests.cs index ccc1885de0..fdc6636b85 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReduceLoyaltyPointsCommandHandlerTests.cs @@ -3,9 +3,11 @@ using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; using Grand.Domain.Customers; using Grand.Domain.Directory; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Mediator; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -22,6 +24,8 @@ public class ReduceLoyaltyPointsCommandHandlerTests private Mock _loyaltyPointsServiceMock; private Mock _mediatorMock; private Mock _orderServiceMock; + private Mock _languageServiceMock; + private Mock _storeServiceMock; private Mock _translationServiceMock; @@ -34,10 +38,13 @@ public void Init() _currencyServiceMock = new Mock(); _orderServiceMock = new Mock(); _translationServiceMock = new Mock(); + _languageServiceMock = new Mock(); + _languageServiceMock.Setup(c => c.GetLanguageById("lang1")).ReturnsAsync(new Language { Id = "lang1" }); + _storeServiceMock = new Mock(); _handler = new ReduceLoyaltyPointsCommandHandler(_customerServiceMock.Object, _loyaltyPointsServiceMock.Object, _mediatorMock.Object, _orderServiceMock.Object, _translationServiceMock.Object, - _currencyServiceMock.Object); + _languageServiceMock.Object, _storeServiceMock.Object, _currencyServiceMock.Object); } @@ -46,7 +53,7 @@ public async Task HandleTest() { //Arrange var command = new ReduceLoyaltyPointsCommand { - Order = new Order { StoreId = "", OrderNumber = 1, CalcLoyaltyPoints = 100 } + Order = new Order { StoreId = "", OrderNumber = 1, CalcLoyaltyPoints = 100, CustomerLanguageId = "lang1" } }; var expectedCustomer = new Customer { Username = "John", Active = true }; _customerServiceMock.Setup(c => c.GetCustomerById(It.IsAny())) @@ -55,7 +62,9 @@ public async Task HandleTest() .Returns(() => Task.FromResult(new Currency())); _currencyServiceMock.Setup(c => c.ConvertToPrimaryStoreCurrency(100, It.IsAny())) .Returns(() => Task.FromResult((double)100)); - _translationServiceMock.Setup(x => x.GetResource(It.IsAny())).Returns("Name"); + //the message is written in the language the order was placed in + _translationServiceMock.Setup(x => x.GetResource(It.IsAny(), "lang1", It.IsAny(), + It.IsAny())).Returns("Name"); _mediatorMock.Setup(x => x.Send(It.IsAny(), default)) .Returns(Task.FromResult(100)); @@ -65,6 +74,6 @@ public async Task HandleTest() //Assert _loyaltyPointsServiceMock.Verify( c => c.AddLoyaltyPointsHistory(It.IsAny(), It.Is(g => g < 0), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + "Name", It.IsAny(), It.IsAny()), Times.Once); } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandlerTests.cs index 062a4b373e..dd178831ae 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ReturnBackRedeemedLoyaltyPointsCommandHandlerTests.cs @@ -2,7 +2,10 @@ using Grand.Business.Core.Commands.Checkout.Orders; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Domain.Localization; using Grand.Domain.Orders; +using Grand.Domain.Stores; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -13,6 +16,8 @@ public class ReturnBackRedeemedLoyaltyPointsCommandHandlerTests { private ReturnBackRedeemedLoyaltyPointsCommandHandler _handler; private Mock _loyaltyPointsServiceMock; + private Mock _languageServiceMock; + private Mock _storeServiceMock; private Mock _translationServiceMock; [TestInitialize] @@ -20,8 +25,12 @@ public void Init() { _loyaltyPointsServiceMock = new Mock(); _translationServiceMock = new Mock(); + _languageServiceMock = new Mock(); + _languageServiceMock.Setup(c => c.GetLanguageById("lang1")).ReturnsAsync(new Language { Id = "lang1" }); + _storeServiceMock = new Mock(); _handler = new ReturnBackRedeemedLoyaltyPointsCommandHandler(_loyaltyPointsServiceMock.Object, - _translationServiceMock.Object); + _translationServiceMock.Object, _languageServiceMock.Object, + _storeServiceMock.Object); } [TestMethod] @@ -29,14 +38,39 @@ public async Task HandleTest() { //Arrange var command = new ReturnBackRedeemedLoyaltyPointsCommand { - Order = new Order { RedeemedLoyaltyPoints = 10 } + Order = new Order { RedeemedLoyaltyPoints = 10, CustomerLanguageId = "lang1" } }; - _translationServiceMock.Setup(x => x.GetResource(It.IsAny())).Returns("Name"); + //the message is written in the language the order was placed in + _translationServiceMock.Setup(x => x.GetResource(It.IsAny(), "lang1", It.IsAny(), + It.IsAny())).Returns("Name"); //Act await _handler.Handle(command, CancellationToken.None); _loyaltyPointsServiceMock.Verify( c => c.AddLoyaltyPointsHistory(It.IsAny(), It.Is(g => g < 0), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + "Name", It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task Handle_OrderWithoutLanguage_WritesTheMessageInTheStoreDefaultLanguage() + { + //Arrange + var command = new ReturnBackRedeemedLoyaltyPointsCommand { + Order = new Order { RedeemedLoyaltyPoints = 10, StoreId = "store1", CustomerLanguageId = null } + }; + _storeServiceMock.Setup(c => c.GetStoreById("store1")) + .ReturnsAsync(new Store { Id = "store1", DefaultLanguageId = "store-lang" }); + _languageServiceMock.Setup(c => c.GetAllLanguages(false, "store1")) + .ReturnsAsync(new List { new() { Id = "first-lang" }, new() { Id = "store-lang" } }); + _translationServiceMock.Setup(x => x.GetResource(It.IsAny(), "store-lang", It.IsAny(), + It.IsAny())).Returns("Store"); + //Act + await _handler.Handle(command, CancellationToken.None); + + _loyaltyPointsServiceMock.Verify( + c => c.AddLoyaltyPointsHistory(It.IsAny(), It.Is(g => g < 0), "store1", + "Store", It.IsAny(), It.IsAny()), Times.Once); + _translationServiceMock.Verify(x => x.GetResource(It.IsAny(), null, It.IsAny(), + It.IsAny()), Times.Never); } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Events/Customers/CustomerRegisteredEventHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Events/Customers/CustomerRegisteredEventHandlerTests.cs index 12049d0a53..3fdbe3fb9a 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Events/Customers/CustomerRegisteredEventHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Events/Customers/CustomerRegisteredEventHandlerTests.cs @@ -2,8 +2,11 @@ using Grand.Business.Core.Events.Customers; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; +using Grand.Domain.Stores; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -13,26 +16,33 @@ namespace Grand.Business.Checkout.Tests.Events.Customers; public class CustomerRegisteredEventHandlerTests { private CustomerRegisteredEventHandler _customerRegisteredEventHandler; + private Mock _languageServiceMock; private Mock _loyaltyPointsServiceMock; private LoyaltyPointsSettings _loyaltyPointsSettings; + private Mock _storeServiceMock; [TestInitialize] public void Init() { var translationServiceMock = new Mock(); - translationServiceMock.Setup(x => x.GetResource(It.IsAny())).Returns("Name"); + translationServiceMock.Setup(x => x.GetResource(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) + .Returns((string name, string languageId, string _, bool _) => $"{name}@{languageId}"); _loyaltyPointsServiceMock = new Mock(); + _storeServiceMock = new Mock(); + _languageServiceMock = new Mock(); _loyaltyPointsSettings = new LoyaltyPointsSettings { Enabled = true, PointsForRegistration = 10 }; _customerRegisteredEventHandler = new CustomerRegisteredEventHandler(translationServiceMock.Object, - _loyaltyPointsServiceMock.Object, _loyaltyPointsSettings); + _loyaltyPointsServiceMock.Object, _loyaltyPointsSettings, _storeServiceMock.Object, + _languageServiceMock.Object); } [TestMethod] public async Task HandleTest_LoyaltyPointsSettings_Enabled() { //Act - await _customerRegisteredEventHandler.Handle(new CustomerRegisteredEvent(new Customer()), - CancellationToken.None); + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer(), new Language { Id = "lang1" }), CancellationToken.None); //Assert _loyaltyPointsServiceMock.Verify( c => c.AddLoyaltyPointsHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -44,11 +54,91 @@ public async Task HandleTest_LoyaltyPointsSettings_Disabled() { _loyaltyPointsSettings.Enabled = false; //Act - await _customerRegisteredEventHandler.Handle(new CustomerRegisteredEvent(new Customer()), - CancellationToken.None); + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer(), new Language { Id = "lang1" }), CancellationToken.None); //Assert _loyaltyPointsServiceMock.Verify( c => c.AddLoyaltyPointsHistory(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } + + [TestMethod] + public async Task Handle_WritesTheMessageInTheLanguageOfTheEvent() + { + //Act + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer { StoreId = "store1" }, new Language { Id = "lang1" }), + CancellationToken.None); + //Assert + _loyaltyPointsServiceMock.Verify( + c => c.AddLoyaltyPointsHistory(It.IsAny(), 10, "store1", + "LoyaltyPoints.Message.EarnedForRegistration@lang1", It.IsAny(), It.IsAny()), + Times.Once); + _storeServiceMock.Verify(c => c.GetStoreById(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task Handle_WithoutLanguage_WritesTheMessageInTheStoreDefaultLanguage() + { + _storeServiceMock.Setup(c => c.GetStoreById("store1")) + .ReturnsAsync(new Store { Id = "store1", DefaultLanguageId = "store-lang" }); + _languageServiceMock.Setup(c => c.GetAllLanguages(false, "store1")) + .ReturnsAsync(new List { new() { Id = "first-lang" }, new() { Id = "store-lang" } }); + //Act + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer { StoreId = "store1" }, null), CancellationToken.None); + //Assert + _loyaltyPointsServiceMock.Verify( + c => c.AddLoyaltyPointsHistory(It.IsAny(), 10, "store1", + "LoyaltyPoints.Message.EarnedForRegistration@store-lang", It.IsAny(), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task Handle_WithoutLanguageAndStoreDefault_WritesTheMessageInTheFirstStoreLanguage() + { + _storeServiceMock.Setup(c => c.GetStoreById("store1")).ReturnsAsync(new Store { Id = "store1" }); + _languageServiceMock.Setup(c => c.GetAllLanguages(false, "store1")) + .ReturnsAsync(new List { new() { Id = "first-lang" } }); + //Act + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer { StoreId = "store1" }, null), CancellationToken.None); + //Assert + _loyaltyPointsServiceMock.Verify( + c => c.AddLoyaltyPointsHistory(It.IsAny(), 10, "store1", + "LoyaltyPoints.Message.EarnedForRegistration@first-lang", It.IsAny(), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task Handle_WithoutLanguageAndDeletedStoreDefault_WritesTheMessageInTheFirstStoreLanguage() + { + _storeServiceMock.Setup(c => c.GetStoreById("store1")) + .ReturnsAsync(new Store { Id = "store1", DefaultLanguageId = "deleted-lang" }); + _languageServiceMock.Setup(c => c.GetAllLanguages(false, "store1")) + .ReturnsAsync(new List { new() { Id = "first-lang" } }); + //Act + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer { StoreId = "store1" }, null), CancellationToken.None); + //Assert + _loyaltyPointsServiceMock.Verify( + c => c.AddLoyaltyPointsHistory(It.IsAny(), 10, "store1", + "LoyaltyPoints.Message.EarnedForRegistration@first-lang", It.IsAny(), It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task Handle_WithoutAnyLanguage_AwardsThePointsWithAnEmptyMessage() + { + _storeServiceMock.Setup(c => c.GetStoreById("store1")).ReturnsAsync(new Store { Id = "store1" }); + _languageServiceMock.Setup(c => c.GetAllLanguages(It.IsAny(), It.IsAny())) + .ReturnsAsync(new List()); + //Act + await _customerRegisteredEventHandler.Handle( + new CustomerRegisteredEvent(new Customer { StoreId = "store1" }, null), CancellationToken.None); + //Assert + _loyaltyPointsServiceMock.Verify( + c => c.AddLoyaltyPointsHistory(It.IsAny(), 10, "store1", "", It.IsAny(), + It.IsAny()), Times.Once); + } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Events/ShoppingCart/CustomerLoggedInEventHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Events/ShoppingCart/CustomerLoggedInEventHandlerTests.cs index 483434ff51..cb7d8fd9e5 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Events/ShoppingCart/CustomerLoggedInEventHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Events/ShoppingCart/CustomerLoggedInEventHandlerTests.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Events.Customers; using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -25,27 +26,29 @@ public void Init() public async Task HandleTest() { //Arrange - var notification = new CustomerLoggedInEvent(new Customer(), new Customer(), ""); + var notification = new CustomerLoggedInEvent(new Customer(), new Customer(), "", new Language()); //Act await _customerLoggedInEventHandler.Handle(notification, CancellationToken.None); //Assert _shoppingCartServiceMock.Verify(c => c.MigrateShoppingCart(It.IsAny(), It.IsAny(), - It.IsAny(), true), + It.IsAny(), It.IsAny(), true), Times.Once); } [TestMethod] - public async Task Handle_MigratesFromThePreviousCustomerInTheEventStore() + public async Task Handle_MigratesFromThePreviousCustomerInTheEventStoreAndLanguage() { //Arrange var guest = new Customer(); var customer = new Customer(); + var language = new Language { Id = "lang-b" }; //Act - await _customerLoggedInEventHandler.Handle(new CustomerLoggedInEvent(customer, guest, "store-b"), + await _customerLoggedInEventHandler.Handle(new CustomerLoggedInEvent(customer, guest, "store-b", language), CancellationToken.None); //Assert - _shoppingCartServiceMock.Verify(c => c.MigrateShoppingCart(guest, customer, "store-b", true), Times.Once); + _shoppingCartServiceMock.Verify(c => c.MigrateShoppingCart(guest, customer, "store-b", language, true), + Times.Once); } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs index 7dabe6e627..fd042f560b 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs @@ -7,6 +7,7 @@ using Grand.Domain.Common; using Grand.Domain.Customers; using Grand.Domain.Directory; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Stores; using Grand.Domain.Tax; @@ -22,6 +23,7 @@ namespace Grand.Business.Checkout.Tests.Services.Orders; [TestClass] public class ShoppingCartServiceTests { + private static readonly Language Language = new() { Id = "lang1" }; private Mock _customerServiceMock; private Mock _mediatorMock; private Mock _productServiceMock; @@ -100,14 +102,15 @@ public async Task AddToCartTest_Success() Task.FromResult(new Product { Id = "1", Published = true, Price = 10 })); _shoppingCartValidatorMock.Setup(x => x.CheckCommonWarnings(It.IsAny(), It.IsAny>(), It.IsAny(), ShoppingCartType.ShoppingCart, null, null, - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); _shoppingCartValidatorMock.Setup(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); //Act - var result = await _shoppingCartService.AddToCart(customer, "2", ShoppingCartType.ShoppingCart, ""); + var result = await _shoppingCartService.AddToCart(customer, Language, "2", ShoppingCartType.ShoppingCart, ""); //Assert Assert.IsNotNull(result.shoppingCartItem); @@ -124,14 +127,15 @@ public async Task AddToCartTest_Warning() Task.FromResult(new Product { Id = "1", Published = true, Price = 10 })); _shoppingCartValidatorMock.Setup(x => x.CheckCommonWarnings(It.IsAny(), It.IsAny>(), It.IsAny(), ShoppingCartType.ShoppingCart, null, null, - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); _shoppingCartValidatorMock.Setup(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) .Returns(() => Task.FromResult((IList)new List { "Error" })); //Act - var result = await _shoppingCartService.AddToCart(customer, "2", ShoppingCartType.ShoppingCart, ""); + var result = await _shoppingCartService.AddToCart(customer, Language, "2", ShoppingCartType.ShoppingCart, ""); //Assert Assert.HasCount(1, result.warnings); @@ -148,15 +152,16 @@ public async Task UpdateShoppingCartItemTest() Task.FromResult(new Product { Id = "1", Published = true, Price = 10 })); _shoppingCartValidatorMock.Setup(x => x.CheckCommonWarnings(It.IsAny(), It.IsAny>(), It.IsAny(), ShoppingCartType.ShoppingCart, null, null, - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); _shoppingCartValidatorMock.Setup(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); - var item = await _shoppingCartService.AddToCart(customer, "2", ShoppingCartType.ShoppingCart, ""); + var item = await _shoppingCartService.AddToCart(customer, Language, "2", ShoppingCartType.ShoppingCart, ""); //Act - var result = await _shoppingCartService.UpdateShoppingCartItem(customer, item.shoppingCartItem.Id, "", + var result = await _shoppingCartService.UpdateShoppingCartItem(customer, Language, item.shoppingCartItem.Id, "", new List(), null); //Assert _customerServiceMock.Verify(x => x.UpdateShoppingCartItem(It.IsAny(), It.IsAny()), @@ -174,13 +179,14 @@ public async Task DeleteShoppingCartItemTest() Task.FromResult(new Product { Id = "1", Published = true, Price = 10 })); _shoppingCartValidatorMock.Setup(x => x.CheckCommonWarnings(It.IsAny(), It.IsAny>(), It.IsAny(), ShoppingCartType.ShoppingCart, null, null, - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); _shoppingCartValidatorMock.Setup(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); - var item = await _shoppingCartService.AddToCart(customer, "2", ShoppingCartType.ShoppingCart, ""); + var item = await _shoppingCartService.AddToCart(customer, Language, "2", ShoppingCartType.ShoppingCart, ""); //Act await _shoppingCartService.DeleteShoppingCartItem(customer, item.shoppingCartItem); //Assert @@ -199,19 +205,20 @@ public async Task MigrateShoppingCartTest() Task.FromResult(new Product { Id = "1", Published = true, Price = 10 })); _shoppingCartValidatorMock.Setup(x => x.CheckCommonWarnings(It.IsAny(), It.IsAny>(), It.IsAny(), ShoppingCartType.ShoppingCart, null, null, - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); _shoppingCartValidatorMock.Setup(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) .Returns(() => Task.FromResult((IList)new List())); _shoppingCartValidatorMock.Setup(x => x.GetRequiredProductWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); - var item = await _shoppingCartService.AddToCart(customer, "2", ShoppingCartType.ShoppingCart, ""); + var item = await _shoppingCartService.AddToCart(customer, Language, "2", ShoppingCartType.ShoppingCart, ""); var customer2 = new Customer(); //Act - await _shoppingCartService.MigrateShoppingCart(customer, customer2, "", false); + await _shoppingCartService.MigrateShoppingCart(customer, customer2, "", Language, false); //Assert Assert.IsNotEmpty(customer2.ShoppingCartItems); Assert.IsEmpty(customer.ShoppingCartItems); @@ -246,22 +253,23 @@ public async Task MigrateShoppingCart_DoesNotRequireRequiredProductsInTheTargetC .ReturnsAsync(new Product { Id = "A", Published = true }); _shoppingCartValidatorMock.Setup(x => x.CheckCommonWarnings(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), null, null, - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); _shoppingCartValidatorMock.Setup(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(new List()); _shoppingCartValidatorMock.Setup(x => x.GetRequiredProductWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); //Act - await _shoppingCartService.MigrateShoppingCart(guest, customer, "", false); + await _shoppingCartService.MigrateShoppingCart(guest, customer, "", Language, false); //Assert //the guest's whole cart moves together, so a required product may still be on its way to the target cart _shoppingCartValidatorMock.Verify(x => x.GetShoppingCartItemWarnings(customer, It.IsAny(), - It.IsAny(), It.Is(o => !o.GetRequiredProductWarnings)), Times.Once); + It.IsAny(), It.Is(o => !o.GetRequiredProductWarnings), It.IsAny()), Times.Once); } [TestMethod] @@ -275,16 +283,16 @@ public async Task MigrateShoppingCart_ChecksRequiredProductsAgainstTheSourceCart var customer = new Customer(); var product = new Product { Id = "A", Published = true, RequireOtherProducts = true, RequiredProductIds = "B" }; _productServiceMock.Setup(a => a.GetProductById("A", false)).ReturnsAsync(product); - _shoppingCartValidatorMock.Setup(x => x.GetRequiredProductWarnings(guest, item, product, "store")) + _shoppingCartValidatorMock.Setup(x => x.GetRequiredProductWarnings(guest, item, product, "store", Language)) .ReturnsAsync(new List { "Product B is required" }); //Act - await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false); + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", Language, false); //Assert Assert.IsEmpty(customer.ShoppingCartItems); _shoppingCartValidatorMock.Verify(x => x.GetShoppingCartItemWarnings(It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [TestMethod] @@ -296,7 +304,7 @@ public async Task MigrateShoppingCart_MovesCheckoutAttributesOfTheGivenStore() var toCustomer = new Customer(); //Act - await _shoppingCartService.MigrateShoppingCart(fromCustomer, toCustomer, "store-b", false); + await _shoppingCartService.MigrateShoppingCart(fromCustomer, toCustomer, "store-b", Language, false); //Assert _customerServiceMock.Verify(x => x.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes, @@ -313,7 +321,7 @@ public async Task MigrateShoppingCart_GuestWithoutCheckoutAttributes_KeepsTheAcc AddCheckoutAttributes(customer, "store", "gift-wrapping", "yes"); //Act - await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false); + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", Language, false); //Assert _customerServiceMock.Verify(x => x.UpdateUserField(It.IsAny(), @@ -331,7 +339,7 @@ public async Task MigrateShoppingCart_GuestWithCheckoutAttributes_ReplacesTheAcc AddCheckoutAttributes(customer, "store", "gift-wrapping", "yes"); //Act - await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false); + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", Language, false); //Assert _customerServiceMock.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.CheckoutAttributes, @@ -348,7 +356,7 @@ public async Task MigrateShoppingCart_AccountWithAttributesInAnotherStore_TakesT AddCheckoutAttributes(customer, "store-a", "gift-wrapping", "no"); //Act - await _shoppingCartService.MigrateShoppingCart(guest, customer, "store-b", false); + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store-b", Language, false); //Assert _customerServiceMock.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.CheckoutAttributes, diff --git a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartValidatorTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartValidatorTests.cs index b2503596ba..d9f2d0ac75 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartValidatorTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartValidatorTests.cs @@ -2,11 +2,13 @@ using FluentValidation.Results; using Grand.Business.Checkout.Services.Orders; using Grand.Business.Checkout.Validators; +using Grand.Business.Core.Events.Checkout.ShoppingCart; using Grand.Business.Core.Utilities.Checkout; using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; using Grand.Domain.Directory; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Stores; using Grand.Domain.Tax; @@ -21,7 +23,9 @@ namespace Grand.Business.Checkout.Tests.Services.Orders; [TestClass] public class ShoppingCartValidatorTests { + private static readonly Language Language = new() { Id = "lang1" }; private readonly List _records = []; + private Mock _mediatorMock; private ShoppingCartValidator _validator; private Mock _validatorFactoryMock; @@ -33,7 +37,8 @@ public void Init() Capture(); Capture(); Capture(); - _validator = new ShoppingCartValidator(new Mock().Object, _validatorFactoryMock.Object); + _mediatorMock = new Mock(); + _validator = new ShoppingCartValidator(_mediatorMock.Object, _validatorFactoryMock.Object); } private void Capture() @@ -54,17 +59,20 @@ public async Task GetShoppingCartWarnings_ValidatesForTheContextCustomerAndStore var cart = new List(); //Act - await _validator.GetShoppingCartWarnings(cart, new List(), context, true, true); + await _validator.GetShoppingCartWarnings(cart, new List(), context, Language, true, true); //Assert var warnings = _records.OfType().Single(); Assert.AreSame(context.Customer, warnings.Customer); Assert.AreSame(context.Store, warnings.Store); + Assert.AreSame(Language, warnings.Language); var attributes = _records.OfType().Single(); Assert.AreSame(context.Customer, attributes.Customer); Assert.AreSame(context.Store, attributes.Store); + Assert.AreSame(Language, attributes.Language); var amount = _records.OfType().Single(); Assert.AreSame(context, amount.Context); + Assert.AreSame(Language, amount.Language); } [TestMethod] @@ -75,11 +83,55 @@ public async Task GetRequiredProductWarnings_UsesTheGivenCustomerAndStore() var product = new Product { RequireOtherProducts = true }; //Act - await _validator.GetRequiredProductWarnings(customer, new ShoppingCartItem(), product, "store-b"); + await _validator.GetRequiredProductWarnings(customer, new ShoppingCartItem(), product, "store-b", Language); //Assert var record = _records.OfType().Single(); Assert.AreSame(customer, record.Customer); Assert.AreEqual("store-b", record.StoreId); + Assert.AreSame(Language, record.Language); } -} + + [TestMethod] + public async Task GetShoppingCartWarnings_PublishesTheWarningsWithTheLanguage() + { + //Arrange + var context = new CalculationContext(new Customer(), new Store { Id = "store" }, + new Currency { CurrencyCode = "EUR" }, TaxDisplayType.IncludingTax); + + //Act + await _validator.GetShoppingCartWarnings(new List(), null, context, Language, false, false); + + //Assert + _mediatorMock.Verify(x => x.Publish( + It.Is>(e => e.Language == Language), + It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task GetShoppingCartItemWarnings_PublishesTheWarningsWithTheLanguage() + { + //Act + await _validator.GetShoppingCartItemWarnings(new Customer(), new ShoppingCartItem(), new Product(), + NoChecks(), Language); + + //Assert + _mediatorMock.Verify(x => x.Publish( + It.Is>(e => e.Language == Language), + It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task GetShoppingCartItemWarnings_WithoutLanguage_Throws() + { + //Act & Assert + await Assert.ThrowsAsync(() => + _validator.GetShoppingCartItemWarnings(new Customer(), new ShoppingCartItem(), new Product(), NoChecks(), + null)); + } + + private static ShoppingCartValidatorOptions NoChecks() => new() { + GetStandardWarnings = false, GetInventoryWarnings = false, GetAttributesWarnings = false, + GetGiftVoucherWarnings = false, GetRequiredProductWarnings = false, GetReservationWarnings = false + }; +} \ No newline at end of file diff --git a/src/Tests/Grand.Business.Checkout.Tests/Validators/ShoppingCartValidatorsTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Validators/ShoppingCartValidatorsTests.cs index 259ffd2ffe..fdfc497d3b 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Validators/ShoppingCartValidatorsTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Validators/ShoppingCartValidatorsTests.cs @@ -9,6 +9,7 @@ using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Permissions; using Grand.Domain.Stores; @@ -20,6 +21,7 @@ namespace Grand.Business.Checkout.Tests.Validators; [TestClass] public class ShoppingCartValidatorsTests { + private static readonly Language Language = new() { Id = "lang1" }; private Mock _aclServiceMock; private Mock _checkoutAttributeParserMock; private Mock _checkoutAttributeServiceMock; @@ -42,7 +44,8 @@ public void Init() _productReservationServiceMock = new Mock(); _permissionServiceMock = new Mock(); _stockQuantityService = new StockQuantityService(); - _translationServiceMock.Setup(x => x.GetResource(It.IsAny())).Returns("Name"); + _translationServiceMock.Setup(x => x.GetResource(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())).Returns("Name"); } [TestMethod] @@ -52,7 +55,7 @@ public void ShoppingCartAuctionValidator_Success() var shoppingCartAuctionValidator = new ShoppingCartAuctionValidator(_translationServiceMock.Object); //Act var result = shoppingCartAuctionValidator.Validate(new ShoppingCartAuctionValidatorRecord(new Customer(), - new Product { AvailableEndDateTimeUtc = DateTime.UtcNow.AddDays(1) }, new ShoppingCartItem(), 10)); + new Product { AvailableEndDateTimeUtc = DateTime.UtcNow.AddDays(1) }, new ShoppingCartItem(), 10, Language)); //Assert Assert.IsTrue(result.IsValid); } @@ -65,7 +68,7 @@ public void ShoppingCartAuctionValidator_Fail() //Act var result = shoppingCartAuctionValidator.Validate(new ShoppingCartAuctionValidatorRecord(new Customer(), new Product { AvailableEndDateTimeUtc = DateTime.UtcNow.AddDays(1), StartPrice = 20 }, - new ShoppingCartItem(), 10)); + new ShoppingCartItem(), 10, Language)); //Assert Assert.IsFalse(result.IsValid); } @@ -85,7 +88,7 @@ public void ShoppingCartGiftVoucherValidator_Success() var result = shoppingCartGiftVoucherValidator.Validate(new ShoppingCartGiftVoucherValidatorRecord( new Customer(), new Product { GiftVoucherTypeId = GiftVoucherType.Virtual - }, new ShoppingCartItem { Attributes = customAttributes })); + }, new ShoppingCartItem { Attributes = customAttributes }, Language)); //Assert Assert.IsTrue(result.IsValid); } @@ -105,7 +108,7 @@ public void ShoppingCartGiftVoucherValidator_Fail_Wrong_SenderEmail() var result = shoppingCartGiftVoucherValidator.Validate(new ShoppingCartGiftVoucherValidatorRecord( new Customer(), new Product { GiftVoucherTypeId = GiftVoucherType.Virtual - }, new ShoppingCartItem { Attributes = customAttributes })); + }, new ShoppingCartItem { Attributes = customAttributes }, Language)); //Assert Assert.IsFalse(result.IsValid); } @@ -141,7 +144,8 @@ public async Task ShoppingCartCheckoutAttributesValidator_Success() new Customer(), new Store(), new List { new() }, - new List { new() { Key = "1", Value = "test" } } + new List { new() { Key = "1", Value = "test" } }, + Language )); //Assert @@ -181,7 +185,8 @@ public async Task ShoppingCartCheckoutAttributesValidator_Fail() new Customer(), new Store(), new List { new() }, - new List { new() { Key = "1", Value = "" } } + new List { new() { Key = "1", Value = "" } }, + Language )); //Assert @@ -202,7 +207,8 @@ public async Task ShoppingCartInventoryProductValidator_Success() Id = "1", StockQuantity = 1, OrderMinimumQuantity = 1, OrderMaximumQuantity = 10, ManageInventoryMethodId = ManageInventoryMethod.ManageStock }, - new ShoppingCartItem { ProductId = "1", Quantity = 1 } + new ShoppingCartItem { ProductId = "1", Quantity = 1 }, + Language )); //Assert @@ -223,7 +229,8 @@ public async Task ShoppingCartInventoryProductValidator_Fail() Id = "1", StockQuantity = 0, OrderMinimumQuantity = 1, OrderMaximumQuantity = 10, ManageInventoryMethodId = ManageInventoryMethod.ManageStock }, - new ShoppingCartItem { ProductId = "1", Quantity = 1 } + new ShoppingCartItem { ProductId = "1", Quantity = 1 }, + Language )); //Assert @@ -262,7 +269,8 @@ public async Task ShoppingCartItemAttributeValidator_Success() new Customer(), product, new ShoppingCartItem { ProductId = "1", Quantity = 1, Attributes = attributes }, - false + false, + Language )); //Assert @@ -301,7 +309,8 @@ public async Task ShoppingCartItemAttributeValidator_Fail() new Customer(), product, new ShoppingCartItem { ProductId = "1", Quantity = 1, Attributes = attributes }, - false + false, + Language )); //Assert @@ -341,7 +350,8 @@ public async Task ShoppingCartRequiredProductValidator_Success() "1", product, new ShoppingCartItem - { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" } + { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" }, + Language )); //Assert @@ -379,7 +389,8 @@ public async Task ShoppingCartRequiredProductValidator_Fail() "1", product, new ShoppingCartItem - { ProductId = "2", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" } + { ProductId = "2", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" }, + Language )); //Assert @@ -437,7 +448,8 @@ public async Task ShoppingCartReservationProductValidator_Success() StoreId = "1", RentalStartDateUtc = DateTime.UtcNow.AddDays(1).Date, RentalEndDateUtc = DateTime.UtcNow.AddDays(2).Date - } + }, + Language )); //Assert @@ -494,7 +506,8 @@ public async Task ShoppingCartReservationProductValidator_Fail() StoreId = "1", RentalStartDateUtc = DateTime.UtcNow.AddDays(1).Date, RentalEndDateUtc = DateTime.UtcNow.AddDays(2).Date - } + }, + Language )); //Assert @@ -538,7 +551,8 @@ public async Task ShoppingCartReservationProductValidator_ReservationsNotStoredA StoreId = "1", RentalStartDateUtc = DateTime.UtcNow.AddDays(1).Date, RentalEndDateUtc = DateTime.UtcNow.AddDays(3).Date - } + }, + Language )); //Assert @@ -573,7 +587,7 @@ public async Task ShoppingCartCommonWarningsValidator_Success() ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" } }, - product, ShoppingCartType.ShoppingCart, null, null, 1, "") + product, ShoppingCartType.ShoppingCart, null, null, 1, "", Language) ); //Assert @@ -608,7 +622,7 @@ public async Task ShoppingCartCommonWarningsValidator_Fail() ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" } }, - product, ShoppingCartType.ShoppingCart, null, null, 1, "") + product, ShoppingCartType.ShoppingCart, null, null, 1, "", Language) ); //Assert @@ -634,13 +648,36 @@ public async Task ShoppingCartStandardValidator_Success() customer, product, new ShoppingCartItem - { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" } + { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" }, + Language )); //Assert Assert.IsTrue(result.IsValid); } + [TestMethod] + public async Task ShoppingCartStandardValidator_WritesWarningsInTheGivenLanguage() + { + //Arrange + var shoppingCartStandardValidator = + new ShoppingCartStandardValidator(_translationServiceMock.Object, _aclServiceMock.Object); + _aclServiceMock.Setup(x => x.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); + _aclServiceMock.Setup(x => x.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); + _translationServiceMock.Setup(x => x.GetResource("ShoppingCart.ProductUnpublished", "lang-pl", It.IsAny(), + It.IsAny())).Returns("Produkt niedostępny"); + + //Act + var result = await shoppingCartStandardValidator.ValidateAsync(new ShoppingCartStandardValidatorRecord( + new Customer(), + new Product { Id = "1", Published = false }, + new ShoppingCartItem { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart }, + new Language { Id = "lang-pl" })); + + //Assert + Assert.AreEqual("Produkt niedostępny", result.Errors.Single().ErrorMessage); + } + [TestMethod] public async Task ShoppingCartStandardValidator_Fail() { @@ -660,7 +697,8 @@ public async Task ShoppingCartStandardValidator_Fail() customer, product, new ShoppingCartItem - { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" } + { ProductId = "1", Quantity = 1, ShoppingCartTypeId = ShoppingCartType.ShoppingCart, StoreId = "1" }, + Language )); //Assert @@ -696,7 +734,8 @@ public async Task ShoppingCartWarningsValidator_Success() new Store(), new List { new() { ProductId = "1", Quantity = 1, Attributes = attributes } - } + }, + Language )); //Assert @@ -736,7 +775,8 @@ public async Task ShoppingCartWarningsValidator_Fail() new List { new() { ProductId = "1", Quantity = 1, Attributes = attributes }, new() { ProductId = "2", Quantity = 1, Attributes = attributes } - } + }, + Language )); //Assert diff --git a/src/Tests/Grand.Business.Customers.Tests/Handler/CustomerLoggedInNotificationHandlerTests.cs b/src/Tests/Grand.Business.Customers.Tests/Handler/CustomerLoggedInNotificationHandlerTests.cs index 879f426e88..b70dcc918e 100644 --- a/src/Tests/Grand.Business.Customers.Tests/Handler/CustomerLoggedInNotificationHandlerTests.cs +++ b/src/Tests/Grand.Business.Customers.Tests/Handler/CustomerLoggedInNotificationHandlerTests.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Customers.Events.Handlers; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -29,7 +30,8 @@ public async Task HandleTest() CannotLoginUntilDateUtc = DateTime.Now }; //Act - await _handler.Handle(new CustomerLoggedInEvent(customer, new Customer(), ""), CancellationToken.None); + await _handler.Handle(new CustomerLoggedInEvent(customer, new Customer(), "", new Language()), + CancellationToken.None); //Assert Assert.AreEqual(0, customer.FailedLoginAttempts); diff --git a/src/Tests/Grand.Modules.Tests/Plugins/OpenIdConnect/OidcLoginControllerTests.cs b/src/Tests/Grand.Modules.Tests/Plugins/OpenIdConnect/OidcLoginControllerTests.cs index cdef82939b..214a2b85fe 100644 --- a/src/Tests/Grand.Modules.Tests/Plugins/OpenIdConnect/OidcLoginControllerTests.cs +++ b/src/Tests/Grand.Modules.Tests/Plugins/OpenIdConnect/OidcLoginControllerTests.cs @@ -10,6 +10,7 @@ using Grand.Business.Core.Utilities.Authentication; using Grand.Domain.Common; using Grand.Domain.Customers; +using Grand.Domain.Localization; using Grand.Domain.Stores; using Grand.Infrastructure; using Grand.Mediator; @@ -30,6 +31,7 @@ namespace Grand.Modules.Tests.Plugins.OpenIdConnect; [TestClass] public class OidcLoginControllerTests { + private static readonly Language Language = new() { Id = "lang1" }; private static readonly ExternalRegistrationIdentity Pending = new(OidcLoginDefaults.ProviderSystemName, "sub-1", "john@example.com", true, "John", "Doe"); @@ -85,6 +87,7 @@ public void Init() _current = new Customer { Id = "c1", Email = "John@Example.com" }; var work = new Mock(); work.Setup(x => x.CurrentCustomer).Returns(() => _current); + work.Setup(x => x.WorkingLanguage).Returns(Language); var store = new Mock(); store.Setup(x => x.CurrentStore).Returns(new Store { Id = "s1" }); var ctx = new Mock(); @@ -165,10 +168,10 @@ public async Task Callback_SignIn_SignsInClearsRedirects() { var customer = new Customer { Id = "c9" }; _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin("sub-1", "john@example.com", true, "s1")).ReturnsAsync(OidcLoginResult.SignIn(customer)); + _resolver.Setup(x => x.ResolveForLogin("sub-1", "john@example.com", true, "s1", Language)).ReturnsAsync(OidcLoginResult.SignIn(customer)); var result = await _controller.Callback("/cart"); _auth.Verify(x => x.SignIn(customer, false, "session-1"), Times.Once); - _mediator.Verify(x => x.Publish(It.Is(e => e.Customer == customer), It.IsAny()), Times.Once); + _mediator.Verify(x => x.Publish(It.Is(e => e.Customer == customer && e.Language == Language), It.IsAny()), Times.Once); _external.Verify(x => x.Clear(), Times.Once); Assert.AreEqual("/cart", ((RedirectResult)result).Url); } @@ -177,7 +180,7 @@ public async Task Callback_SignIn_SignsInClearsRedirects() public async Task Callback_RegistrationRequired_ToRegister_PendingKept() { _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.RegistrationRequired()); Assert.AreEqual("Register", ((RedirectToRouteResult)await _controller.Callback("/")).RouteName); _external.Verify(x => x.Clear(), Times.Never); @@ -187,7 +190,7 @@ public async Task Callback_RegistrationRequired_ToRegister_PendingKept() public async Task Callback_LinkRequired_ToLink() { _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.LinkRequired(new Customer())); Assert.AreEqual("OidcLoginLink", ((RedirectToRouteResult)await _controller.Callback("/")).RouteName); } @@ -196,7 +199,7 @@ public async Task Callback_LinkRequired_ToLink() public async Task Callback_Refused_ClearsAndFailsWithReason() { _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.Refused(OidcLoginRefusal.AccountInactive)); var result = (RedirectToRouteResult)await _controller.Callback("/"); Assert.AreEqual("OidcLoginFailed", result.RouteName); @@ -289,7 +292,7 @@ public async Task Callback_MethodInactive_ClearsAndFails_NothingResolved() _externalAuth.Setup(x => x.AuthenticationProviderIsAvailable(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); Assert.AreEqual("OidcLoginFailed", ((RedirectToRouteResult)await _controller.Callback("/")).RouteName); _external.Verify(x => x.Clear(), Times.Once); - _resolver.Verify(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + _resolver.Verify(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -299,7 +302,7 @@ public async Task Callback_OtherProviderPending_ClearsAndFails_NothingResolved() _external.Setup(x => x.GetPending()).ReturnsAsync(Pending with { ProviderSystemName = "Other" }); Assert.AreEqual("OidcLoginFailed", ((RedirectToRouteResult)await _controller.Callback("/")).RouteName); _external.Verify(x => x.Clear(), Times.Once); - _resolver.Verify(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + _resolver.Verify(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -333,7 +336,7 @@ public async Task Callback_SignIn_TwoFactorCustomer_SignsInWithoutStoreCode() var customer = new Customer { Id = "c9", Email = "john@example.com" }; customer.UserFields.Add(new UserField { Key = SystemCustomerFieldNames.TwoFactorEnabled, Value = "True", StoreId = "" }); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); Assert.AreEqual("/cart", ((RedirectResult)await _controller.Callback("/cart")).Url); _auth.Verify(x => x.SignIn(customer, false, "session-1"), Times.Once); @@ -344,7 +347,7 @@ public async Task Callback_SignIn_OtherAccountSignedIn_ClearsAndFails_SessionKep { _groups.Setup(x => x.IsRegistered(_current)).ReturnsAsync(true); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(new Customer { Id = "other" })); var result = (RedirectToRouteResult)await _controller.Callback("/"); Assert.AreEqual("OidcLoginFailed", result.RouteName); @@ -360,7 +363,7 @@ public async Task Callback_SameCustomerSignedIn_ReplacesCookieWithSession() { _groups.Setup(x => x.IsRegistered(_current)).ReturnsAsync(true); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(_current)); var result = (RedirectResult)await _controller.Callback("/account"); @@ -375,7 +378,7 @@ public async Task Callback_RegistrationRequired_SignedIn_ClearsAndFails() { _groups.Setup(x => x.IsRegistered(_current)).ReturnsAsync(true); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.RegistrationRequired()); var result = (RedirectToRouteResult)await _controller.Callback("/"); Assert.AreEqual("OidcLoginFailed", result.RouteName); @@ -388,7 +391,7 @@ public async Task Callback_LinkRequired_SignedIn_StillToLink() { _groups.Setup(x => x.IsRegistered(_current)).ReturnsAsync(true); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.LinkRequired(new Customer { Id = "other" })); Assert.AreEqual("OidcLoginLink", ((RedirectToRouteResult)await _controller.Callback("/")).RouteName); } @@ -462,7 +465,7 @@ public async Task Callback_SignIn_StartsSessionWithTokensOfTheSignIn() { var customer = new Customer { Id = "c2" }; _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin("sub-1", "john@example.com", true, "s1")).ReturnsAsync(OidcLoginResult.SignIn(customer)); + _resolver.Setup(x => x.ResolveForLogin("sub-1", "john@example.com", true, "s1", Language)).ReturnsAsync(OidcLoginResult.SignIn(customer)); await _controller.Callback("/"); @@ -476,7 +479,7 @@ public async Task Callback_SessionNotStarted_FailedPage_NotSignedIn() { var customer = new Customer { Id = "c2" }; _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); _sessions.Setup(x => x.Start(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((string)null); @@ -502,7 +505,7 @@ public async Task Callback_Signin_ExternalItemsMissing_StartsSessionWithoutToken var customer = new Customer { Id = "c2" }; _externalItems.Clear(); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); await _controller.Callback("/"); _sessions.Verify(x => x.Start(customer, "sub-1", null, null, "s1"), Times.Once); @@ -513,7 +516,7 @@ public async Task Callback_SignIn_ExternalReturnUrl_GoesHome() { var customer = new Customer { Id = "c9" }; _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); Assert.AreEqual("HomePage", ((RedirectToRouteResult)await _controller.Callback("https://evil.example/")).RouteName); } @@ -594,7 +597,7 @@ public async Task Callback_ProviderOnly_SignIn_SyncsProfile() _loginConfig.SignInMode = OidcSignInMode.ProviderOnly; var customer = new Customer { Id = "c9", Email = "john@example.com" }; _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); await _controller.Callback("/"); _customers.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.FirstName, "John", ""), Times.Once); @@ -610,7 +613,7 @@ public async Task Callback_SyncThrows_StillSignsIn() _customers.Setup(x => x.UpdateUserField(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new InvalidOperationException()); _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); await _controller.Callback("/"); _auth.Verify(x => x.SignIn(customer, false, "session-1"), Times.Once); @@ -621,7 +624,7 @@ public async Task Callback_Mixed_SignIn_NoSync() { var customer = new Customer { Id = "c9", Email = "john@example.com" }; _external.Setup(x => x.GetPending()).ReturnsAsync(Pending); - _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _resolver.Setup(x => x.ResolveForLogin(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(OidcLoginResult.SignIn(customer)); await _controller.Callback("/"); _customers.Verify(x => x.UpdateUserField(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); diff --git a/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs b/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs index e66c04b438..86c3111623 100644 --- a/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs +++ b/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Checkout.Orders; +using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Interfaces.Messages; using Grand.Business.Core.Utilities.Checkout; @@ -21,6 +22,7 @@ public class EndAuctionsTaskTests { private Mock _auctionMock; private Mock _customerServiceMock; + private Mock _languageServiceMock; private Mock> _loggerMock; private Mock _messageProviderMock; private LanguageSettings _settings; @@ -35,9 +37,12 @@ public void Init() _shoppingCartMock = new Mock(); _customerServiceMock = new Mock(); _loggerMock = new Mock>(); - _settings = new LanguageSettings(); + _settings = new LanguageSettings { DefaultAdminLanguageId = "admin-lang" }; + _languageServiceMock = new Mock(); + _languageServiceMock.Setup(c => c.GetLanguageById("admin-lang")) + .ReturnsAsync(new Language { Id = "admin-lang" }); _task = new EndAuctionsTask(_auctionMock.Object, _messageProviderMock.Object, _settings, - _shoppingCartMock.Object, _customerServiceMock.Object, + _shoppingCartMock.Object, _customerServiceMock.Object, _languageServiceMock.Object, _loggerMock.Object); } @@ -62,7 +67,8 @@ public async Task Execute_HasWarnings_InvokeExpectedMethos() _auctionMock.Setup(c => c.GetAuctionsToEnd()).ReturnsAsync(new List { new() { Id = "id" } }); _auctionMock.Setup(c => c.GetBidsByProductId(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(() => new PagedList { new() }); - _shoppingCartMock.Setup(c => c.AddToCart(It.IsAny(), It.IsAny(), It.IsAny(), + _shoppingCartMock.Setup(c => c.AddToCart(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -71,6 +77,13 @@ public async Task Execute_HasWarnings_InvokeExpectedMethos() .ReturnsAsync((new List { "warning" }, null)); await _task.Execute(); + //the warnings only reach the log, so they are written in the admin language + _shoppingCartMock.Verify(c => c.AddToCart(It.IsAny(), + It.Is(l => l.Id == "admin-lang"), It.IsAny(), + It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); _loggerMock.Verify(c => c.Log( It.IsAny(), It.IsAny(), @@ -85,7 +98,8 @@ public async Task Execute_Valid_InvokeExpectedMethos() _auctionMock.Setup(c => c.GetAuctionsToEnd()).ReturnsAsync(new List { new() { Id = "id" } }); _auctionMock.Setup(c => c.GetBidsByProductId(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(() => new PagedList { new() }); - _shoppingCartMock.Setup(c => c.AddToCart(It.IsAny(), It.IsAny(), It.IsAny(), + _shoppingCartMock.Setup(c => c.AddToCart(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -112,4 +126,46 @@ public async Task Execute_Valid_InvokeExpectedMethos() c => c.SendAuctionEndedLostCustomerMessage(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } + + [TestMethod] + public async Task Execute_DeletedAdminLanguage_UsesAPublishedLanguage() + { + _languageServiceMock.Setup(c => c.GetLanguageById("admin-lang")).ReturnsAsync((Language)null); + _languageServiceMock.Setup(c => c.GetAllLanguages(false, "")) + .ReturnsAsync(new List { new() { Id = "published-lang" } }); + _auctionMock.Setup(c => c.GetAuctionsToEnd()).ReturnsAsync(new List { new() { Id = "id" } }); + _auctionMock.Setup(c => c.GetBidsByProductId(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => new PagedList { new() }); + _shoppingCartMock.Setup(c => c.AddToCart(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .ReturnsAsync((new List(), null)); + + await _task.Execute(); + + _shoppingCartMock.Verify(c => c.AddToCart(It.IsAny(), + It.Is(l => l.Id == "published-lang"), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task Execute_NoLanguageAtAll_SkipsTheBidWithoutThrowing() + { + _languageServiceMock.Setup(c => c.GetLanguageById("admin-lang")).ReturnsAsync((Language)null); + _languageServiceMock.Setup(c => c.GetAllLanguages(It.IsAny(), It.IsAny())) + .ReturnsAsync(new List()); + _auctionMock.Setup(c => c.GetAuctionsToEnd()).ReturnsAsync(new List { new() { Id = "id" } }); + _auctionMock.Setup(c => c.GetBidsByProductId(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => new PagedList { new() }); + + await _task.Execute(); + + _shoppingCartMock.VerifyNoOtherCalls(); + _auctionMock.Verify(c => c.UpdateAuctionEnded(It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + } } \ No newline at end of file diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs index fad33c836c..57e0912557 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs @@ -222,7 +222,8 @@ protected async Task SignInAction(Customer customer, bool createP //raise event await _mediator.Publish(new CustomerLoggedInEvent(customer, - _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id)); + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id, + _contextAccessor.WorkContext.WorkingLanguage)); return RedirectToRoute($"{GetCurrentArea()}Index"); } diff --git a/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs index 0d1319c084..d3b2154665 100644 --- a/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs @@ -915,7 +915,7 @@ public virtual async Task> UpdateCart(Customer customer, string sh if (cart != null) return await _httpContextAccessor.HttpContext!.RequestServices.GetRequiredService() .UpdateShoppingCartItem( - customer, + customer, _contextAccessor.WorkContext.WorkingLanguage, shoppingCartId, cart.WarehouseId, cart.Attributes, diff --git a/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs index ab9b76fbbf..5ca2a698be 100644 --- a/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs @@ -1158,10 +1158,11 @@ public virtual async Task> AddProductToOrderDetails(AddProductToOr }; warnings.AddRange( - await _shoppingCartValidator.GetShoppingCartItemAttributeWarnings(customer, product, shoppingCartItem)); + await _shoppingCartValidator.GetShoppingCartItemAttributeWarnings(customer, product, shoppingCartItem, + _contextAccessor.WorkContext.WorkingLanguage)); warnings.AddRange( await _shoppingCartValidator.GetShoppingCartItemGiftVoucherWarnings(customer, product, - shoppingCartItem)); + shoppingCartItem, _contextAccessor.WorkContext.WorkingLanguage)); if (warnings.Count == 0) { //the description is stored on the order, so it is rendered for the order, not for the admin diff --git a/src/Web/Grand.Web/Controllers/AccountController.cs b/src/Web/Grand.Web/Controllers/AccountController.cs index 101fa48163..6314695ff5 100644 --- a/src/Web/Grand.Web/Controllers/AccountController.cs +++ b/src/Web/Grand.Web/Controllers/AccountController.cs @@ -358,7 +358,8 @@ private async Task SignInAction(Customer customer, bool createPer { //raise event await _mediator.Publish(new CustomerLoggedInEvent(customer, - _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id)); + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id, + _contextAccessor.WorkContext.WorkingLanguage)); //sign in new customer await _authenticationService.SignIn(customer, createPersistentCookie); @@ -613,7 +614,8 @@ await _mediator.Send(new CustomerRegisteredCommand { await _authenticationService.SignIn(_contextAccessor.WorkContext.CurrentCustomer, true); //raise event - await _mediator.Publish(new CustomerRegisteredEvent(_contextAccessor.WorkContext.CurrentCustomer)); + await _mediator.Publish(new CustomerRegisteredEvent(_contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.WorkContext.WorkingLanguage)); IActionResult result; switch (registrationType) diff --git a/src/Web/Grand.Web/Controllers/ActionCartController.cs b/src/Web/Grand.Web/Controllers/ActionCartController.cs index 6bb4f3b5ea..24b7566ae0 100644 --- a/src/Web/Grand.Web/Controllers/ActionCartController.cs +++ b/src/Web/Grand.Web/Controllers/ActionCartController.cs @@ -201,7 +201,7 @@ await _shoppingCartService.FindShoppingCartItem(cart, model.ShoppingCartTypeId, Quantity = quantityToValidate }, product, new ShoppingCartValidatorOptions { GetRequiredProductWarnings = false - }); + }, _contextAccessor.WorkContext.WorkingLanguage); if (addToCartWarnings.Any()) //cannot be added to the cart @@ -212,7 +212,7 @@ await _shoppingCartService.FindShoppingCartItem(cart, model.ShoppingCartTypeId, } //try adding product to the cart - var addToCart = await _shoppingCartService.AddToCart(customer, + var addToCart = await _shoppingCartService.AddToCart(customer, _contextAccessor.WorkContext.WorkingLanguage, product.Id, model.ShoppingCartTypeId, _contextAccessor.StoreContext.CurrentStore.Id, @@ -402,6 +402,7 @@ public virtual async Task> AddProductDetails(Pr //add to the cart var (warnings, shoppingCartItem) = await _shoppingCartService.AddToCart(_contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.WorkContext.WorkingLanguage, product.Id, model.ShoppingCartTypeId, _contextAccessor.StoreContext.CurrentStore.Id, warehouseId, attributes, customerEnteredPriceConverted, rentalStartDate, rentalEndDate, model.EnteredQuantity, true, model.Reservation, parameter, duration, @@ -642,8 +643,9 @@ await _currencyService.ConvertToPrimaryStoreCurrency(customerEnteredPrice, //update existing item addToCartWarnings.AddRange(await _shoppingCartService.UpdateShoppingCartItem(_contextAccessor.WorkContext.CurrentCustomer, - cart.Id, warehouseId, attributes, customerEnteredPriceConverted, - rentalStartDate, rentalEndDate, model.EnteredQuantity)); + _contextAccessor.WorkContext.WorkingLanguage, + cart.Id, warehouseId, attributes, + customerEnteredPriceConverted, rentalStartDate, rentalEndDate, model.EnteredQuantity)); if (addToCartWarnings.Any()) //cannot be updated the cart/wishlist @@ -756,8 +758,10 @@ public virtual async Task> AddBid(AddBidModel model, Quantity = 1 }; - var warnings = (await _shoppingCartValidator.GetStandardWarnings(customer, product, shoppingCartItem)).ToList(); - warnings.AddRange(await _shoppingCartValidator.GetAuctionProductWarning(bid, product, customer)); + var language = _contextAccessor.WorkContext.WorkingLanguage; + var warnings = (await _shoppingCartValidator.GetStandardWarnings(customer, product, shoppingCartItem, language)) + .ToList(); + warnings.AddRange(await _shoppingCartValidator.GetAuctionProductWarning(bid, product, customer, language)); if (warnings.Any()) { diff --git a/src/Web/Grand.Web/Controllers/CheckoutController.cs b/src/Web/Grand.Web/Controllers/CheckoutController.cs index 72d3d3cca3..3494ce8927 100644 --- a/src/Web/Grand.Web/Controllers/CheckoutController.cs +++ b/src/Web/Grand.Web/Controllers/CheckoutController.cs @@ -103,7 +103,7 @@ public virtual async Task Index() //validation (cart) var checkoutAttributes = customer.GetUserFieldFromEntity>(SystemCustomerFieldNames.CheckoutAttributes, _contextAccessor.StoreContext.CurrentStore.Id); var scWarnings = await _shoppingCartValidator.GetShoppingCartWarnings(cart, checkoutAttributes, - _contextAccessor.CalculationContext(), true, true); + _contextAccessor.CalculationContext(), _contextAccessor.WorkContext.WorkingLanguage, true, true); if (scWarnings.Any()) return RedirectToRoute("ShoppingCart", new { checkoutAttributes = true }); @@ -113,7 +113,7 @@ public virtual async Task Index() var product = await _productService.GetProductById(sci.ProductId); var sciWarnings = await _shoppingCartValidator.GetShoppingCartItemWarnings(customer, sci, product, - new ShoppingCartValidatorOptions()); + new ShoppingCartValidatorOptions(), _contextAccessor.WorkContext.WorkingLanguage); if (sciWarnings.Any()) return RedirectToRoute("ShoppingCart", new { checkoutAttributes = true }); } @@ -144,7 +144,7 @@ public virtual async Task Start() .GetUserFieldFromEntity>(SystemCustomerFieldNames.CheckoutAttributes, _contextAccessor.StoreContext.CurrentStore.Id); var cartWarnings = await _shoppingCartValidator.GetShoppingCartWarnings(cart, checkoutAttributes, - _contextAccessor.CalculationContext(), true, true); + _contextAccessor.CalculationContext(), _contextAccessor.WorkContext.WorkingLanguage, true, true); if (cartWarnings.Any()) return RedirectToRoute("ShoppingCart", new { checkoutAttributes = true }); @@ -153,7 +153,7 @@ public virtual async Task Start() { var product = await _productService.GetProductById(sci.ProductId); var sciWarnings = await _shoppingCartValidator.GetShoppingCartItemWarnings(_contextAccessor.WorkContext.CurrentCustomer, - sci, product, new ShoppingCartValidatorOptions()); + sci, product, new ShoppingCartValidatorOptions(), _contextAccessor.WorkContext.WorkingLanguage); if (sciWarnings.Any()) return RedirectToRoute("ShoppingCart", new { checkoutAttributes = true }); } diff --git a/src/Web/Grand.Web/Controllers/OrderController.cs b/src/Web/Grand.Web/Controllers/OrderController.cs index 9e4cf9c131..22410d0eb5 100644 --- a/src/Web/Grand.Web/Controllers/OrderController.cs +++ b/src/Web/Grand.Web/Controllers/OrderController.cs @@ -164,7 +164,10 @@ public virtual async Task ReOrder(string orderId) if (!await order.Access(_contextAccessor.WorkContext.CurrentCustomer, _groupService)) return Challenge(); - var warnings = await _mediator.Send(new ReOrderCommand { Order = order }); + var warnings = await _mediator.Send(new ReOrderCommand { + Order = order, + Language = _contextAccessor.WorkContext.WorkingLanguage + }); if (warnings.Any()) Notification(NotifyType.Error, string.Join(",", warnings), true); diff --git a/src/Web/Grand.Web/Controllers/ShoppingCartController.cs b/src/Web/Grand.Web/Controllers/ShoppingCartController.cs index 2a2e609fc4..3cab9ba40f 100644 --- a/src/Web/Grand.Web/Controllers/ShoppingCartController.cs +++ b/src/Web/Grand.Web/Controllers/ShoppingCartController.cs @@ -323,6 +323,7 @@ public virtual async Task UpdateQuantity(UpdateQuantityModel mode if (cart != null) { var currSciWarnings = await _shoppingCartService.UpdateShoppingCartItem(_contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.WorkContext.WorkingLanguage, cart.Id, cart.WarehouseId, cart.Attributes, cart.EnteredPrice, cart.RentalStartDateUtc, cart.RentalEndDateUtc, model.Quantity); @@ -473,7 +474,7 @@ public virtual async Task StartCheckout(CheckoutAttributeSelected var checkoutAttributeWarnings = await _shoppingCartValidator.GetShoppingCartWarnings(cart, checkoutAttributes, - _contextAccessor.CalculationContext(), true, true); + _contextAccessor.CalculationContext(), _contextAccessor.WorkContext.WorkingLanguage, true, true); if (checkoutAttributeWarnings.Any()) return RedirectToRoute("ShoppingCart", new { checkoutAttributes = true }); //everything is OK diff --git a/src/Web/Grand.Web/Controllers/WishlistController.cs b/src/Web/Grand.Web/Controllers/WishlistController.cs index 458f171a6f..4456f6b92a 100644 --- a/src/Web/Grand.Web/Controllers/WishlistController.cs +++ b/src/Web/Grand.Web/Controllers/WishlistController.cs @@ -123,8 +123,9 @@ public virtual async Task UpdateQuantity(UpdateQuantityModel mode if (cart != null) { var currSciWarnings = await _shoppingCartService.UpdateShoppingCartItem( - _contextAccessor.WorkContext.CurrentCustomer, - cart.Id, cart.WarehouseId, cart.Attributes, cart.EnteredPrice, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.WorkContext.WorkingLanguage, + cart.Id, cart.WarehouseId, cart.Attributes, + cart.EnteredPrice, cart.RentalStartDateUtc, cart.RentalEndDateUtc, model.Quantity); warnings.AddRange(currSciWarnings); @@ -169,8 +170,10 @@ public virtual async Task AddItemToCartFromWishlist(AddCartFromWi return Json(new { success = false, message = "Shopping cart ident not found" }); var warnings = (await _shoppingCartService.AddToCart(_contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.WorkContext.WorkingLanguage, itemCart.ProductId, ShoppingCartType.ShoppingCart, - _contextAccessor.StoreContext.CurrentStore.Id, itemCart.WarehouseId, + _contextAccessor.StoreContext.CurrentStore.Id, + itemCart.WarehouseId, itemCart.Attributes, itemCart.EnteredPrice, itemCart.RentalStartDateUtc, itemCart.RentalEndDateUtc, itemCart.Quantity, validator: new ShoppingCartValidatorOptions { GetRequiredProductWarnings = false })).warnings; diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs index 3d549b7822..e275700406 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs @@ -206,7 +206,7 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSu //cart warnings var cartWarnings = await _shoppingCartValidator.GetShoppingCartWarnings(request.Cart, checkoutAttributes, - request.CalculationContext(), + request.CalculationContext(), request.Language, request.ValidateCheckoutAttributes, true); foreach (var warning in cartWarnings) model.Warnings.Add(warning); @@ -511,7 +511,7 @@ private async Task PrepareCartItems(ShoppingCartModel model, GetShoppingCart req //item warnings var itemWarnings = await _shoppingCartValidator.GetShoppingCartItemWarnings(request.Customer, sci, product, - new ShoppingCartValidatorOptions()); + new ShoppingCartValidatorOptions(), request.Language); foreach (var warning in itemWarnings) cartItemModel.Warnings.Add(warning); diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs index 139b2d6859..2b2c069a6b 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs @@ -94,7 +94,7 @@ public async Task Handle(GetWishlist request, CancellationToken c //cart warnings var cartWarnings = await _shoppingCartValidator.GetShoppingCartWarnings(request.Cart, null, - request.CalculationContext(), false, false); + request.CalculationContext(), request.Language, false, false); foreach (var warning in cartWarnings) model.Warnings.Add(warning); @@ -188,7 +188,7 @@ public async Task Handle(GetWishlist request, CancellationToken c //item warnings var itemWarnings = await _shoppingCartValidator.GetShoppingCartItemWarnings(request.Customer, sci, product, - new ShoppingCartValidatorOptions()); + new ShoppingCartValidatorOptions(), request.Language); foreach (var warning in itemWarnings) cartItemModel.Warnings.Add(warning);