diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs index e82d0b5a..eba59744 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs @@ -13,6 +13,7 @@ namespace EasyAbp.EShop.Products { [DependsOn( typeof(EShopProductsDomainSharedModule), + typeof(AbpAutoMapperModule), typeof(AbpCachingModule), typeof(AbpTreesDomainModule) )] diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryRollbackOrderCanceledEventHandler.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryRollbackOrderCanceledEventHandler.cs new file mode 100644 index 00000000..6b8f3d70 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryRollbackOrderCanceledEventHandler.cs @@ -0,0 +1,47 @@ +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.EShop.Orders.Orders; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Uow; + +namespace EasyAbp.EShop.Products.Products +{ + // see: https://github.com/EasyAbp/EShop/issues/139 + public class InventoryRollbackOrderCanceledEventHandler : IDistributedEventHandler, ITransientDependency + { + private readonly IProductManager _productManager; + private readonly IProductRepository _productRepository; + + public InventoryRollbackOrderCanceledEventHandler( + IProductManager productManager, + IProductRepository productRepository) + { + _productManager = productManager; + _productRepository = productRepository; + } + + [UnitOfWork(true)] + public virtual async Task HandleEventAsync(OrderCanceledEto eventData) + { + if (eventData.Order.PaidTime.HasValue) + { + return; + } + + foreach (var orderLine in eventData.Order.OrderLines) + { + var product = await _productRepository.GetAsync(orderLine.ProductId); + + if (product.InventoryStrategy is InventoryStrategy.NoNeed or InventoryStrategy.ReduceAfterPayment) + { + continue; + } + + var productSku = product.ProductSkus.Single(x => x.Id == orderLine.ProductSkuId); + + await _productManager.TryIncreaseInventoryAsync(product, productSku, orderLine.Quantity, true); + } + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/InventoryRollbackTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/InventoryRollbackTests.cs new file mode 100644 index 00000000..6a93e3bc --- /dev/null +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/InventoryRollbackTests.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.EShop.Orders.Orders; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace EasyAbp.EShop.Products.Products +{ + public class InventoryRollbackTests : ProductsDomainTestBase + { + private InventoryRollbackOrderCanceledEventHandler EventHandler { get; } + private IProductRepository ProductRepository { get; } + private IProductManager ProductManager { get; } + + public InventoryRollbackTests() + { + EventHandler = ServiceProvider.GetRequiredService(); + ProductRepository = ServiceProvider.GetRequiredService(); + ProductManager = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task Should_Roll_Back_ReduceAfterPlacing_Inventory_If_Order_Is_Not_Paid() + { + await TestAsync(InventoryStrategy.ReduceAfterPlacing, false, true); + } + + [Fact] + public async Task Should_Not_Roll_Back_ReduceAfterPlacing_Inventory_If_Order_Is_Paid() + { + await TestAsync(InventoryStrategy.ReduceAfterPlacing, true, false); + } + + [Fact] + public async Task Should_Not_Roll_Back_ReduceAfterPayment_Inventory_If_Order_Is_Not_Paid() + { + await TestAsync(InventoryStrategy.ReduceAfterPayment, false, false); + } + + [Fact] + public async Task Should_Not_Roll_Back_ReduceAfterPayment_Inventory_If_Order_Is_Paid() + { + await TestAsync(InventoryStrategy.ReduceAfterPayment, true, false); + } + + [Fact] + public async Task Should_Not_Roll_Back_NoNeed_Inventory_If_Order_Is_Not_Paid() + { + await TestAsync(InventoryStrategy.NoNeed, false, false); + } + + [Fact] + public async Task Should_Not_Roll_Back_NoNeed_Inventory_If_Order_Is_Paid() + { + await TestAsync(InventoryStrategy.NoNeed, true, false); + } + + protected async Task TestAsync(InventoryStrategy inventoryStrategy, bool orderPaid, bool expectRollback) + { + var product = await ProductRepository.GetAsync(ProductsTestData.Product1Id); + var productSku = product.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id); + + typeof(Product).GetProperty("InventoryStrategy")!.SetValue(product, inventoryStrategy); + + await ProductRepository.UpdateAsync(product, true); + + (await ProductManager.TryIncreaseInventoryAsync(product, productSku, 3, false)).ShouldBeTrue(); + (await ProductManager.TryReduceInventoryAsync(product, productSku, 3, true)).ShouldBeTrue(); + + var inventoryDataModel = await ProductManager.GetInventoryDataAsync(product, productSku); + + inventoryDataModel.Inventory.ShouldBe(0); + + await EventHandler.HandleEventAsync(new OrderCanceledEto( + new OrderEto + { + #region properties + + Id = Guid.NewGuid(), + TenantId = null, + StoreId = ProductsTestData.Store1Id, + OrderNumber = null, + CustomerUserId = Guid.NewGuid(), + OrderStatus = OrderStatus.Processing, + Currency = null, + ProductTotalPrice = 0, + TotalDiscount = 0, + TotalPrice = 0, + ActualTotalPrice = 0, + RefundAmount = 0, + CustomerRemark = null, + StaffRemark = null, + PaymentId = orderPaid ? Guid.NewGuid() : null, + PaidTime = orderPaid ? DateTime.Now : null, + CompletionTime = null, + CanceledTime = null, + CancellationReason = null, + ReducedInventoryAfterPlacingTime = null, + ReducedInventoryAfterPaymentTime = null, + PaymentExpiration = null, + OrderLines = new List + { + new() + { + Id = Guid.NewGuid(), + ProductId = product.Id, + ProductSkuId = productSku.Id, + ProductModificationTime = default, + ProductDetailModificationTime = default, + ProductGroupName = null, + ProductGroupDisplayName = null, + ProductUniqueName = null, + ProductDisplayName = null, + SkuName = null, + SkuDescription = null, + MediaResources = null, + Currency = null, + UnitPrice = 0, + TotalPrice = 0, + TotalDiscount = 0, + ActualTotalPrice = 0, + Quantity = 3, + RefundedQuantity = 0, + RefundAmount = 0 + } + }, + CreationTime = default, + CreatorId = null, + LastModificationTime = null, + LastModifierId = null, + IsDeleted = false, + DeletionTime = null, + DeleterId = null + + #endregion + } + )); + + product = await ProductRepository.GetAsync(ProductsTestData.Product1Id); + productSku = product.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id); + + inventoryDataModel = await ProductManager.GetInventoryDataAsync(product, productSku); + + inventoryDataModel.Inventory.ShouldBe(expectRollback ? 3 : 0); + } + } +}