diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs index 47d4e6349..ba0a992d3 100644 --- a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs +++ b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartService.cs @@ -392,7 +392,9 @@ public virtual async Task DeleteShoppingCartItem(Customer customer, ShoppingCart /// /// From customer /// To customer - /// Store whose selected checkout attributes move along + /// + /// Store whose selected checkout attributes move along, when the source customer has selected any there + /// /// /// A value indicating whether to coupon codes (discount and gift voucher) should be also /// re-applied @@ -440,10 +442,13 @@ await AddToCart(toCustomer, sci.ProductId, sci.ShoppingCartTypeId, sci.StoreId, await _customerService.UpdateUserField(toCustomer, SystemCustomerFieldNames.GiftVoucherCoupons, resultGift); } - //move selected checkout attributes + //move selected checkout attributes; the source customer's selection is the most recent one, + //but when it has none, the target keeps its own, so logging in from a fresh browser + //does not clear a choice saved on the account var checkoutAttributes = fromCustomer.GetUserFieldFromEntity>(SystemCustomerFieldNames.CheckoutAttributes, storeId); - await _customerService.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes, checkoutAttributes, - storeId); + if (checkoutAttributes is { Count: > 0 }) + await _customerService.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes, checkoutAttributes, + storeId); } #endregion 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 9c883d8dc..7dabe6e62 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/ShoppingCartServiceTests.cs @@ -11,9 +11,11 @@ using Grand.Domain.Stores; using Grand.Domain.Tax; using Grand.Infrastructure; +using Grand.Infrastructure.TypeConverters; using Grand.Mediator; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using System.ComponentModel; namespace Grand.Business.Checkout.Tests.Services.Orders; @@ -27,6 +29,13 @@ public class ShoppingCartServiceTests private ShoppingCartSettings _shoppingCartSettings; private Mock _shoppingCartValidatorMock; + [ClassInitialize] + public static void InitClass(TestContext context) + { + //user fields are stored as strings; the converters read them back as typed values + new WebTypeConverter().Register(); + } + [TestInitialize] public void Init() { @@ -283,6 +292,7 @@ public async Task MigrateShoppingCart_MovesCheckoutAttributesOfTheGivenStore() { //Arrange var fromCustomer = new Customer(); + AddCheckoutAttributes(fromCustomer, "store-b", "gift-wrapping", "yes"); var toCustomer = new Customer(); //Act @@ -290,6 +300,68 @@ public async Task MigrateShoppingCart_MovesCheckoutAttributesOfTheGivenStore() //Assert _customerServiceMock.Verify(x => x.UpdateUserField(toCustomer, SystemCustomerFieldNames.CheckoutAttributes, - It.IsAny>(), "store-b"), Times.Once); + It.Is>(a => a.Count == 1 && a[0].Key == "gift-wrapping" && a[0].Value == "yes"), + "store-b"), Times.Once); + } + + [TestMethod] + public async Task MigrateShoppingCart_GuestWithoutCheckoutAttributes_KeepsTheAccountAttributes() + { + //Arrange + var guest = new Customer(); + var customer = new Customer(); + AddCheckoutAttributes(customer, "store", "gift-wrapping", "yes"); + + //Act + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false); + + //Assert + _customerServiceMock.Verify(x => x.UpdateUserField(It.IsAny(), + SystemCustomerFieldNames.CheckoutAttributes, It.IsAny>(), It.IsAny()), + Times.Never); + } + + [TestMethod] + public async Task MigrateShoppingCart_GuestWithCheckoutAttributes_ReplacesTheAccountAttributes() + { + //Arrange + var guest = new Customer(); + AddCheckoutAttributes(guest, "store", "gift-wrapping", "no"); + var customer = new Customer(); + AddCheckoutAttributes(customer, "store", "gift-wrapping", "yes"); + + //Act + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store", false); + + //Assert + _customerServiceMock.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.CheckoutAttributes, + It.Is>(a => a.Count == 1 && a[0].Value == "no"), "store"), Times.Once); + } + + [TestMethod] + public async Task MigrateShoppingCart_AccountWithAttributesInAnotherStore_TakesTheGuestAttributes() + { + //Arrange + var guest = new Customer(); + AddCheckoutAttributes(guest, "store-b", "gift-wrapping", "yes"); + var customer = new Customer(); + AddCheckoutAttributes(customer, "store-a", "gift-wrapping", "no"); + + //Act + await _shoppingCartService.MigrateShoppingCart(guest, customer, "store-b", false); + + //Assert + _customerServiceMock.Verify(x => x.UpdateUserField(customer, SystemCustomerFieldNames.CheckoutAttributes, + It.Is>(a => a.Count == 1 && a[0].Value == "yes"), "store-b"), Times.Once); + } + + private static void AddCheckoutAttributes(Customer customer, string storeId, string key, string value) + { + customer.UserFields.Add(new UserField { + Key = SystemCustomerFieldNames.CheckoutAttributes, + Value = TypeDescriptor.GetConverter(typeof(List)) + .ConvertToInvariantString(new List { new() { Key = key, Value = value } }), + StoreId = storeId + }); } } \ No newline at end of file