diff --git a/src/Business/Grand.Business.Catalog/Services/Discounts/DiscountValidationService.cs b/src/Business/Grand.Business.Catalog/Services/Discounts/DiscountValidationService.cs index 52f6287dd2..530335039e 100644 --- a/src/Business/Grand.Business.Catalog/Services/Discounts/DiscountValidationService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Discounts/DiscountValidationService.cs @@ -1,5 +1,4 @@ using Grand.Business.Core.Interfaces.Catalog.Discounts; -using Grand.Business.Core.Queries.Catalog; using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Customers; @@ -8,23 +7,22 @@ using Grand.Domain.Orders; using Grand.Domain.Stores; using Grand.Infrastructure.Extensions; -using Grand.Mediator; namespace Grand.Business.Catalog.Services.Discounts; public class DiscountValidationService : IDiscountValidationService { private readonly IRepository _discountCouponRepository; + private readonly IRepository _discountUsageHistoryRepository; private readonly IDiscountProviderLoader _discountProviderLoader; - private readonly IMediator _mediator; public DiscountValidationService( IDiscountProviderLoader discountProviderLoader, IRepository discountCouponRepository, - IMediator mediator) + IRepository discountUsageHistoryRepository) { _discountCouponRepository = discountCouponRepository; - _mediator = mediator; + _discountUsageHistoryRepository = discountUsageHistoryRepository; _discountProviderLoader = discountProviderLoader; } @@ -163,17 +161,15 @@ public virtual async Task ValidateDiscount(Discount di { case DiscountLimitationType.NTimes: { - var usedTimes = await _mediator.Send(new GetDiscountUsageHistoryQuery - { DiscountId = discount.Id, PageSize = 1 }); - if (usedTimes.TotalCount >= discount.LimitationTimes) + var usedTimes = await GetUsedTimes(discount.Id, null); + if (usedTimes >= discount.LimitationTimes) return result; } break; case DiscountLimitationType.NTimesPerUser: { - var usedTimes = await _mediator.Send(new GetDiscountUsageHistoryQuery - { DiscountId = discount.Id, CustomerId = customer.Id, PageSize = 1 }); - if (usedTimes.TotalCount >= discount.LimitationTimes) + var usedTimes = await GetUsedTimes(discount.Id, customer.Id); + if (usedTimes >= discount.LimitationTimes) { result.UserErrorResource = "ShoppingCart.Discount.CannotBeUsedAnymore"; return result; @@ -219,6 +215,18 @@ public virtual async Task ValidateDiscount(Discount di return result; } + /// + /// Counts the usage history of a discount, of one customer when customerId is set + /// + private Task GetUsedTimes(string discountId, string customerId) + { + var query = _discountUsageHistoryRepository.Table.Where(x => x.DiscountId == discountId); + if (!string.IsNullOrEmpty(customerId)) + query = query.Where(x => x.CustomerId == customerId); + + return _discountUsageHistoryRepository.CountAsync(query); + } + /// /// Exist coupon code in discount /// @@ -237,7 +245,6 @@ public async Task ExistsCodeInDiscount(string couponCode, string discountI if (used.HasValue) query = query.Where(x => x.Used == used.Value); - var result = await _discountCouponRepository.ToListAsync(query); - return result.Count != 0; + return await _discountCouponRepository.AnyAsync(query); } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Discounts/DiscountServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Discounts/DiscountServiceTests.cs index 4d2c8f33cd..a10f717453 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Discounts/DiscountServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Discounts/DiscountServiceTests.cs @@ -57,7 +57,8 @@ public void InitializeTests() _discountAmountProviders = new List { new DiscountAmountProviderTests() }; _discountProviderLoader = new DiscountProviderLoader(_discountProviders, _discountAmountProviders); _discountValidationService = - new DiscountValidationService(_discountProviderLoader, _discountCouponRepository, _mediatorMock.Object); + new DiscountValidationService(_discountProviderLoader, _discountCouponRepository, + _discountUsageHistoryRepository); _dicountService = new DiscountService(_cacheBase, _repository, _discountCouponRepository, _discountUsageHistoryRepository, _mediatorMock.Object, new AccessControlConfig()); handler = new GetDiscountUsageHistoryQueryHandler(_discountUsageHistoryRepository); @@ -850,4 +851,100 @@ public void LoadDiscountAmountProvidersTest() //Assert Assert.HasCount(1, discountProviders); } + + [TestMethod] + public async Task ValidateDiscountTest_NTimes_InValid_WhenLimitReached() + { + //Arrange + var discount = await InsertLimitedDiscount(DiscountLimitationType.NTimes, 2); + await InsertUsage(discount.Id, "customer1"); + await InsertUsage(discount.Id, "customer2"); + //Act + var result = await _discountValidationService.ValidateDiscount(discount, new Customer(), new Store(), + new Currency { CurrencyCode = "USD" }); + //Assert + Assert.IsFalse(result.IsValid); + } + + [TestMethod] + public async Task ValidateDiscountTest_NTimes_Valid_BelowLimit() + { + //Arrange + var discount = await InsertLimitedDiscount(DiscountLimitationType.NTimes, 2); + await InsertUsage(discount.Id, "customer1"); + await InsertUsage("otherDiscount", "customer1"); + //Act + var result = await _discountValidationService.ValidateDiscount(discount, new Customer(), new Store(), + new Currency { CurrencyCode = "USD" }); + //Assert + Assert.IsTrue(result.IsValid); + } + + [TestMethod] + public async Task ValidateDiscountTest_NTimesPerUser_CountsOnlyTheCustomer() + { + //Arrange + var discount = await InsertLimitedDiscount(DiscountLimitationType.NTimesPerUser, 1); + var customer = new Customer(); + await InsertUsage(discount.Id, "otherCustomer"); + //Act + var valid = await _discountValidationService.ValidateDiscount(discount, customer, new Store(), + new Currency { CurrencyCode = "USD" }); + await InsertUsage(discount.Id, customer.Id); + var used = await _discountValidationService.ValidateDiscount(discount, customer, new Store(), + new Currency { CurrencyCode = "USD" }); + //Assert + Assert.IsTrue(valid.IsValid); + Assert.IsFalse(used.IsValid); + Assert.AreEqual("ShoppingCart.Discount.CannotBeUsedAnymore", used.UserErrorResource); + } + + [TestMethod] + public async Task ValidateDiscountTest_NTimes_SeesUsageRecordedInTheScope() + { + //Arrange + var discount = await InsertLimitedDiscount(DiscountLimitationType.NTimes, 1); + //Act - order placement validates, records the usage, and the next order validates again + var first = await _discountValidationService.ValidateDiscount(discount, new Customer(), new Store(), + new Currency { CurrencyCode = "USD" }); + await InsertUsage(discount.Id, "customer1"); + var second = await _discountValidationService.ValidateDiscount(discount, new Customer(), new Store(), + new Currency { CurrencyCode = "USD" }); + //Assert + Assert.IsTrue(first.IsValid); + Assert.IsFalse(second.IsValid); + } + + [TestMethod] + public async Task ExistsCodeInDiscountTest() + { + //Arrange + await _discountCouponRepository.InsertAsync(new DiscountCoupon + { CouponCode = "USED1", DiscountId = "discount1", Used = true }); + //Act & Assert + Assert.IsTrue(await _discountValidationService.ExistsCodeInDiscount("USED1", "discount1", null)); + Assert.IsTrue(await _discountValidationService.ExistsCodeInDiscount("USED1", "discount1", true)); + Assert.IsFalse(await _discountValidationService.ExistsCodeInDiscount("USED1", "discount1", false)); + Assert.IsFalse(await _discountValidationService.ExistsCodeInDiscount("USED1", "discount2", null)); + Assert.IsFalse(await _discountValidationService.ExistsCodeInDiscount("", "discount1", null)); + } + + private async Task InsertLimitedDiscount(DiscountLimitationType limitation, int times) + { + var discount = new Discount { + Name = "limited", + IsEnabled = true, + CurrencyCode = "USD", + DiscountLimitationId = limitation, + LimitationTimes = times + }; + await _dicountService.InsertDiscount(discount); + return discount; + } + + private Task InsertUsage(string discountId, string customerId) + { + return _discountUsageHistoryRepository.InsertAsync(new DiscountUsageHistory + { DiscountId = discountId, CustomerId = customerId, OrderId = "order" }); + } } \ No newline at end of file diff --git a/src/Tests/Grand.Modules.Tests/Plugins/PluginDependencyCycleTests.cs b/src/Tests/Grand.Modules.Tests/Plugins/PluginDependencyCycleTests.cs index e1fd739670..f3af65c82b 100644 --- a/src/Tests/Grand.Modules.Tests/Plugins/PluginDependencyCycleTests.cs +++ b/src/Tests/Grand.Modules.Tests/Plugins/PluginDependencyCycleTests.cs @@ -107,7 +107,8 @@ public void StandardDiscountRules_DoNotDependBackOnPricing() AddMocks(services, typeof(IContextAccessor), typeof(IProductService), typeof(IMediator), typeof(ICurrencyService), typeof(IDiscountService), typeof(ICategoryService), typeof(IBrandService), typeof(ICollectionService), typeof(IVendorService), typeof(IRepository), - typeof(IOrderService), typeof(ITranslationService), typeof(IRequestTranslationService)); + typeof(IRepository), typeof(IOrderService), typeof(ITranslationService), + typeof(IRequestTranslationService)); services.AddSingleton(new ShoppingCartSettings()); services.AddSingleton(new CatalogSettings()); @@ -134,7 +135,8 @@ public void ShippingProviders_DoNotDependBackOnShipping() typeof(IShippingMethodService), typeof(IShippingByWeightService), typeof(ICheckoutAttributeParser), typeof(IContextAccessor), typeof(IProductService), typeof(IMediator), typeof(ICurrencyService), typeof(IDiscountService), typeof(ICategoryService), typeof(IBrandService), typeof(ICollectionService), - typeof(IVendorService), typeof(IRepository), typeof(IOrderService), + typeof(IVendorService), typeof(IRepository), typeof(IRepository), + typeof(IOrderService), typeof(ITranslationService), typeof(IRequestTranslationService)); services.AddSingleton(new ShippingProviderSettings()); services.AddSingleton(new ShippingSettings());